From c29ef6c7b32eaa5dc7d306231d14b9904de75efe Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Thu, 28 Nov 2024 01:05:05 -0600 Subject: [PATCH 01/31] update pyproject.toml to fix dependency issue --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e1e3d89..bd50c03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.12" -consys4py = { git = "https://github.com/Botts-Innovative-Research/CSAPI4Py.git", branch = "dev" } +conSys4Py = { git = "https://github.com/Botts-Innovative-Research/CSAPI4Py.git", branch = "dev" } swecommondm = { git = "https://github.com/ChainReaction31/SWECommonDMPython.git", branch = "master" } pydantic = "^2.7.4" shapely = "^2.0.4" From 71e43ac2ceae46b2a60c16bbf21e936db3e1d2f2 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Thu, 16 Jan 2025 16:27:12 -0600 Subject: [PATCH 02/31] small typo fix in tutorial.rst --- docs/source/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index 1b0473a..43d1d73 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -156,7 +156,7 @@ datastream schemas. A TimeSchema is required to be the first field in the DataRecordSchema for OSH. Inserting an Observation into and OpenSensorHub Node ------------------------------------------ +----------------------------------------------------- Upon successfully adding a new datastream to a system, it is now possible to send observation data to the node. .. code-block:: python From 3fff8b552c7ee1b0a988516ac14deb635d920fcc Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Fri, 24 Jan 2025 13:55:03 -0600 Subject: [PATCH 03/31] fix a missing reference to urn in creating a System object from a SystemResource --- oshconnect/core_datamodels.py | 3 ++- oshconnect/datasource.py | 2 +- oshconnect/osh_connect_datamodels.py | 7 +++++-- tests/test_oshconnect.py | 7 ++++--- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/oshconnect/core_datamodels.py b/oshconnect/core_datamodels.py index f070316..1a05072 100644 --- a/oshconnect/core_datamodels.py +++ b/oshconnect/core_datamodels.py @@ -8,7 +8,8 @@ from typing import List -from conSys4Py import DatastreamSchema, Geometry +from conSys4Py.datamodels.swe_components import Geometry +from conSys4Py.datamodels.datastreams import DatastreamSchema from conSys4Py.datamodels.api_utils import Link from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny from shapely import Point diff --git a/oshconnect/datasource.py b/oshconnect/datasource.py index 6ac802c..e937b3b 100644 --- a/oshconnect/datasource.py +++ b/oshconnect/datasource.py @@ -16,7 +16,7 @@ import requests import websockets -from conSys4Py import APIResourceTypes +from conSys4Py.constants import APIResourceTypes from conSys4Py.datamodels.observations import ObservationOMJSONInline from conSys4Py.datamodels.swe_components import DataRecordSchema diff --git a/oshconnect/osh_connect_datamodels.py b/oshconnect/osh_connect_datamodels.py index 4a3f9c9..e89dda6 100644 --- a/oshconnect/osh_connect_datamodels.py +++ b/oshconnect/osh_connect_datamodels.py @@ -10,10 +10,11 @@ import uuid from dataclasses import dataclass, field -from conSys4Py import APIResourceTypes, DataRecordSchema +from conSys4Py.constants import APIResourceTypes from conSys4Py.core.default_api_helpers import APIHelper from conSys4Py.datamodels.datastreams import SWEDatastreamSchema from conSys4Py.datamodels.encoding import JSONEncoding +from conSys4Py.datamodels.swe_components import DataRecordSchema from .core_datamodels import DatastreamResource, ObservationResource, SystemResource from .timemanagement import TimeInstant, TimePeriod, TimeUtils @@ -188,7 +189,9 @@ def from_system_resource(system_resource: SystemResource): # case 1: has properties a la geojson if 'properties' in other_props: new_system = System(name=other_props['properties']['name'], - label=other_props['properties']['name']) + label=other_props['properties']['name'], + urn=other_props['properties']['uid'], + resource_id=system_resource.system_id) else: new_system = System(name=system_resource.name, label=system_resource.label, urn=system_resource.urn, diff --git a/tests/test_oshconnect.py b/tests/test_oshconnect.py index d3b664c..29013e4 100644 --- a/tests/test_oshconnect.py +++ b/tests/test_oshconnect.py @@ -14,6 +14,7 @@ class TestOSHConnect: + TEST_PORT = 8282 def test_time_period(self): tp = TimePeriod(start="2024-06-18T15:46:32Z", end="2024-06-18T20:00:00Z") @@ -44,7 +45,7 @@ def test_oshconnect_create(self): def test_oshconnect_add_node(self): app = OSHConnect(name="Test OSH Connect") - node = Node(address="http://localhost", port=8585) + node = Node(address="http://localhost", port=self.TEST_PORT, protocol="http", username="admin", password="admin") # node.add_basicauth("admin", "admin") app.add_node(node) assert len(app._nodes) == 1 @@ -52,7 +53,7 @@ def test_oshconnect_add_node(self): def test_find_systems(self): app = OSHConnect(name="Test OSH Connect") - node = Node(address="localhost", port=8585, username="admin", password="admin", protocol="http") + node = Node(address="localhost", port=self.TEST_PORT, username="admin", password="admin", protocol="http") # node.add_basicauth("admin", "admin") app.add_node(node) app.discover_systems() @@ -62,7 +63,7 @@ def test_find_systems(self): def test_oshconnect_find_datastreams(self): app = OSHConnect(name="Test OSH Connect") - node = Node(address="localhost", port=8585, username="admin", password="admin", protocol="http") + node = Node(address="localhost", port=self.TEST_PORT, username="admin", password="admin", protocol="http") app.add_node(node) app.discover_systems() From b4c5153b5006d60ac9af372bfa9538a875dddac5 Mon Sep 17 00:00:00 2001 From: ChainReaction31 Date: Mon, 27 Jan 2025 22:19:59 -0600 Subject: [PATCH 04/31] update dependencies --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bd50c03..15e7f3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,11 +7,11 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.12" -conSys4Py = { git = "https://github.com/Botts-Innovative-Research/CSAPI4Py.git", branch = "dev" } -swecommondm = { git = "https://github.com/ChainReaction31/SWECommonDMPython.git", branch = "master" } pydantic = "^2.7.4" shapely = "^2.0.4" websockets = "^12.0" +consys4py = "^0.0.1a1" +swecommondm = "^0.0.1a0" [tool.poetry.group.dev.dependencies] pytest = "^8.2.2" From 96a90c64e291dde38fe62f9ad4bb62f1ac567232 Mon Sep 17 00:00:00 2001 From: ChainReaction31 Date: Mon, 27 Jan 2025 22:24:42 -0600 Subject: [PATCH 05/31] rename "conSys4Py" to "consys4py" to reflect fixed name preventing builds --- oshconnect/control.py | 6 +++--- oshconnect/core_datamodels.py | 6 +++--- oshconnect/datasource.py | 8 ++++---- oshconnect/osh_connect_datamodels.py | 10 +++++----- oshconnect/oshconnectapi.py | 2 +- pyproject.toml | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/oshconnect/control.py b/oshconnect/control.py index e7c3955..fdce1b3 100644 --- a/oshconnect/control.py +++ b/oshconnect/control.py @@ -5,9 +5,9 @@ # Contact Email: ian@botts-inc.com # ============================================================================== import websockets -from conSys4Py.comm.mqtt import MQTTCommClient -from conSys4Py.datamodels.commands import CommandJSON -from conSys4Py.datamodels.control_streams import ControlStreamJSONSchema +from consys4py.comm.mqtt import MQTTCommClient +from consys4py.datamodels.commands import CommandJSON +from consys4py.datamodels.control_streams import ControlStreamJSONSchema from oshconnect.osh_connect_datamodels import System diff --git a/oshconnect/core_datamodels.py b/oshconnect/core_datamodels.py index 1a05072..a95e919 100644 --- a/oshconnect/core_datamodels.py +++ b/oshconnect/core_datamodels.py @@ -8,9 +8,9 @@ from typing import List -from conSys4Py.datamodels.swe_components import Geometry -from conSys4Py.datamodels.datastreams import DatastreamSchema -from conSys4Py.datamodels.api_utils import Link +from consys4py.datamodels.swe_components import Geometry +from consys4py.datamodels.datastreams import DatastreamSchema +from consys4py.datamodels.api_utils import Link from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny from shapely import Point diff --git a/oshconnect/datasource.py b/oshconnect/datasource.py index e937b3b..dcefa1f 100644 --- a/oshconnect/datasource.py +++ b/oshconnect/datasource.py @@ -16,9 +16,9 @@ import requests import websockets -from conSys4Py.constants import APIResourceTypes -from conSys4Py.datamodels.observations import ObservationOMJSONInline -from conSys4Py.datamodels.swe_components import DataRecordSchema +from consys4py.constants import APIResourceTypes +from consys4py.datamodels.observations import ObservationOMJSONInline +from consys4py.datamodels.swe_components import DataRecordSchema from .core_datamodels import DatastreamResource, SystemResource, TimePeriod from .timemanagement import TemporalModes @@ -73,7 +73,7 @@ def __init__(self, name: str, datastream: DatastreamResource, # t_url = f'http://{self._parent_system.get_parent_node().get_address()}:{self._parent_system.get_parent_node().get_port()}' # - # res = conSys4Py.part_2.datastreams.retrieve_datastream_schema(t_url, + # res = consys4py.part_2.datastreams.retrieve_datastream_schema(t_url, # datastream_id=self._datastream.ds_id, # api_root=self._parent_system.get_parent_node()._api_helper.api_root, # headers=self._extra_headers) diff --git a/oshconnect/osh_connect_datamodels.py b/oshconnect/osh_connect_datamodels.py index e89dda6..6f1899e 100644 --- a/oshconnect/osh_connect_datamodels.py +++ b/oshconnect/osh_connect_datamodels.py @@ -10,11 +10,11 @@ import uuid from dataclasses import dataclass, field -from conSys4Py.constants import APIResourceTypes -from conSys4Py.core.default_api_helpers import APIHelper -from conSys4Py.datamodels.datastreams import SWEDatastreamSchema -from conSys4Py.datamodels.encoding import JSONEncoding -from conSys4Py.datamodels.swe_components import DataRecordSchema +from consys4py.constants import APIResourceTypes +from consys4py.core.default_api_helpers import APIHelper +from consys4py.datamodels.datastreams import SWEDatastreamSchema +from consys4py.datamodels.encoding import JSONEncoding +from consys4py.datamodels.swe_components import DataRecordSchema from .core_datamodels import DatastreamResource, ObservationResource, SystemResource from .timemanagement import TimeInstant, TimePeriod, TimeUtils diff --git a/oshconnect/oshconnectapi.py b/oshconnect/oshconnectapi.py index 4157260..da06767 100644 --- a/oshconnect/oshconnectapi.py +++ b/oshconnect/oshconnectapi.py @@ -7,7 +7,7 @@ import logging import shelve -from conSys4Py.core.default_api_helpers import APIHelper +from consys4py.core.default_api_helpers import APIHelper from .core_datamodels import DatastreamResource, TimePeriod from .datasource import DataStream, DataStreamHandler, MessageWrapper diff --git a/pyproject.toml b/pyproject.toml index 15e7f3e..246230b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "oshconnect" -version = "0.2.0" +version = "0.2.1" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." authors = ["Ian Patterson "] readme = "README.md" From c293de6119a3b24100a6bdc1f9329fb2f974891e Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 16 Apr 2025 18:38:40 -0500 Subject: [PATCH 06/31] bump version to 0.2.2 and update consys4py dependency to 0.0.1a8 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 246230b..4241b96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "oshconnect" -version = "0.2.1" +version = "0.2.2" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." authors = ["Ian Patterson "] readme = "README.md" @@ -10,7 +10,7 @@ python = "^3.12" pydantic = "^2.7.4" shapely = "^2.0.4" websockets = "^12.0" -consys4py = "^0.0.1a1" +consys4py = "^0.0.1a8" swecommondm = "^0.0.1a0" [tool.poetry.group.dev.dependencies] From c46ef952ebb72643efaba342894fd73f13a07c7a Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 16 Apr 2025 19:13:22 -0500 Subject: [PATCH 07/31] bump version to 0.2.3 and update dependency specifications in pyproject.toml --- pyproject.toml | 40 ++-- uv.lock | 631 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 651 insertions(+), 20 deletions(-) create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml index 4241b96..c08198e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,24 +1,24 @@ -[tool.poetry] +[project] name = "oshconnect" -version = "0.2.2" +version = "0.2.3" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." -authors = ["Ian Patterson "] readme = "README.md" +authors = [ + { name = "Ian Patterson", email = "ian@botts-inc.com" }, +] +requires-python = "<4.0,>=3.12" +dependencies = [ + "pydantic<3.0.0,>=2.7.4", + "shapely<3.0.0,>=2.0.4", + "websockets<13.0,>=12.0", + "consys4py<1.0.0,>=0.0.1a8", + "swecommondm<1.0.0,>=0.0.1a0", +] -[tool.poetry.dependencies] -python = "^3.12" -pydantic = "^2.7.4" -shapely = "^2.0.4" -websockets = "^12.0" -consys4py = "^0.0.1a8" -swecommondm = "^0.0.1a0" - -[tool.poetry.group.dev.dependencies] -pytest = "^8.2.2" -sphinx = "^7.3.7" -flake8 = "^7.1.0" -sphinx-rtd-theme = "^2.0.0" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[dependency-groups] +dev = [ + "pytest<9.0.0,>=8.2.2", + "sphinx<8.0.0,>=7.3.7", + "flake8<8.0.0,>=7.1.0", + "sphinx-rtd-theme<3.0.0,>=2.0.0", +] \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..65b20d5 --- /dev/null +++ b/uv.lock @@ -0,0 +1,631 @@ +version = 1 +revision = 1 +requires-python = ">=3.12, <4.0" + +[[package]] +name = "alabaster" +version = "0.7.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "consys4py" +version = "0.0.1a8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "paho-mqtt" }, + { name = "pydantic" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/ba/c3cd77f11bb32a87b6a55b60a03c1526324feb3ac0e2f6edb9053b160ba3/consys4py-0.0.1a8.tar.gz", hash = "sha256:26133ff3737c88470e2b622dd73cc46411f4f6a95affc2c91cd7f137d2d302a2", size = 16875 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/00/2d8f77f8974b556ac3dc395c736f4e5193117b37187b7814ec2025f6b76f/consys4py-0.0.1a8-py3-none-any.whl", hash = "sha256:b4846216242e59b484dbf2e9a66d42e75c59bdbe5e9c386bd8f04c42dffb3b6d", size = 31100 }, +] + +[[package]] +name = "docutils" +version = "0.20.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/53/a5da4f2c5739cf66290fac1431ee52aff6851c7c8ffd8264f13affd7bcdd/docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b", size = 2058365 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", size = 572666 }, +] + +[[package]] +name = "flake8" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mccabe" }, + { name = "pycodestyle" }, + { name = "pyflakes" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, +] + +[[package]] +name = "numpy" +version = "2.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156 }, + { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092 }, + { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515 }, + { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558 }, + { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 }, + { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 }, + { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 }, + { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 }, + { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 }, + { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 }, + { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 }, + { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 }, + { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 }, + { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 }, + { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 }, + { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 }, + { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 }, + { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 }, + { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 }, + { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 }, + { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 }, + { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 }, + { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 }, + { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 }, + { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 }, + { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 }, + { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 }, + { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 }, + { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 }, + { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, +] + +[[package]] +name = "oshconnect" +version = "0.2.3" +source = { virtual = "." } +dependencies = [ + { name = "consys4py" }, + { name = "pydantic" }, + { name = "shapely" }, + { name = "swecommondm" }, + { name = "websockets" }, +] + +[package.dev-dependencies] +dev = [ + { name = "flake8" }, + { name = "pytest" }, + { name = "sphinx" }, + { name = "sphinx-rtd-theme" }, +] + +[package.metadata] +requires-dist = [ + { name = "consys4py", specifier = ">=0.0.1a8,<1.0.0" }, + { name = "pydantic", specifier = ">=2.7.4,<3.0.0" }, + { name = "shapely", specifier = ">=2.0.4,<3.0.0" }, + { name = "swecommondm", specifier = ">=0.0.1a0,<1.0.0" }, + { name = "websockets", specifier = ">=12.0,<13.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "flake8", specifier = ">=7.1.0,<8.0.0" }, + { name = "pytest", specifier = ">=8.2.2,<9.0.0" }, + { name = "sphinx", specifier = ">=7.3.7,<8.0.0" }, + { name = "sphinx-rtd-theme", specifier = ">=2.0.0,<3.0.0" }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "paho-mqtt" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/15/0a6214e76d4d32e7f663b109cf71fb22561c2be0f701d67f93950cd40542/paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834", size = 148848 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pycodestyle" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424 }, +] + +[[package]] +name = "pydantic" +version = "2.11.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 }, + { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 }, + { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 }, + { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 }, + { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 }, + { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 }, + { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 }, + { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 }, + { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 }, + { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 }, + { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 }, + { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 }, + { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 }, + { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 }, + { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 }, + { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 }, + { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 }, + { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 }, + { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 }, + { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 }, + { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 }, + { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 }, + { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 }, + { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 }, + { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 }, + { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, + { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, + { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, + { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 }, + { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 }, + { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, +] + +[[package]] +name = "pyflakes" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "shapely" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/fe/3b0d2f828ffaceadcdcb51b75b9c62d98e62dd95ce575278de35f24a1c20/shapely-2.1.0.tar.gz", hash = "sha256:2cbe90e86fa8fc3ca8af6ffb00a77b246b918c7cf28677b7c21489b678f6b02e", size = 313617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/d1/6a9371ec39d3ef08e13225594e6c55b045209629afd9e6d403204507c2a8/shapely-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53e7ee8bd8609cf12ee6dce01ea5affe676976cf7049315751d53d8db6d2b4b2", size = 1830732 }, + { url = "https://files.pythonhosted.org/packages/32/87/799e3e48be7ce848c08509b94d2180f4ddb02e846e3c62d0af33da4d78d3/shapely-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cab20b665d26dbec0b380e15749bea720885a481fa7b1eedc88195d4a98cfa4", size = 1638404 }, + { url = "https://files.pythonhosted.org/packages/85/00/6665d77f9dd09478ab0993b8bc31668aec4fd3e5f1ddd1b28dd5830e47be/shapely-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4a38b39a09340273c3c92b3b9a374272a12cc7e468aeeea22c1c46217a03e5c", size = 2945316 }, + { url = "https://files.pythonhosted.org/packages/34/49/738e07d10bbc67cae0dcfe5a484c6e518a517f4f90550dda2adf3a78b9f2/shapely-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edaec656bdd9b71278b98e6f77c464b1c3b2daa9eace78012ff0f0b4b5b15b04", size = 3063099 }, + { url = "https://files.pythonhosted.org/packages/88/b8/138098674559362ab29f152bff3b6630de423378fbb0324812742433a4ef/shapely-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c8a732ddd9b25e7a54aa748e7df8fd704e23e5d5d35b7d376d80bffbfc376d04", size = 3887873 }, + { url = "https://files.pythonhosted.org/packages/67/a8/fdae7c2db009244991d86f4d2ca09d2f5ccc9d41c312c3b1ee1404dc55da/shapely-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9c93693ad8adfdc9138a5a2d42da02da94f728dd2e82d2f0f442f10e25027f5f", size = 4067004 }, + { url = "https://files.pythonhosted.org/packages/ed/78/17e17d91b489019379df3ee1afc4bd39787b232aaa1d540f7d376f0280b7/shapely-2.1.0-cp312-cp312-win32.whl", hash = "sha256:d8ac6604eefe807e71a908524de23a37920133a1729fe3a4dfe0ed82c044cbf4", size = 1527366 }, + { url = "https://files.pythonhosted.org/packages/b8/bd/9249bd6dda948441e25e4fb14cbbb5205146b0fff12c66b19331f1ff2141/shapely-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f4f47e631aa4f9ec5576eac546eb3f38802e2f82aeb0552f9612cb9a14ece1db", size = 1708265 }, + { url = "https://files.pythonhosted.org/packages/8d/77/4e368704b2193e74498473db4461d697cc6083c96f8039367e59009d78bd/shapely-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b64423295b563f43a043eb786e7a03200ebe68698e36d2b4b1c39f31dfb50dfb", size = 1830029 }, + { url = "https://files.pythonhosted.org/packages/71/3c/d888597bda680e4de987316b05ca9db07416fa29523beff64f846503302f/shapely-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1b5578f45adc25b235b22d1ccb9a0348c8dc36f31983e57ea129a88f96f7b870", size = 1637999 }, + { url = "https://files.pythonhosted.org/packages/03/8d/ee0e23b7ef88fba353c63a81f1f329c77f5703835db7b165e7c0b8b7f839/shapely-2.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a7e83d383b27f02b684e50ab7f34e511c92e33b6ca164a6a9065705dd64bcb", size = 2929348 }, + { url = "https://files.pythonhosted.org/packages/d1/a7/5c9cb413e4e2ce52c16be717e94abd40ce91b1f8974624d5d56154c5d40b/shapely-2.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:942031eb4d8f7b3b22f43ba42c09c7aa3d843aa10d5cc1619fe816e923b66e55", size = 3048973 }, + { url = "https://files.pythonhosted.org/packages/84/23/45b90c0bd2157b238490ca56ef2eedf959d3514c7d05475f497a2c88b6d9/shapely-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d2843c456a2e5627ee6271800f07277c0d2652fb287bf66464571a057dbc00b3", size = 3873148 }, + { url = "https://files.pythonhosted.org/packages/c0/bc/ed7d5d37f5395166042576f0c55a12d7e56102799464ba7ea3a72a38c769/shapely-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8c4b17469b7f39a5e6a7cfea79f38ae08a275427f41fe8b48c372e1449147908", size = 4052655 }, + { url = "https://files.pythonhosted.org/packages/c0/8f/a1dafbb10d20d1c569f2db3fb1235488f624dafe8469e8ce65356800ba31/shapely-2.1.0-cp313-cp313-win32.whl", hash = "sha256:30e967abd08fce49513d4187c01b19f139084019f33bec0673e8dbeb557c45e4", size = 1526600 }, + { url = "https://files.pythonhosted.org/packages/e3/f0/9f8cdf2258d7aed742459cea51c70d184de92f5d2d6f5f7f1ded90a18c31/shapely-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:1dc8d4364483a14aba4c844b7bd16a6fa3728887e2c33dfa1afa34a3cf4d08a5", size = 1707115 }, + { url = "https://files.pythonhosted.org/packages/75/ed/32952df461753a65b3e5d24c8efb361d3a80aafaef0b70d419063f6f2c11/shapely-2.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:673e073fea099d1c82f666fb7ab0a00a77eff2999130a69357ce11941260d855", size = 1824847 }, + { url = "https://files.pythonhosted.org/packages/ff/b9/2284de512af30b02f93ddcdd2e5c79834a3cf47fa3ca11b0f74396feb046/shapely-2.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6d1513f915a56de67659fe2047c1ad5ff0f8cbff3519d1e74fced69c9cb0e7da", size = 1631035 }, + { url = "https://files.pythonhosted.org/packages/35/16/a59f252a7e736b73008f10d0950ffeeb0d5953be7c0bdffd39a02a6ba310/shapely-2.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d6a7043178890b9e028d80496ff4c79dc7629bff4d78a2f25323b661756bab8", size = 2968639 }, + { url = "https://files.pythonhosted.org/packages/a5/0a/6a20eca7b0092cfa243117e8e145a58631a4833a0a519ec9b445172e83a0/shapely-2.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb638378dc3d76f7e85b67d7e2bb1366811912430ac9247ac00c127c2b444cdc", size = 3055713 }, + { url = "https://files.pythonhosted.org/packages/fb/44/eeb0c7583b1453d1cf7a319a1d738e08f98a5dc993fa1ef3c372983e4cb5/shapely-2.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:737124e87d91d616acf9a911f74ac55e05db02a43a6a7245b3d663817b876055", size = 3890478 }, + { url = "https://files.pythonhosted.org/packages/5d/6e/37ff3c6af1d408cacb0a7d7bfea7b8ab163a5486e35acb08997eae9d8756/shapely-2.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e6c229e7bb87aae5df82fa00b6718987a43ec168cc5affe095cca59d233f314", size = 4036148 }, + { url = "https://files.pythonhosted.org/packages/c8/6a/8c0b7de3aeb5014a23f06c5e9d3c7852ebcf0d6b00fe660b93261e310e24/shapely-2.1.0-cp313-cp313t-win32.whl", hash = "sha256:a9580bda119b1f42f955aa8e52382d5c73f7957e0203bc0c0c60084846f3db94", size = 1535993 }, + { url = "https://files.pythonhosted.org/packages/a8/91/ae80359a58409d52e4d62c7eacc7eb3ddee4b9135f1db884b6a43cf2e174/shapely-2.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e8ff4e5cfd799ba5b6f37b5d5527dbd85b4a47c65b6d459a03d0962d2a9d4d10", size = 1717777 }, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, +] + +[[package]] +name = "sphinx" +version = "7.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624 }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/33/2a35a9cdbfda9086bda11457bcc872173ab3565b16b6d7f6b3efaa6dc3d6/sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b", size = 2785005 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/46/00fda84467815c29951a9c91e3ae7503c409ddad04373e7cfc78daad4300/sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586", size = 2824721 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104 }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, +] + +[[package]] +name = "swecommondm" +version = "0.0.1a0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/89/a88489999fd0d1b3409c198439962d9c41db98f1af239d79329f38554c73/swecommondm-0.0.1a0-py3-none-any.whl", hash = "sha256:e0d4fe9b15aa1516e10ce4f20c592ebbf544a76ef3af644c9efd8654de74f8f7", size = 13502 }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, +] + +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, +] + +[[package]] +name = "websockets" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/62/7a7874b7285413c954a4cca3c11fd851f11b2fe5b4ae2d9bee4f6d9bdb10/websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", size = 104994 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/6d/23cc898647c8a614a0d9ca703695dd04322fb5135096a20c2684b7c852b6/websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", size = 124061 }, + { url = "https://files.pythonhosted.org/packages/39/34/364f30fdf1a375e4002a26ee3061138d1571dfda6421126127d379d13930/websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", size = 121296 }, + { url = "https://files.pythonhosted.org/packages/2e/00/96ae1c9dcb3bc316ef683f2febd8c97dde9f254dc36c3afc65c7645f734c/websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", size = 121326 }, + { url = "https://files.pythonhosted.org/packages/af/f1/bba1e64430685dd456c1a1fd6b0c791ae33104967b928aefeff261761e8d/websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", size = 131807 }, + { url = "https://files.pythonhosted.org/packages/62/3b/98ee269712f37d892b93852ce07b3e6d7653160ca4c0d4f8c8663f8021f8/websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", size = 130751 }, + { url = "https://files.pythonhosted.org/packages/f1/00/d6f01ca2b191f8b0808e4132ccd2e7691f0453cbd7d0f72330eb97453c3a/websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", size = 131176 }, + { url = "https://files.pythonhosted.org/packages/af/9c/703ff3cd8109dcdee6152bae055d852ebaa7750117760ded697ab836cbcf/websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", size = 136246 }, + { url = "https://files.pythonhosted.org/packages/0b/a5/1a38fb85a456b9dc874ec984f3ff34f6550eafd17a3da28753cd3c1628e8/websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", size = 135466 }, + { url = "https://files.pythonhosted.org/packages/3c/98/1261f289dff7e65a38d59d2f591de6ed0a2580b729aebddec033c4d10881/websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", size = 136083 }, + { url = "https://files.pythonhosted.org/packages/a9/1c/f68769fba63ccb9c13fe0a25b616bd5aebeef1c7ddebc2ccc32462fb784d/websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", size = 124460 }, + { url = "https://files.pythonhosted.org/packages/20/52/8915f51f9aaef4e4361c89dd6cf69f72a0159f14e0d25026c81b6ad22525/websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", size = 124985 }, + { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370 }, +] From fced3dcbf03d6d42c15bb0bd582cd79e5811b094 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 16 Apr 2025 19:17:16 -0500 Subject: [PATCH 08/31] add setuptools configuration for oshconnect package in pyproject.toml --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c08198e..ebab807 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,4 +21,7 @@ dev = [ "sphinx<8.0.0,>=7.3.7", "flake8<8.0.0,>=7.1.0", "sphinx-rtd-theme<3.0.0,>=2.0.0", -] \ No newline at end of file +] + +[tool.setuptools] +packages = ["oshconnect"] From 69ea69e68af28b1285853b4fac3b3ca6cb5724d1 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 16 Apr 2025 19:21:20 -0500 Subject: [PATCH 09/31] replace Poetry with uv for dependency management and build processes --- .github/workflows/docs_pages.yaml | 8 +++----- .github/workflows/linting.yaml | 9 ++++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docs_pages.yaml b/.github/workflows/docs_pages.yaml index 90de032..6316fa3 100644 --- a/.github/workflows/docs_pages.yaml +++ b/.github/workflows/docs_pages.yaml @@ -13,16 +13,14 @@ jobs: with: python-version: '3.12' - - name: Install Poetry - uses: snok/install-poetry@v1 - - name: Install dependencies run: | - poetry install --with dev + pip install uv + uv install --group dev - name: Sphinx build run: | - poetry run sphinx-build -b html docs/source docs/build/html + uv run sphinx-build -b html docs/source docs/build/html - name: Deploy documentation uses: peaceiris/actions-gh-pages@v4 diff --git a/.github/workflows/linting.yaml b/.github/workflows/linting.yaml index 8644159..2294b5f 100644 --- a/.github/workflows/linting.yaml +++ b/.github/workflows/linting.yaml @@ -9,12 +9,11 @@ jobs: with: python-version: '3.12' - - name: Install Poetry - uses: snok/install-poetry@v1 - - name: Install dependencies run: | - poetry install --with dev + pip install uv + uv install --group dev + - name: Lint run: | - poetry run flake8 + uv run flake8 oshconnect \ No newline at end of file From b66e124f33e2858c63d744a88a031cc0661e8c3e Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 16 Apr 2025 19:33:51 -0500 Subject: [PATCH 10/31] update dependency installation commands to use 'uv sync --all-extras' --- .github/workflows/docs_pages.yaml | 2 +- .github/workflows/linting.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs_pages.yaml b/.github/workflows/docs_pages.yaml index 6316fa3..f5fc5d6 100644 --- a/.github/workflows/docs_pages.yaml +++ b/.github/workflows/docs_pages.yaml @@ -16,7 +16,7 @@ jobs: - name: Install dependencies run: | pip install uv - uv install --group dev + uv sync --all-extras - name: Sphinx build run: | diff --git a/.github/workflows/linting.yaml b/.github/workflows/linting.yaml index 2294b5f..3bb0167 100644 --- a/.github/workflows/linting.yaml +++ b/.github/workflows/linting.yaml @@ -12,7 +12,7 @@ jobs: - name: Install dependencies run: | pip install uv - uv install --group dev + uv sync --all-extras - name: Lint run: | From 27abcca561ceb75149e7c903cf47b82ec56f7335 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 30 Apr 2025 00:06:51 -0500 Subject: [PATCH 11/31] bump consys4py version to 0.0.1b1 in pyproject.toml and uv.lock --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ebab807..b505d54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dependencies = [ "pydantic<3.0.0,>=2.7.4", "shapely<3.0.0,>=2.0.4", "websockets<13.0,>=12.0", - "consys4py<1.0.0,>=0.0.1a8", + "consys4py<1.0.0,>=0.0.1b1", "swecommondm<1.0.0,>=0.0.1a0", ] diff --git a/uv.lock b/uv.lock index 65b20d5..c07a9d9 100644 --- a/uv.lock +++ b/uv.lock @@ -84,16 +84,16 @@ wheels = [ [[package]] name = "consys4py" -version = "0.0.1a8" +version = "0.0.1b1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "paho-mqtt" }, { name = "pydantic" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/ba/c3cd77f11bb32a87b6a55b60a03c1526324feb3ac0e2f6edb9053b160ba3/consys4py-0.0.1a8.tar.gz", hash = "sha256:26133ff3737c88470e2b622dd73cc46411f4f6a95affc2c91cd7f137d2d302a2", size = 16875 } +sdist = { url = "https://files.pythonhosted.org/packages/62/33/5d4cf57adf7d0d42584638ceaeaacbec66810698b40cb1e302981777b7ba/consys4py-0.0.1b1.tar.gz", hash = "sha256:a571f3ef940773f55c6e4b133bb048bddce13a2c4a8bbf3b37d68d9e0c5d09b4", size = 15720 } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/00/2d8f77f8974b556ac3dc395c736f4e5193117b37187b7814ec2025f6b76f/consys4py-0.0.1a8-py3-none-any.whl", hash = "sha256:b4846216242e59b484dbf2e9a66d42e75c59bdbe5e9c386bd8f04c42dffb3b6d", size = 31100 }, + { url = "https://files.pythonhosted.org/packages/fd/5b/1390bd68753b339e6347237cf79a8755d81d8f4c6c6d7285cd433b1a0bf5/consys4py-0.0.1b1-py3-none-any.whl", hash = "sha256:fdccc610715689f0a9905c89687561653974caaf58316d1eaac46aecde87faae", size = 8256 }, ] [[package]] @@ -265,7 +265,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "consys4py", specifier = ">=0.0.1a8,<1.0.0" }, + { name = "consys4py", specifier = ">=0.0.1b1,<1.0.0" }, { name = "pydantic", specifier = ">=2.7.4,<3.0.0" }, { name = "shapely", specifier = ">=2.0.4,<3.0.0" }, { name = "swecommondm", specifier = ">=0.0.1a0,<1.0.0" }, From d68bd20771eebf471469466dd1dedbabb5d28847 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 30 Apr 2025 00:07:38 -0500 Subject: [PATCH 12/31] bump version to 0.2.4 in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b505d54..216725b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oshconnect" -version = "0.2.3" +version = "0.2.4" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." readme = "README.md" authors = [ From 111c7a4e558f33df82fd4918428059d0f8f0ba63 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 30 Apr 2025 01:33:43 -0500 Subject: [PATCH 13/31] update version to accept latest consys4py fix - update geometry type to GeometrySchema and - bump consys4py version to 0.0.1b3 --- oshconnect/core_datamodels.py | 4 ++-- pyproject.toml | 4 ++-- uv.lock | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/oshconnect/core_datamodels.py b/oshconnect/core_datamodels.py index a95e919..4bd179e 100644 --- a/oshconnect/core_datamodels.py +++ b/oshconnect/core_datamodels.py @@ -8,7 +8,7 @@ from typing import List -from consys4py.datamodels.swe_components import Geometry +from consys4py.datamodels.swe_components import GeometrySchema from consys4py.datamodels.datastreams import DatastreamSchema from consys4py.datamodels.api_utils import Link from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny @@ -94,7 +94,7 @@ class SystemResource(BaseModel): feature_type: str = Field(None, serialization_alias="type") system_id: str = Field(None, serialization_alias="id") properties: dict = Field(None) - geometry: Geometry | None = Field(None) + geometry: GeometrySchema | None = Field(None) bbox: BoundingBox = Field(None) links: List[Link] = Field(None) description: str = Field(None) diff --git a/pyproject.toml b/pyproject.toml index 216725b..761bb2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oshconnect" -version = "0.2.4" +version = "0.2.4-1" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." readme = "README.md" authors = [ @@ -11,7 +11,7 @@ dependencies = [ "pydantic<3.0.0,>=2.7.4", "shapely<3.0.0,>=2.0.4", "websockets<13.0,>=12.0", - "consys4py<1.0.0,>=0.0.1b1", + "consys4py<1.0.0,>=0.0.1b3", "swecommondm<1.0.0,>=0.0.1a0", ] diff --git a/uv.lock b/uv.lock index c07a9d9..1db7aca 100644 --- a/uv.lock +++ b/uv.lock @@ -84,16 +84,16 @@ wheels = [ [[package]] name = "consys4py" -version = "0.0.1b1" +version = "0.0.1b3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "paho-mqtt" }, { name = "pydantic" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/33/5d4cf57adf7d0d42584638ceaeaacbec66810698b40cb1e302981777b7ba/consys4py-0.0.1b1.tar.gz", hash = "sha256:a571f3ef940773f55c6e4b133bb048bddce13a2c4a8bbf3b37d68d9e0c5d09b4", size = 15720 } +sdist = { url = "https://files.pythonhosted.org/packages/9f/26/5185fdf57b9ade4f5b2df9141ed01b938986bd1b8155c534536bd665c6d1/consys4py-0.0.1b3.tar.gz", hash = "sha256:10b8a8a3e3644e811af10b21052d7fa7172fd07b80c8fa60755d0c6ff3d8f816", size = 27114 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/5b/1390bd68753b339e6347237cf79a8755d81d8f4c6c6d7285cd433b1a0bf5/consys4py-0.0.1b1-py3-none-any.whl", hash = "sha256:fdccc610715689f0a9905c89687561653974caaf58316d1eaac46aecde87faae", size = 8256 }, + { url = "https://files.pythonhosted.org/packages/27/ee/27dd55fc1068a89707a4f8205393c8c3424a77e170d7cde42319742e790b/consys4py-0.0.1b3-py3-none-any.whl", hash = "sha256:968cdd83386f64184dda6b3cd478e5b68ae4e77581901393e83a1df83922dcc5", size = 31383 }, ] [[package]] @@ -245,7 +245,7 @@ wheels = [ [[package]] name = "oshconnect" -version = "0.2.3" +version = "0.2.4.post1" source = { virtual = "." } dependencies = [ { name = "consys4py" }, @@ -265,7 +265,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "consys4py", specifier = ">=0.0.1b1,<1.0.0" }, + { name = "consys4py", specifier = ">=0.0.1b3,<1.0.0" }, { name = "pydantic", specifier = ">=2.7.4,<3.0.0" }, { name = "shapely", specifier = ">=2.0.4,<3.0.0" }, { name = "swecommondm", specifier = ">=0.0.1a0,<1.0.0" }, From 87c9ebf1dba651e0349428aaba9475941ea6e6db Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Mon, 15 Sep 2025 16:53:54 -0500 Subject: [PATCH 14/31] initial joining of OSHConnect with previous dep csapi4py --- oshconnect/control.py | 6 +- oshconnect/core_datamodels.py | 8 +- oshconnect/csapi4py/__init__.py | 0 oshconnect/csapi4py/comm/__init__.py | 0 oshconnect/csapi4py/comm/mqtt.py | 197 +++++++ oshconnect/csapi4py/con_sys_api.py | 105 ++++ oshconnect/csapi4py/constants.py | 108 ++++ oshconnect/csapi4py/core/__init__.py | 0 .../csapi4py/core/default_api_helpers.py | 249 +++++++++ oshconnect/csapi4py/endpoints.py | 117 ++++ oshconnect/csapi4py/part_1/__init__.py | 7 + oshconnect/csapi4py/part_1/capabilities.py | 31 ++ oshconnect/csapi4py/part_1/collections_ep.py | 67 +++ oshconnect/csapi4py/part_1/deployments.py | 217 ++++++++ oshconnect/csapi4py/part_1/procedures.py | 97 ++++ oshconnect/csapi4py/part_1/properties.py | 80 +++ .../csapi4py/part_1/sampling_features.py | 116 ++++ oshconnect/csapi4py/part_1/systems.py | 213 ++++++++ oshconnect/csapi4py/part_2/__init__.py | 0 oshconnect/csapi4py/part_2/commands.py | 227 ++++++++ .../csapi4py/part_2/control_channels.py | 159 ++++++ oshconnect/csapi4py/part_2/datastreams.py | 155 ++++++ oshconnect/csapi4py/part_2/observations.py | 122 +++++ oshconnect/csapi4py/part_2/system_events.py | 122 +++++ oshconnect/csapi4py/part_2/system_history.py | 83 +++ oshconnect/csapi4py/querymodel.py | 25 + oshconnect/csapi4py/request_bodies.py | 99 ++++ oshconnect/csapi4py/request_wrappers.py | 58 ++ oshconnect/csapi4py/sensor_ml/__init__.py | 0 oshconnect/csapi4py/sensor_ml/sml.py | 51 ++ oshconnect/csapi4py/utilities/__init__.py | 0 oshconnect/csapi4py/utilities/model_utils.py | 10 + oshconnect/datamodels/__init__.py | 0 oshconnect/datamodels/api_utils.py | 39 ++ oshconnect/datamodels/commands.py | 14 + oshconnect/datamodels/control_streams.py | 39 ++ oshconnect/datamodels/datastreams.py | 25 + oshconnect/datamodels/encoding.py | 11 + oshconnect/datamodels/geometry.py | 14 + oshconnect/datamodels/network_properties.py | 10 + oshconnect/datamodels/observations.py | 19 + oshconnect/datamodels/swe_components.py | 200 +++++++ .../datamodels/system_events_and_history.py | 46 ++ oshconnect/datasource.py | 6 +- oshconnect/osh_connect_datamodels.py | 10 +- oshconnect/oshconnectapi.py | 2 +- pyproject.toml | 3 +- rud/README.md | 3 - rud/__init__.py | 6 - rud/unimpl/__init__.py | 6 - rud/unimpl/datasources/__init__.py | 6 - rud/unimpl/datasources/handler.py | 67 --- uv.lock | 498 +++++++++--------- 53 files changed, 3386 insertions(+), 367 deletions(-) create mode 100644 oshconnect/csapi4py/__init__.py create mode 100644 oshconnect/csapi4py/comm/__init__.py create mode 100644 oshconnect/csapi4py/comm/mqtt.py create mode 100644 oshconnect/csapi4py/con_sys_api.py create mode 100644 oshconnect/csapi4py/constants.py create mode 100644 oshconnect/csapi4py/core/__init__.py create mode 100644 oshconnect/csapi4py/core/default_api_helpers.py create mode 100644 oshconnect/csapi4py/endpoints.py create mode 100644 oshconnect/csapi4py/part_1/__init__.py create mode 100644 oshconnect/csapi4py/part_1/capabilities.py create mode 100644 oshconnect/csapi4py/part_1/collections_ep.py create mode 100644 oshconnect/csapi4py/part_1/deployments.py create mode 100644 oshconnect/csapi4py/part_1/procedures.py create mode 100644 oshconnect/csapi4py/part_1/properties.py create mode 100644 oshconnect/csapi4py/part_1/sampling_features.py create mode 100644 oshconnect/csapi4py/part_1/systems.py create mode 100644 oshconnect/csapi4py/part_2/__init__.py create mode 100644 oshconnect/csapi4py/part_2/commands.py create mode 100644 oshconnect/csapi4py/part_2/control_channels.py create mode 100644 oshconnect/csapi4py/part_2/datastreams.py create mode 100644 oshconnect/csapi4py/part_2/observations.py create mode 100644 oshconnect/csapi4py/part_2/system_events.py create mode 100644 oshconnect/csapi4py/part_2/system_history.py create mode 100644 oshconnect/csapi4py/querymodel.py create mode 100644 oshconnect/csapi4py/request_bodies.py create mode 100644 oshconnect/csapi4py/request_wrappers.py create mode 100644 oshconnect/csapi4py/sensor_ml/__init__.py create mode 100644 oshconnect/csapi4py/sensor_ml/sml.py create mode 100644 oshconnect/csapi4py/utilities/__init__.py create mode 100644 oshconnect/csapi4py/utilities/model_utils.py create mode 100644 oshconnect/datamodels/__init__.py create mode 100644 oshconnect/datamodels/api_utils.py create mode 100644 oshconnect/datamodels/commands.py create mode 100644 oshconnect/datamodels/control_streams.py create mode 100644 oshconnect/datamodels/datastreams.py create mode 100644 oshconnect/datamodels/encoding.py create mode 100644 oshconnect/datamodels/geometry.py create mode 100644 oshconnect/datamodels/network_properties.py create mode 100644 oshconnect/datamodels/observations.py create mode 100644 oshconnect/datamodels/swe_components.py create mode 100644 oshconnect/datamodels/system_events_and_history.py delete mode 100644 rud/README.md delete mode 100644 rud/__init__.py delete mode 100644 rud/unimpl/__init__.py delete mode 100644 rud/unimpl/datasources/__init__.py delete mode 100644 rud/unimpl/datasources/handler.py diff --git a/oshconnect/control.py b/oshconnect/control.py index fdce1b3..01f2c45 100644 --- a/oshconnect/control.py +++ b/oshconnect/control.py @@ -5,9 +5,9 @@ # Contact Email: ian@botts-inc.com # ============================================================================== import websockets -from consys4py.comm.mqtt import MQTTCommClient -from consys4py.datamodels.commands import CommandJSON -from consys4py.datamodels.control_streams import ControlStreamJSONSchema +from oshconnect.csapi4py.comm.mqtt import MQTTCommClient +from oshconnect.datamodels.commands import CommandJSON +from oshconnect.datamodels.control_streams import ControlStreamJSONSchema from oshconnect.osh_connect_datamodels import System diff --git a/oshconnect/core_datamodels.py b/oshconnect/core_datamodels.py index 4bd179e..83fced5 100644 --- a/oshconnect/core_datamodels.py +++ b/oshconnect/core_datamodels.py @@ -8,9 +8,9 @@ from typing import List -from consys4py.datamodels.swe_components import GeometrySchema -from consys4py.datamodels.datastreams import DatastreamSchema -from consys4py.datamodels.api_utils import Link +from oshconnect.datamodels.geometry import Geometry +from oshconnect.datamodels.datastreams import DatastreamSchema +from oshconnect.datamodels.api_utils import Link from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny from shapely import Point @@ -94,7 +94,7 @@ class SystemResource(BaseModel): feature_type: str = Field(None, serialization_alias="type") system_id: str = Field(None, serialization_alias="id") properties: dict = Field(None) - geometry: GeometrySchema | None = Field(None) + geometry: Geometry | None = Field(None) bbox: BoundingBox = Field(None) links: List[Link] = Field(None) description: str = Field(None) diff --git a/oshconnect/csapi4py/__init__.py b/oshconnect/csapi4py/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oshconnect/csapi4py/comm/__init__.py b/oshconnect/csapi4py/comm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oshconnect/csapi4py/comm/mqtt.py b/oshconnect/csapi4py/comm/mqtt.py new file mode 100644 index 0000000..9295545 --- /dev/null +++ b/oshconnect/csapi4py/comm/mqtt.py @@ -0,0 +1,197 @@ +import paho.mqtt.client as mqtt + + +class MQTTCommClient: + def __init__(self, url, port=1883, username=None, password=None, path='mqtt', client_id="", transport='tcp'): + """ + Wraps a paho mqtt client to provide a simple interface for interacting with the mqtt server that is customized + for this library. + + :param url: url of the mqtt server + :param port: port the mqtt server is communicating over, default is 1883 or whichever port the main node is + using if in websocket mode + :param username: used if node is requiring authentication to access this service + :param password: used if node is requiring authentication to access this service + :param path: used for setting the path when using websockets (usually sensorhub/mqtt by default) + """ + self.__url = url + self.__port = port + self.__path = path + self.__client_id = client_id + self.__transport = transport + + self.__client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=client_id) + + if self.__transport == 'websockets': + self.__client.ws_set_options(path=self.__path) + + if username is not None and password is not None: + self.__client.username_pw_set(username, password) + self.__client.tls_set(tls_version=mqtt.ssl.PROTOCOL_TLSv1_2) + + self.__client.on_connect = self.on_connect + self.__client.on_subscribe = self.on_subscribe + self.__client.on_message = self.on_message + self.__client.on_publish = self.on_publish + self.__client.on_log = self.on_log + self.__client.on_disconnect = self.on_disconnect + + self.__is_connected = False + + @staticmethod + def on_connect(client, userdata, flags, rc, properties): + print(f'Connected with result code: {rc}') + print(f'{properties}') + + @staticmethod + def on_subscribe(client, userdata, mid, granted_qos, properties): + print(f'Subscribed: {mid} {granted_qos}') + + @staticmethod + def on_message(client, userdata, msg): + print(f'{msg.payload.decode("utf-8")}') + + @staticmethod + def on_publish(client, userdata, mid, info, properties): + print(f'Published: {mid}') + + @staticmethod + def on_log(client, userdata, level, buf): + print(f'Log: {buf}') + + @staticmethod + def on_disconnect(client, userdata, dc_flag, rc, properties): + print(f'Client {client} disconnected: {dc_flag} {rc}') + + def connect(self, keepalive=60): + # print(f'Connecting to {self.__url}:{self.__port}') + self.__client.connect(self.__url, self.__port, keepalive=keepalive) + + def subscribe(self, topic, qos=0, msg_callback=None): + """ + Subscribe to a topic, and optionally set a callback for when a message is received on that topic. To actually + retrieve any information you must set a callback. + + :param topic: MQTT topic to subscribe to (example/topic) + :param qos: quality of service, 0, 1, or 2 + :param msg_callback: callback with the form: callback(client, userdata, msg) + :return: + """ + self.__client.subscribe(topic, qos) + if msg_callback is not None: + self.__client.message_callback_add(topic, msg_callback) + + def publish(self, topic, payload=None, qos=0, retain=False): + self.__client.publish(topic, payload, qos, retain=retain) + + def unsubscribe(self, topic): + self.__client.unsubscribe(topic) + + def disconnect(self): + self.__client.disconnect() + + def set_on_connect(self, on_connect): + """ + Set the on_connect callback for the MQTT client. + + :param on_connect: + :return: + """ + self.__client.on_connect = on_connect + + def set_on_disconnect(self, on_disconnect): + """ + Set the on_disconnect callback for the MQTT client. + + :param on_disconnect: + :return: + """ + self.__client.on_disconnect = on_disconnect + + def set_on_subscribe(self, on_subscribe): + """ + Set the on_subscribe callback for the MQTT client. + + :param on_subscribe: + :return: + """ + self.__client.on_subscribe = on_subscribe + + def set_on_unsubscribe(self, on_unsubscribe): + """ + Set the on_unsubscribe callback for the MQTT client. + + :param on_unsubscribe: + :return: + """ + self.__client.on_unsubscribe = on_unsubscribe + + def set_on_publish(self, on_publish): + """ + Set the on_publish callback for the MQTT client. + + :param on_publish: + :return: + """ + self.__client.on_publish = on_publish + + def set_on_message(self, on_message): + """ + Set the on_message callback for the MQTT client. It is recommended to set individual callbacks for each + subscribed topic. + + :param on_message: + :return: + """ + self.__client.on_message = on_message + + def set_on_log(self, on_log): + """ + Set the on_log callback for the MQTT client. + + :param on_log: + :return: + """ + self.__client.on_log = on_log + + def set_on_message_callback(self, sub, on_message_callback): + """ + Set the on_message callback for a specific topic. + :param sub: + :param on_message_callback: + :return: + """ + self.__client.message_callback_add(sub, on_message_callback) + + def start(self): + """ + Start the MQTT client in a separate thread. This is required for the client to be able to receive messages. + + :return: + """ + self.__client.loop_start() + + def stop(self): + """ + Stop the MQTT client.\ + + :return: + """ + self.__client.loop_stop() + + def __toggle_is_connected(self): + self.__is_connected = not self.__is_connected + + def is_connected(self): + return self.__is_connected + + @staticmethod + def publish_single(self, topic, msg): + self.__client.single(topic, msg, 0) + + @staticmethod + def publish_multiple(self, topic, msgs): + self.__client.multiple(msgs, ) + + def tls_set(self): + self.__client.tls_set() diff --git a/oshconnect/csapi4py/con_sys_api.py b/oshconnect/csapi4py/con_sys_api.py new file mode 100644 index 0000000..120ba32 --- /dev/null +++ b/oshconnect/csapi4py/con_sys_api.py @@ -0,0 +1,105 @@ +from typing import Union + +from pydantic import BaseModel, HttpUrl, Field + +from oshconnect.csapi4py.endpoints import Endpoint +from oshconnect.csapi4py.request_wrappers import post_request, put_request, get_request, delete_request + + +class ConnectedSystemAPIRequest(BaseModel): + url: HttpUrl = Field(None) + body: Union[dict, str] = Field(None) + params: dict = Field(None) + request_method: str = Field('GET') + headers: dict = Field(None) + auth: Union[tuple, None] = Field(None) + + def make_request(self): + match self.request_method: + case 'GET': + return get_request(self.url, self.params, self.headers, self.auth) + case 'POST': + print(f'POST request: {self}') + return post_request(self.url, self.body, self.headers, self.auth) + case 'PUT': + print(f'PUT request: {self}') + return put_request(self.url, self.body, self.headers, self.auth) + case 'DELETE': + print(f'DELETE request: {self}') + return delete_request(self.url, self.params, self.headers, self.auth) + case _: + raise ValueError('Invalid request method') + + +class ConnectedSystemsRequestBuilder(BaseModel): + api_request: ConnectedSystemAPIRequest = Field(default_factory=ConnectedSystemAPIRequest) + base_url: HttpUrl = None + endpoint: Endpoint = Field(default_factory=Endpoint) + + def with_api_url(self, url: HttpUrl): + self.api_request.url = url + return self + + def with_server_url(self, server_url: HttpUrl): + self.base_url = server_url + return self + + def build_url_from_base(self): + """ + Builds the full API endpoint URL from the base URL and the endpoint parameters that have been previously + provided. + """ + self.api_request.url = f'{self.base_url}/{self.endpoint.create_endpoint()}' + return self + + def with_api_root(self, api_root: str): + """ + Optional: Set the API root for the request. This is useful if you want to use a different API root than the + default one (api). + :param api_root: + :return: + """ + self.endpoint.api_root = api_root + return self + + def for_resource_type(self, resource_type: str): + self.endpoint.base_resource = resource_type + return self + + def with_resource_id(self, resource_id: str): + self.endpoint.resource_id = resource_id + return self + + def for_sub_resource_type(self, sub_resource_type: str): + self.endpoint.sub_component = sub_resource_type + return self + + def with_secondary_resource_id(self, resource_id: str): + self.endpoint.secondary_resource_id = resource_id + return self + + def with_request_body(self, request_body: str): + self.api_request.body = request_body + return self + + def with_request_method(self, request_method: str): + self.api_request.request_method = request_method + return self + + def with_headers(self, headers: dict = None): + # TODO: ensure headers can default if excluded + self.api_request.headers = headers + return self + + def with_auth(self, uname: str, pword: str): + self.api_request.auth = (uname, pword) + return self + + def build(self): + # convert endpoint to HttpUrl + return self.api_request + + def reset(self): + self.api_request = ConnectedSystemAPIRequest() + self.endpoint = Endpoint() + return self diff --git a/oshconnect/csapi4py/constants.py b/oshconnect/csapi4py/constants.py new file mode 100644 index 0000000..aa91a86 --- /dev/null +++ b/oshconnect/csapi4py/constants.py @@ -0,0 +1,108 @@ +from enum import Enum + + +class APITerms(Enum): + """ + Defines common endpoint terms used in the API + """ + API = 'api' + COLLECTIONS = 'collections' + COMMANDS = 'commands' + COMPONENTS = 'components' + CONFORMANCE = 'conformance' + CONTROL_STREAMS = 'controlstreams' + DATASTREAMS = 'datastreams' + DEPLOYMENTS = 'deployments' + EVENTS = 'events' + FOIS = 'featuresOfInterest' + HISTORY = 'history' + ITEMS = 'items' + OBSERVATIONS = 'observations' + PROCEDURES = 'procedures' + PROPERTIES = 'properties' + SAMPLING_FEATURES = 'samplingFeatures' + SCHEMA = 'schema' + STATUS = 'status' + SYSTEMS = 'systems' + SYSTEM_EVENTS = 'systemEvents' + TASKING = 'controls' + UNDEFINED = '' + + +class SystemTypes(Enum): + """ + Defines the system types + """ + FEATURE = "Feature" + + +class ObservationFormat(Enum): + """ + Defines common observation formats + """ + JSON = "application/om+json" + XML = "application/om+xml" + SWE_XML = "application/swe+xml" + SWE_JSON = "application/swe+json" + SWE_CSV = "application/swe+csv" + SWE_BINARY = "application/swe+binary" + SWE_TEXT = "application/swe+text" + + +class DatastreamResultTypes(Enum): + """ + Defines the datastream result types + """ + MEASURE = "measure" + VECTOR = "vector" + RECORD = "record" + COVERAGE_1D = "coverage1D" + COVERAGE_2D = "coverage2D" + COVERAGE_3D = "coverage3D" + + +class GeometryTypes(Enum): + """ + Defines the geometry types + """ + POINT = "Point" + LINESTRING = "LineString" + POLYGON = "Polygon" + MULTI_POINT = "MultiPoint" + MULTI_LINESTRING = "MultiLineString" + MULTI_POLYGON = "MultiPolygon" + + +class APIResourceTypes(Enum): + """ + Defines the resource types + """ + COLLECTION = "Collection" + COMMAND = "Command" + COMPONENT = "Component" + CONTROL_CHANNEL = "ControlChannel" + DATASTREAM = "Datastream" + DEPLOYMENT = "Deployment" + OBSERVATION = "Observation" + PROCEDURE = "Procedure" + PROPERTY = "Property" + SAMPLING_FEATURE = "SamplingFeature" + SYSTEM = "System" + SYSTEM_EVENT = "SystemEvent" + SYSTEM_HISTORY = "SystemHistory" + + +class EncodingSchema(Enum): + """ + Defines the encoding formats + """ + JSON = "application/json" + XML = "application/xml" + SWE_XML = "application/swe+xml" + SWE_JSON = "application/swe+json" + SWE_CSV = "application/swe+csv" + SWE_BINARY = "application/swe+binary" + SWE_TEXT = "application/swe+text" + GEO_JSON = "application/geo+json" + SML_JSON = "application/sml+json" + OM_JSON = "application/om+json" diff --git a/oshconnect/csapi4py/core/__init__.py b/oshconnect/csapi4py/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oshconnect/csapi4py/core/default_api_helpers.py b/oshconnect/csapi4py/core/default_api_helpers.py new file mode 100644 index 0000000..d034876 --- /dev/null +++ b/oshconnect/csapi4py/core/default_api_helpers.py @@ -0,0 +1,249 @@ +from __future__ import annotations + +from abc import ABC +from dataclasses import dataclass + +from pydantic import BaseModel, Field + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemAPIRequest +from oshconnect.csapi4py.constants import APIResourceTypes, EncodingSchema, APITerms + + +def determine_parent_type(res_type: APIResourceTypes): + match res_type: + case APIResourceTypes.SYSTEM: + return APIResourceTypes.SYSTEM + case APIResourceTypes.COLLECTION: + return None + case APIResourceTypes.CONTROL_CHANNEL: + return APIResourceTypes.SYSTEM + case APIResourceTypes.COMMAND: + return APIResourceTypes.CONTROL_CHANNEL + case APIResourceTypes.DATASTREAM: + return APIResourceTypes.SYSTEM + case APIResourceTypes.OBSERVATION: + return APIResourceTypes.DATASTREAM + case APIResourceTypes.SYSTEM_EVENT: + return APIResourceTypes.SYSTEM + case APIResourceTypes.SAMPLING_FEATURE: + return APIResourceTypes.SYSTEM + case APIResourceTypes.PROCEDURE: + return None + case APIResourceTypes.PROPERTY: + return None + case APIResourceTypes.SYSTEM_HISTORY: + return None + case APIResourceTypes.DEPLOYMENT: + return None + case _: + return None + + +def resource_type_to_endpoint(res_type: APIResourceTypes, parent_type: APIResourceTypes = None): + if parent_type is APIResourceTypes.COLLECTION: + return APITerms.ITEMS.value + + match res_type: + case APIResourceTypes.SYSTEM: + return APITerms.SYSTEMS.value + case APIResourceTypes.COLLECTION: + return APITerms.COLLECTIONS.value + case APIResourceTypes.CONTROL_CHANNEL: + return APITerms.CONTROL_STREAMS.value + case APIResourceTypes.COMMAND: + return APITerms.COMMANDS.value + case APIResourceTypes.DATASTREAM: + return APITerms.DATASTREAMS.value + case APIResourceTypes.OBSERVATION: + return APITerms.OBSERVATIONS.value + case APIResourceTypes.SYSTEM_EVENT: + return APITerms.SYSTEM_EVENTS.value + case APIResourceTypes.SAMPLING_FEATURE: + return APITerms.SAMPLING_FEATURES.value + case APIResourceTypes.PROCEDURE: + return APITerms.PROCEDURES.value + case APIResourceTypes.PROPERTY: + return APITerms.PROPERTIES.value + case APIResourceTypes.SYSTEM_HISTORY: + return APITerms.HISTORY.value + case APIResourceTypes.DEPLOYMENT: + return APITerms.DEPLOYMENTS.value + case _: + raise ValueError('Invalid resource type') + + +@dataclass +class APIHelper(ABC): + server_url: str = None + api_root: str = "api" + username: str = None + password: str = None + user_auth: bool = False + + def create_resource(self, res_type: APIResourceTypes, json_data: any, parent_res_id: str = None, + from_collection: bool = False, url_endpoint: str = None, req_headers: dict = None): + """ + Creates a resource of the given type with the given data, will attempt to create a sub-resource if parent_res_id + is provided. + :param req_headers: + :param res_type: + :param json_data: + :param parent_res_id: + :param from_collection: + :param url_endpoint: If given, will override the default URL construction. Should contain the endpoint past the API root. + :return: + """ + + if url_endpoint is None: + url = self.resource_url_resolver(res_type, None, parent_res_id, from_collection) + else: + url = f'{self.server_url}/{self.api_root}/{url_endpoint}' + api_request = ConnectedSystemAPIRequest(url=url, request_method='POST', auth=self.get_helper_auth(), + body=json_data, headers=req_headers) + return api_request.make_request() + + def retrieve_resource(self, res_type: APIResourceTypes, res_id: str = None, parent_res_id: str = None, + from_collection: bool = False, + collection_id: str = None, url_endpoint: str = None, req_headers: dict = None): + """ + Retrieves a resource or list of resources if no res_id is provided, will attempt to retrieve a sub-resource if + parent_res_id is provided. + :param req_headers: + :param res_type: + :param res_id: + :param parent_res_id: + :param from_collection: + :param collection_id: + :param url_endpoint: If given, will override the default URL construction. Should contain the endpoint past the API root. + :return: + """ + if url_endpoint is None: + url = self.resource_url_resolver(res_type, res_id, parent_res_id, from_collection) + else: + url = f'{self.server_url}/{self.api_root}/{url_endpoint}' + api_request = ConnectedSystemAPIRequest(url=url, request_method='GET', auth=self.get_helper_auth(), + headers=req_headers) + return api_request.make_request() + + def update_resource(self, res_type: APIResourceTypes, res_id: str, json_data: any, parent_res_id: str = None, + from_collection: bool = False, url_endpoint: str = None, req_headers: dict = None): + """ + Updates a resource of the given type by its id, if necessary, will attempt to update a sub-resource if + parent_res_id is provided. + :param req_headers: + :param res_type: + :param res_id: + :param json_data: + :param parent_res_id: + :param from_collection: + :param url_endpoint: If given, will override the default URL construction. Should contain the endpoint past the API root. + :return: + """ + if url_endpoint is None: + url = self.resource_url_resolver(res_type, None, parent_res_id, from_collection) + else: + url = f'{self.server_url}/{self.api_root}/{url_endpoint}' + api_request = ConnectedSystemAPIRequest(url=url, request_method='PUT', auth=self.get_helper_auth(), + body=json_data, headers=req_headers) + return api_request.make_request() + + def delete_resource(self, res_type: APIResourceTypes, res_id: str, parent_res_id: str = None, + from_collection: bool = False, url_endpoint: str = None, req_headers: dict = None): + """ + Deletes a resource of the given type by its id, if necessary, will attempt to delete a sub-resource if + parent_res_id is provided. + :param req_headers: + :param res_type: + :param res_id: + :param parent_res_id: + :param from_collection: + :param url_endpoint: If given, will override the default URL construction. Should contain the endpoint past the API root. + :return: + """ + if url_endpoint is None: + url = self.resource_url_resolver(res_type, None, parent_res_id, from_collection) + else: + url = f'{self.server_url}/{self.api_root}/{url_endpoint}' + api_request = ConnectedSystemAPIRequest(url=url, request_method='DELETE', auth=self.get_helper_auth(), + headers=req_headers) + return api_request.make_request() + + # Helpers + def resource_url_resolver(self, res_type: APIResourceTypes, res_id: str = None, parent_res_id: str = None, + from_collection: bool = False): + """ + Helper to generate a URL endpoint for a given resource type and id by matching the resource type to an + appropriate parent endpoint and inserting the resource ids as necessary. + :param res_type: + :param res_id: + :param parent_res_id: + :param from_collection: + :return: + """ + if res_type is None: + raise ValueError('Resource type must contain a valid APIResourceType') + if res_type is APIResourceTypes.COLLECTION and from_collection: + raise ValueError('Collections are not sub-resources of other collections') + + parent_type = None + if parent_res_id and not from_collection: + parent_type = determine_parent_type(res_type) + elif parent_res_id and from_collection: + parent_type = APIResourceTypes.COLLECTION + + return self.construct_url(parent_type, res_id, res_type, parent_res_id) + + def construct_url(self, parent_type, res_id, res_type, parent_res_id): + """ + Constructs an API endpoint url from the given parameters + :param parent_type: + :param res_id: + :param res_type: + :param parent_res_id: + :return: + """ + # TODO: Test for less common cases to ensure that the URL is being constructed correctly + base_url = f'{self.server_url}/{self.api_root}' + resource_endpoint = resource_type_to_endpoint(res_type, parent_type) + url = f'{base_url}/{resource_endpoint}' + + if parent_type: + parent_endpoint = resource_type_to_endpoint(parent_type) + url = f'{base_url}/{parent_endpoint}/{parent_res_id}/{resource_endpoint}' + + if res_id: + url = f'{url}/{res_id}' + + return url + + def get_helper_auth(self): + if self.user_auth: + return self.username, self.password + return None + + +@dataclass(kw_only=True) +class ResponseParserHelper: + default_object_reps: DefaultObjectRepresentations + + +class DefaultObjectRepresentations(BaseModel): + """ + Intended to be used as a way to determine which formats should be used when serializing and deserializing objects. + Should work in tandem with planned Serializer/Deserializer classes. + """ + # Part 1 + collections: str = Field(EncodingSchema.JSON.value) + deployments: str = Field(EncodingSchema.GEO_JSON.value) + procedures: str = Field(EncodingSchema.GEO_JSON.value) + properties: str = Field(EncodingSchema.SML_JSON.value) + sampling_features: str = Field(EncodingSchema.GEO_JSON.value) + systems: str = Field(EncodingSchema.GEO_JSON.value) + # Part 2 + datastreams: str = Field(EncodingSchema.JSON.value) + observations: str = Field(EncodingSchema.JSON.value) + control_channels: str = Field(EncodingSchema.JSON.value) + commands: str = Field(EncodingSchema.JSON.value) + system_events: str = Field(EncodingSchema.OM_JSON.value) + system_history: str = Field(EncodingSchema.GEO_JSON.value) + # TODO: validate schemas for each resource to amke sure they are allowed per the spec diff --git a/oshconnect/csapi4py/endpoints.py b/oshconnect/csapi4py/endpoints.py new file mode 100644 index 0000000..5943fe7 --- /dev/null +++ b/oshconnect/csapi4py/endpoints.py @@ -0,0 +1,117 @@ +from enum import Enum + +import requests +# import websockets +from pydantic import BaseModel, Field + +from oshconnect.csapi4py.constants import APITerms + + +class Endpoint(BaseModel): + api_root: str = APITerms.API.value + base_resource: APITerms = Field(None) + resource_id: str = Field(None) + sub_component: APITerms = Field(None) + secondary_resource_id: str = Field(None) + + def create_endpoint(self): + # TODO: Handle insertion of "/" in the right places + # Create endpoints bases of api spec + base_res_id = '' if self.base_resource is None else f'/{self.base_resource}' + res_id = '' if self.resource_id is None else f'/{self.resource_id}' + sub_comp = '' if self.sub_component is None else f'/{self.sub_component}' + secondary_res_id = '' if self.secondary_resource_id is None else f'/{self.secondary_resource_id}' + + return f'{self.api_root}{base_res_id}{res_id}{sub_comp}{secondary_res_id}' + + +class SystemQueryParams(Enum): + Keywords = 'q' + """ + A comma-separated list of keywords to search for in the system name, description, and definition. + """ + BBOX = 'bbox' + """ + BBOX to fileter resources based on their location + """ + LOCATION = 'location' + """ + WKT geometry to filter resources based on their location or geometry + """ + VALID_TIME = 'validTime' + """ + ISO 8601 time interval to filter resources based on their valid time. When omitted, the implicit time is "now" + except for "history" collection where no filtering is applied. + """ + PARENT = 'parent' + """ + Comma-separated list of parent system IDs or "*" to included nested resources at any level + """ + SELECT = 'select' + """ + Comma-separated list of properties to include or exclude from results (use "!" prefix to exclude) + """ + FORMAT = 'format' + """ + Mime type of the response format. + """ + LIMIT = 'limit' + """ + Maximum number of resources to return per page (max 1000) + """ + OFFSET = 'offset' + """ + Token specifying the page to return (usually the token provided in the previous call) + """ + + @classmethod + def list(cls): + return list(map(lambda c: c.value, cls)) + + +def handle_request(url, params=None, content_json=None, method='get', response_handler=None): + """ + Handles a request to the API + :param url: The URL to make the request to + :param params: The parameters to send with the request + :param content_json: The JSON to send with the request + :param method: The method to use for the request + :param response_handler: + :return: The response from the API + """ + + r = None + + if method == 'get': + r = requests.get(url, params=params) + elif method == 'post': + r = requests.post(url, params=params, json=content_json, headers={'Content-Type': 'application/json'}) + elif method == 'put': + r = requests.put(url, params=params, json=content_json) + elif method == 'delete': + r = requests.delete(url, params=params) + else: + raise ValueError(f'Invalid method: {method}') + + if response_handler is not None: + return response_handler(r) + else: + return r + + +# async def handle_ws(url, params=None, json_data=None, method='get', response_handler=None): +# """ +# Handles a request to the API. Functionality is limited to receiving observations for now, but will be improved in +# future versions. +# :param url: The URL to make the request to +# :param params: The parameters to send with the request +# :param json_data: The JSON to send with the request +# :param method: The method to use for the request +# :param response_handler: callback function to handle the response msg +# :return: The response from the API +# """ +# +# async with websockets.connect(url) as ws: +# while True: +# msg = await ws.recv() +# response_handler(msg) diff --git a/oshconnect/csapi4py/part_1/__init__.py b/oshconnect/csapi4py/part_1/__init__.py new file mode 100644 index 0000000..dd72804 --- /dev/null +++ b/oshconnect/csapi4py/part_1/__init__.py @@ -0,0 +1,7 @@ +# import capabilities +# import collections_ep +# import deployments +# import procedures +# import properties +# import sampling_features +# import systems diff --git a/oshconnect/csapi4py/part_1/capabilities.py b/oshconnect/csapi4py/part_1/capabilities.py new file mode 100644 index 0000000..e094aa5 --- /dev/null +++ b/oshconnect/csapi4py/part_1/capabilities.py @@ -0,0 +1,31 @@ +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def get_landing_page(server_addr: HttpUrl, api_root: str = APITerms.API.value): + """ + Returns the landing page of the API + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .build_url_from_base() + .build()) + return api_request + + +def get_conformance_info(server_addr: HttpUrl, api_root: str = APITerms.API.value): + """ + Returns the conformance information of the API + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONFORMANCE.value) + .build_url_from_base() + .build()) + return api_request diff --git a/oshconnect/csapi4py/part_1/collections_ep.py b/oshconnect/csapi4py/part_1/collections_ep.py new file mode 100644 index 0000000..0c8dbfc --- /dev/null +++ b/oshconnect/csapi4py/part_1/collections_ep.py @@ -0,0 +1,67 @@ +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_all_collections(server_addr: HttpUrl, api_root: str = APITerms.API.value): + """ + List all collections + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .build_url_from_base() + .build()) + return api_request + + +def retrieve_collection_metadata(server_addr: HttpUrl, collection_id: str, api_root: str = APITerms.API.value): + """ + Retrieve a collection by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .with_resource_id(collection_id) + .build_url_from_base() + .build()) + return api_request + + +def list_all_items_in_collection(server_addr: HttpUrl, collection_id: str, api_root: str = APITerms.API.value): + """ + Lists all systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .with_resource_id(collection_id) + .for_sub_resource_type(APITerms.ITEMS.value) + .build_url_from_base() + .build()) + return api_request + + +def retrieve_collection_item_by_id(server_addr: HttpUrl, collection_id: str, item_id: str, + api_root: str = APITerms.API.value): + """ + Retrieves a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .with_resource_id(collection_id) + .for_sub_resource_type(APITerms.ITEMS.value) + .with_resource_id(item_id) + .build_url_from_base() + .build()) + return api_request diff --git a/oshconnect/csapi4py/part_1/deployments.py b/oshconnect/csapi4py/part_1/deployments.py new file mode 100644 index 0000000..0f8fe60 --- /dev/null +++ b/oshconnect/csapi4py/part_1/deployments.py @@ -0,0 +1,217 @@ +from typing import Union + +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_all_deployments(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all deployments in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def create_new_deployments(server_addr: HttpUrl, request_body: Union[str, dict], api_root: str = APITerms.API.value, + headers: dict = None): + """ + Create a new deployment as defined by the request body + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_deployment_by_id(server_addr: HttpUrl, deployment_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Retrieve a deployment by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_deployment_by_id(server_addr: HttpUrl, deployment_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers: dict = None): + """ + Update a deployment by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_deployment_by_id(server_addr: HttpUrl, deployment_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Delete a deployment by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request + + +def list_deployed_systems(server_addr: HttpUrl, deployment_id, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Lists all deployed systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .for_sub_resource_type(APITerms.SYSTEMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def add_systems_to_deployment(server_addr: HttpUrl, deployment_id: str, uri_list: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .for_sub_resource_type(APITerms.SYSTEMS.value) + .with_request_body(uri_list) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_deployed_system_by_id(server_addr: HttpUrl, deployment_id: str, system_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Retrieves a system by its id + :return: + """ + + # TODO: Add a way to have a secondary resource ID for certain endpoints + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .for_sub_resource_type(APITerms.SYSTEMS.value) + .with_secondary_resource_id(system_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_deployed_system_by_id(server_addr: HttpUrl, deployment_id: str, system_id: str, request_body: dict, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Update a system by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .for_sub_resource_type(APITerms.SYSTEMS.value) + .with_secondary_resource_id(system_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + + return api_request + + +def delete_deployed_system_by_id(server_addr: HttpUrl, deployment_id: str, system_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Delete a system by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .for_sub_resource_type(APITerms.SYSTEMS.value) + .with_secondary_resource_id(system_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() + + +def list_deployments_of_specific_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Lists all deployments of a specific system in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.DEPLOYMENTS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() diff --git a/oshconnect/csapi4py/part_1/procedures.py b/oshconnect/csapi4py/part_1/procedures.py new file mode 100644 index 0000000..41e58a5 --- /dev/null +++ b/oshconnect/csapi4py/part_1/procedures.py @@ -0,0 +1,97 @@ +from typing import Union + +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_all_procedures(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all procedures in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROCEDURES.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def create_new_procedures(server_addr: HttpUrl, request_body: Union[str, dict], api_root: str = APITerms.API.value, + headers: dict = None): + """ + Create a new procedure as defined by the request body + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROCEDURES.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + print(api_request) + return api_request.make_request() + + +def retrieve_procedure_by_id(server_addr: HttpUrl, procedure_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Retrieve a procedure by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROCEDURES.value) + .with_resource_id(procedure_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_procedure_by_id(server_addr: HttpUrl, procedure_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers: dict = None): + """ + Update a procedure by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROCEDURES.value) + .with_resource_id(procedure_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_procedure_by_id(server_addr: HttpUrl, procedure_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Delete a procedure by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROCEDURES.value) + .with_resource_id(procedure_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() diff --git a/oshconnect/csapi4py/part_1/properties.py b/oshconnect/csapi4py/part_1/properties.py new file mode 100644 index 0000000..8347424 --- /dev/null +++ b/oshconnect/csapi4py/part_1/properties.py @@ -0,0 +1,80 @@ +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_all_properties(server_addr: HttpUrl, api_root: str = APITerms.API.value): + """ + List all properties + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROPERTIES.value) + .build_url_from_base() + .build()) + return api_request + + +def create_new_properties(server_addr: HttpUrl, request_body: dict, api_root: str = APITerms.API.value): + """ + Create a new property as defined by the request body + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROPERTIES.value) + .with_request_body(request_body) + .build_url_from_base() + .build()) + return api_request + + +def retrieve_property_by_id(server_addr: HttpUrl, property_id: str, api_root: str = APITerms.API.value): + """ + Retrieve a property by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROPERTIES.value) + .with_resource_id(property_id) + .build_url_from_base() + .build()) + return api_request + + +def update_property_by_id(server_addr: HttpUrl, property_id: str, request_body: dict, + api_root: str = APITerms.API.value): + """ + Update a property by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROPERTIES.value) + .with_resource_id(property_id) + .with_request_body(request_body) + .build_url_from_base() + .build()) + return api_request + + +def delete_property_by_id(server_addr: HttpUrl, property_id: str, api_root: str = APITerms.API.value): + """ + Delete a property by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROPERTIES.value) + .with_resource_id(property_id) + .build_url_from_base() + .build()) + return api_request diff --git a/oshconnect/csapi4py/part_1/sampling_features.py b/oshconnect/csapi4py/part_1/sampling_features.py new file mode 100644 index 0000000..5ae3d5c --- /dev/null +++ b/oshconnect/csapi4py/part_1/sampling_features.py @@ -0,0 +1,116 @@ +from typing import Union + +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_all_sampling_features(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers=None): + """ + Lists all sampling features in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SAMPLING_FEATURES.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def list_sampling_features_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all sampling features of a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.SAMPLING_FEATURES.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def create_new_sampling_features(server_addr: HttpUrl, system_id: str, request_body: Union[dict, str], + api_root: str = APITerms.API.value, headers=None): + """ + Create a new sampling feature as defined by the request body + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.SAMPLING_FEATURES.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_sampling_feature_by_id(server_addr: HttpUrl, sampling_feature_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Retrieve a sampling feature by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SAMPLING_FEATURES.value) + .with_resource_id(sampling_feature_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_sampling_feature_by_id(server_addr: HttpUrl, sampling_feature_id: str, request_body: Union[dict, str], + api_root: str = APITerms.API.value, headers=None): + """ + Update a sampling feature by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SAMPLING_FEATURES.value) + .with_resource_id(sampling_feature_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_sampling_feature_by_id(server_addr: HttpUrl, sampling_feature_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Delete a sampling feature by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SAMPLING_FEATURES.value) + .with_resource_id(sampling_feature_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() diff --git a/oshconnect/csapi4py/part_1/systems.py b/oshconnect/csapi4py/part_1/systems.py new file mode 100644 index 0000000..d746b24 --- /dev/null +++ b/oshconnect/csapi4py/part_1/systems.py @@ -0,0 +1,213 @@ +from typing import Union + +import requests +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.request_wrappers import post_request + + +def list_all_systems(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def create_new_systems(server_addr: HttpUrl, request_body: Union[str, dict], api_root: str = APITerms.API.value, + uname: str = None, + pword: str = None, headers: dict = None): + """ + Create a new system as defined by the request body + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_auth(uname, pword) + .with_headers(headers) + .with_request_method('POST') + .build()) + print(api_request.url) + # resp = requests.post(api_request.url, data=api_request.body, headers=api_request.headers, auth=(uname, pword)) + resp = post_request(api_request.url, api_request.body, api_request.headers, api_request.auth) + print(f'Create new system response: {resp}') + return resp + + +def list_all_systems_in_collection(server_addr: HttpUrl, collection_id: str, api_root: str = APITerms.API.value): + """ + NOTE: function may not be able to fully represent a request to the API at this time, as the test server lacks a few + elements. + Lists all systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .with_resource_id(collection_id) + # .for_sub_resource_type(APITerms.ITEMS.value) + .build_url_from_base() + .build()) + print(api_request.url) + resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() + + +def add_systems_to_collection(server_addr: HttpUrl, collection_id: str, uri_list: str, + api_root: str = APITerms.API.value): + """ + Lists all systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .with_resource_id(collection_id) + .for_sub_resource_type(APITerms.ITEMS.value) + .with_request_body(uri_list) + .build_url_from_base() + .build()) + resp = requests.post(api_request.url, json=api_request.body, headers=api_request.headers) + return resp.json() + + +def retrieve_system_by_id(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): + """ + Retrieves a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .build_url_from_base() + .build()) + resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() + + +def update_system_description(server_addr: HttpUrl, system_id: str, request_body: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Updates a system's description by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .build()) + resp = requests.put(api_request.url, data=request_body, headers=api_request.headers) + return resp + + +def delete_system_by_id(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, headers: dict = None): + """ + Deletes a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() + + +def list_system_components(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): + """ + Lists all components of a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.COMPONENTS.value) + .build_url_from_base() + .build()) + print(api_request.url) + resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() + + +def add_system_components(server_addr: HttpUrl, system_id: str, request_body: dict, + api_root: str = APITerms.API.value): + """ + Adds components to a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.COMPONENTS.value) + .with_request_body(request_body) + .build_url_from_base() + .build()) + resp = requests.post(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() + + +def list_deployments_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): + """ + Lists all deployments of a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.DEPLOYMENTS.value) + .build_url_from_base() + + .build()) + resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() + + +def list_sampling_features_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): + """ + Lists all sampling features of a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.SAMPLING_FEATURES.value) + .build_url_from_base() + .build()) + print(api_request.url) + resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() diff --git a/oshconnect/csapi4py/part_2/__init__.py b/oshconnect/csapi4py/part_2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oshconnect/csapi4py/part_2/commands.py b/oshconnect/csapi4py/part_2/commands.py new file mode 100644 index 0000000..ac26dbf --- /dev/null +++ b/oshconnect/csapi4py/part_2/commands.py @@ -0,0 +1,227 @@ +from typing import Union + +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_all_commands(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all commands + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def list_commands_of_control_channel(server_addr: HttpUrl, control_channel_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all commands of a control channel + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_channel_id) + .for_sub_resource_type(APITerms.COMMANDS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def send_commands_to_specific_control_stream(server_addr: HttpUrl, control_stream_id: str, + request_body: Union[dict, str], + api_root: str = APITerms.API.value, headers=None): + """ + Sends a command to a control stream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + .for_sub_resource_type(APITerms.COMMANDS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + + return api_request.make_request() + + +def retrieve_command_by_id(server_addr: HttpUrl, command_id: str, api_root: str = APITerms.API.value, headers=None): + """ + Retrieves a command by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def update_command_description(server_addr: HttpUrl, command_id: str, request_body: Union[dict, str], + api_root: str = APITerms.API.value, headers=None): + """ + Updates a command's description by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + + return api_request.make_request() + + +def delete_command_by_id(server_addr: HttpUrl, command_id: str, api_root: str = APITerms.API.value, headers=None): + """ + Deletes a command by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + + return api_request.make_request() + + +def list_command_status_reports(server_addr: HttpUrl, command_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all status reports of a command by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .for_sub_resource_type(APITerms.STATUS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def add_command_status_reports(server_addr: HttpUrl, command_id: str, request_body: Union[dict, str], + api_root: str = APITerms.API.value, headers=None): + """ + Adds a status report to a command by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .for_sub_resource_type(APITerms.STATUS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + + return api_request.make_request() + + +def retrieve_command_status_report_by_id(server_addr: HttpUrl, command_id: str, status_report_id: str, + api_root: str = APITerms.API.value, headers=None): + """ + Retrieves a status report of a command by its id and status report id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .for_sub_resource_type(APITerms.STATUS.value) + .with_secondary_resource_id(status_report_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def update_command_status_report_by_id(server_addr: HttpUrl, command_id: str, status_report_id: str, + request_body: Union[dict, str], api_root: str = APITerms.API.value, + headers=None): + """ + Updates a status report of a command by its id and status report id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .for_sub_resource_type(APITerms.STATUS.value) + .with_secondary_resource_id(status_report_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + + return api_request.make_request() + + +def delete_command_status_report_by_id(server_addr: HttpUrl, command_id: str, status_report_id: str, + api_root: str = APITerms.API.value, headers=None): + """ + Deletes a status report of a command by its id and status report id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .for_sub_resource_type(APITerms.STATUS.value) + .with_secondary_resource_id(status_report_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + + return api_request.make_request() diff --git a/oshconnect/csapi4py/part_2/control_channels.py b/oshconnect/csapi4py/part_2/control_channels.py new file mode 100644 index 0000000..3b4d995 --- /dev/null +++ b/oshconnect/csapi4py/part_2/control_channels.py @@ -0,0 +1,159 @@ +from typing import Union + +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_all_control_streams(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all control streams + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def list_control_streams_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all control streams of a system + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.CONTROL_STREAMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def add_control_streams_to_system(server_addr: HttpUrl, system_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers=None): + """ + Adds a control stream to a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.CONTROL_STREAMS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_control_stream_description_by_id(server_addr: HttpUrl, control_stream_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Retrieves a control stream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def update_control_stream_description_by_id(server_addr: HttpUrl, control_stream_id: str, + request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers: dict = None): + """ + Updates a control stream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_control_stream_by_id(server_addr: HttpUrl, control_stream_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Deletes a control stream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + + return api_request.make_request() + + +def retrieve_control_stream_schema_by_id(server_addr: HttpUrl, control_stream_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Retrieves a control stream schema by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + .for_sub_resource_type(APITerms.SCHEMA.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def update_control_stream_schema_by_id(server_addr: HttpUrl, control_stream_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers: dict = None): + """ + Updates a control stream schema by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + # .for_sub_resource_type(APITerms.SCHEMA.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() diff --git a/oshconnect/csapi4py/part_2/datastreams.py b/oshconnect/csapi4py/part_2/datastreams.py new file mode 100644 index 0000000..3a21b3a --- /dev/null +++ b/oshconnect/csapi4py/part_2/datastreams.py @@ -0,0 +1,155 @@ +from typing import Union + +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_all_datastreams(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all datastreams + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def list_all_datastreams_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all datastreams of a system + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.DATASTREAMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def add_datastreams_to_system(server_addr: HttpUrl, system_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers=None): + """ + Adds a datastream to a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.DATASTREAMS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_datastream_by_id(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Retrieves a datastream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_datastream_by_id(server_addr: HttpUrl, datastream_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers=None): + """ + Updates a datastream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_datastream_by_id(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, headers=None): + """ + Deletes a datastream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() + + +def retrieve_datastream_schema(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Retrieves a datastream schema by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .for_sub_resource_type(APITerms.SCHEMA.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_datastream_schema(server_addr: HttpUrl, datastream_id: str, request_body: dict, + api_root: str = APITerms.API.value, headers=None): + """ + Updates a datastream schema by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .for_resource_type(APITerms.SCHEMA.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() diff --git a/oshconnect/csapi4py/part_2/observations.py b/oshconnect/csapi4py/part_2/observations.py new file mode 100644 index 0000000..788407b --- /dev/null +++ b/oshconnect/csapi4py/part_2/observations.py @@ -0,0 +1,122 @@ +from typing import Union + +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_all_observations(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers=None): + """ + Lists all observations + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.OBSERVATIONS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def list_observations_from_datastream(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all observations of a datastream + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .for_sub_resource_type(APITerms.OBSERVATIONS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def add_observations_to_datastream(server_addr: HttpUrl, datastream_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers=None): + """ + Adds an observation to a datastream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .for_sub_resource_type(APITerms.OBSERVATIONS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + + return api_request.make_request() + + +def retrieve_observation_by_id(server_addr: HttpUrl, observation_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Retrieves an observation by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.OBSERVATIONS.value) + .with_resource_id(observation_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def update_observation_by_id(server_addr: HttpUrl, observation_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers=None): + """ + Updates an observation by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.OBSERVATIONS.value) + .with_resource_id(observation_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + + return api_request.make_request() + + +def delete_observation_by_id(server_addr: HttpUrl, observation_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Deletes an observation by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.OBSERVATIONS.value) + .with_resource_id(observation_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + + return api_request.make_request() diff --git a/oshconnect/csapi4py/part_2/system_events.py b/oshconnect/csapi4py/part_2/system_events.py new file mode 100644 index 0000000..afbbb47 --- /dev/null +++ b/oshconnect/csapi4py/part_2/system_events.py @@ -0,0 +1,122 @@ +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_system_events(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all system events + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEM_EVENTS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def list_events_by_system_id(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Lists all events of a system + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.EVENTS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def add_new_system_events(server_addr: HttpUrl, system_id: str, request_body: dict, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Adds a new system event to a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.EVENTS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_system_event_by_id(server_addr: HttpUrl, system_id: str, event_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Retrieves a system event by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.EVENTS.value) + .with_secondary_resource_id(event_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_system_event_by_id(server_addr: HttpUrl, system_id: str, event_id: str, request_body: dict, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Updates a system event by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.EVENTS.value) + + .with_secondary_resource_id(event_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_system_event_by_id(server_addr: HttpUrl, system_id: str, event_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Deletes a system event by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.EVENTS.value) + .with_secondary_resource_id(event_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + + return api_request.make_request() diff --git a/oshconnect/csapi4py/part_2/system_history.py b/oshconnect/csapi4py/part_2/system_history.py new file mode 100644 index 0000000..dc73c5c --- /dev/null +++ b/oshconnect/csapi4py/part_2/system_history.py @@ -0,0 +1,83 @@ +from pydantic import HttpUrl + +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms + + +def list_system_history(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all history versions of a system + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_resource_type(APITerms.HISTORY.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def retrieve_system_historical_description_by_id(server_addr: HttpUrl, system_id: str, history_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Retrieves a historical system description by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_resource_type(APITerms.HISTORY.value) + .with_secondary_resource_id(history_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_system_historical_description(server_addr: HttpUrl, system_id: str, history_rev_id: str, request_body: dict, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Updates a historical system description by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_resource_type(APITerms.HISTORY.value) + .with_secondary_resource_id(history_rev_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_system_historical_description_by_id(server_addr: HttpUrl, system_id: str, history_rev_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Deletes a historical system description by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_resource_type(APITerms.HISTORY.value) + .with_secondary_resource_id(history_rev_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() diff --git a/oshconnect/csapi4py/querymodel.py b/oshconnect/csapi4py/querymodel.py new file mode 100644 index 0000000..f483b06 --- /dev/null +++ b/oshconnect/csapi4py/querymodel.py @@ -0,0 +1,25 @@ +from datetime import datetime +from typing import Union, Optional, List + +from pydantic import BaseModel, StrictStr, Field, field_validator + + +class QueryModel(BaseModel): + id: list = None + bbox: list = None + date_time: Union[StrictStr, datetime] = Field(None, alias='datetime') + geom: dict = None + q: list = Optional[List[str]] + parent: list = None + procedure: list = None + foi: list = None + observed_property: list = Field(None, serialization_alias='observedProperty') + controlled_property: list = Field(None, serialization_alias='controlledProperty') + recursive: bool = False + limit: int = Field(10, ge=1, le=10000) + + @field_validator('q') + def validate_q(cls, v): + if v is not None: + return v.split(',') + return v diff --git a/oshconnect/csapi4py/request_bodies.py b/oshconnect/csapi4py/request_bodies.py new file mode 100644 index 0000000..c9bbb93 --- /dev/null +++ b/oshconnect/csapi4py/request_bodies.py @@ -0,0 +1,99 @@ +from typing import Union + +from pydantic import BaseModel, HttpUrl, Field, model_serializer, RootModel, SerializeAsAny + +from oshconnect.csapi4py.constants import DatastreamResultTypes +from oshconnect.datamodels.datastreams import DatastreamSchema +from oshconnect.datamodels.geometry import Geometry +from oshconnect.csapi4py.sensor_ml.sml import TypeOf + + +# TODO: Consider some sort of Abstract Base Class for all valid request bodies to inherit from to reduce the complexity +# of the final request body. + +class GeoJSONBody(BaseModel): + type: str + id: str + properties: dict = None + geometry: Geometry = None + bbox: list = None + links: list = None + + +class SmlJSONBody(BaseModel): + object_type: str = Field(None, serialization_alias='type') + id: str = Field(None) + description: str = Field(None) + unique_id: str = Field(..., serialization_alias='uniqueId') + label: str = Field(...) + lang: str = None + keywords: list = None + identifiers: list = None + classifiers: list = None + valid_time: list = Field(None, serialization_alias='validTime') + security_constraints: list = Field(None, serialization_alias='securityConstraints') + legal_constraints: list = Field(None, serialization_alias='legalConstraints') + characteristics: list = None + capabilities: list = None + contacts: list = None + documents: list = None + history: list = None + definition: HttpUrl = None + type_of: TypeOf = Field(None, serialization_alias='typeOf') + configuration: HttpUrl = None + features_of_interest: list = Field(None, serialization_alias='featuresOfInterest') + inputs: list = None + outputs: list = None + parameters: list = None + modes: list = None + method: str = None + position: list = None + links: list = Field(None) + + +class OMJSONBody(BaseModel): + datastream_id: str = Field(None, alias="datastream@id") + foi_id: str = Field(None, alias="foi@id") + phenomenon_time: str = Field(None, alias="phenomenonTime") + result_time: str = Field(None, alias="resultTime") + parameters: list = Field(None) + result: dict = Field(None) + result_links: list = Field(None, alias="result@links") + + +class DatastreamBodyJSON(BaseModel): + """ + NOTES: though the spec does not require that outputName, and schema be present, they are required for the + implementation of the API present on OSH + """ + id: str = Field(None) + name: str = Field(...) + description: str = Field(None) + deployment: HttpUrl = Field(None, serialization_alias='deployment@link') + ultimate_feature_of_interest: HttpUrl = Field(None, serialization_alias='ultimateFeatureOfInterest@link') + sampling_feature: HttpUrl = Field(None, serialization_alias='samplingFeature@link') + valid_time: list = Field(None, serialization_alias='validTime') + output_name: str = Field(..., serialization_alias='outputName') + phenomenon_time_interval: str = Field(None, serialization_alias='phenomenonTimeInterval') + result_time_interval: str = Field(None, serialization_alias='resultTimeInterval') + result_type: DatastreamResultTypes = Field(None, serialization_alias='resultType') + links: list = Field(None) + datastream_schema: SerializeAsAny[DatastreamSchema] = Field(..., serialization_alias='schema') + + +class RequestBody(BaseModel): + """ + Wrapper class to support different request json structures + """ + json_structure: Union[GeoJSONBody, SmlJSONBody, OMJSONBody, DatastreamSchema] = Field(..., + serialization_alias='json') + test_extra: str = Field("Hello, I am test", serialization_alias='testExtra') + + @model_serializer + def ser_model(self): + print("Serializing model...") + return self.json_structure + + +class RequestBodyList(RootModel): + root: list[Union[GeoJSONBody, SmlJSONBody, OMJSONBody, DatastreamSchema]] = Field(...) diff --git a/oshconnect/csapi4py/request_wrappers.py b/oshconnect/csapi4py/request_wrappers.py new file mode 100644 index 0000000..8f1f837 --- /dev/null +++ b/oshconnect/csapi4py/request_wrappers.py @@ -0,0 +1,58 @@ +from typing import Union + +import requests +from pydantic import HttpUrl + + +def get_request(url: HttpUrl, params: dict = None, headers: dict = None, auth: tuple = None): + """ + Sends a GET request to the provided URL with the given parameters and headers + :param url: + :param params: + :param headers: + :param auth: + :return: the response of the request + """ + return requests.get(url, params=params, headers=headers, auth=auth) + + +def post_request(url: HttpUrl, body: Union[str, dict] = None, headers: dict = None, auth: tuple = None): + """ + Sends a POST request to the provided URL with the given content and headers + :param url: + :param content_json: + :param headers: + :param auth: + :return: the response of the request + """ + if isinstance(body, str): + return requests.post(url, data=body, headers=headers, auth=auth) + else: + return requests.post(url, json=body, headers=headers, auth=auth) + + +def put_request(url: HttpUrl, body: Union[str, dict] = None, headers: dict = None, auth: tuple = None): + """ + Sends a PUT request to the provided URL with the given content and headers + :param url: + :param content_json: + :param headers: + :param auth: + :return: the response of the request + """ + if isinstance(body, str): + return requests.put(url, data=body, headers=headers, auth=auth) + else: + return requests.put(url, json=body, headers=headers, auth=auth) + + +def delete_request(url: HttpUrl, params: dict = None, headers: dict = None, auth: tuple = None): + """ + Sends a DELETE request to the provided URL with the given parameters and headers + :param url: + :param params: + :param headers: + :param auth: + :return: the response of the request + """ + return requests.delete(url, params=params, headers=headers, auth=auth) diff --git a/oshconnect/csapi4py/sensor_ml/__init__.py b/oshconnect/csapi4py/sensor_ml/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oshconnect/csapi4py/sensor_ml/sml.py b/oshconnect/csapi4py/sensor_ml/sml.py new file mode 100644 index 0000000..8c704b0 --- /dev/null +++ b/oshconnect/csapi4py/sensor_ml/sml.py @@ -0,0 +1,51 @@ +from pydantic import BaseModel, HttpUrl, Field + + +class TypeOf(BaseModel): + """ + TypeOf is a resolvable reference to some other general process (that can be any type inheriting from AbstractProcess) + :param href: The URL of the referenced process + :param relationship: The relationship of the referenced process to the current process + :param media_type: The media type of the referenced process + :param href_lang: The language of the referenced process + :param title: The title of the referenced process + :param uid: The unique identifier of the referenced process + :param target_resource: The target resource of the referenced process + :param interface: The interface of the referenced process + """ + href: HttpUrl + relationship: str = Field(..., serialization_alias='rel') + media_type: str = Field(None, serialization_alias='type') + href_lang: str = Field(None, serialization_alias='hreflang') + title: str = Field(None) + uid: str = Field(None) + target_resource: str = Field(None, serialization_alias='rt') + interface: str = Field(None, serialization_alias='if') + + +class SMLAbstractProcess(BaseModel): + description: str = None + unique_id: str = Field(None, serialization_alias='uniqueID') + label: str = None + lang: str = None + keywords: list = None + identifiers: list = None + classifiers: list = None + valid_time: list = Field(None, serialization_alias='validTime') + security_constraints: list = Field(None, serialization_alias='securityConstraints') + legal_constraints: list = Field(None, serialization_alias='legalConstraints') + characteristics: list = None + capabilities: list = None + contacts: list = None + documents: list = None + history: list = None + definition: HttpUrl = None + type_of: TypeOf = Field(None, serialization_alias='typeOf') + configuration: HttpUrl = None + features_of_interest: list = Field(None, serialization_alias='featuresOfInterest') + inputs: list = None + outputs: list = None + parameters: list = None + modes: list = None + method: str = None + position: list = None diff --git a/oshconnect/csapi4py/utilities/__init__.py b/oshconnect/csapi4py/utilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oshconnect/csapi4py/utilities/model_utils.py b/oshconnect/csapi4py/utilities/model_utils.py new file mode 100644 index 0000000..87cac03 --- /dev/null +++ b/oshconnect/csapi4py/utilities/model_utils.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel + + +def serialize_model_list(model_list: list[BaseModel]): + """ + Serializes a list of pydantic models + :param model_list: + :return: a valid json string + """ + return '[' + ','.join([model.model_dump_json(exclude_none=True, by_alias=True) for model in model_list]) + ']' diff --git a/oshconnect/datamodels/__init__.py b/oshconnect/datamodels/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oshconnect/datamodels/api_utils.py b/oshconnect/datamodels/api_utils.py new file mode 100644 index 0000000..9ed710f --- /dev/null +++ b/oshconnect/datamodels/api_utils.py @@ -0,0 +1,39 @@ +from __future__ import annotations +from pydantic import BaseModel, Field, HttpUrl, field_validator + + +class Link(BaseModel): + href: HttpUrl = Field(...) + rel: str = Field(None) + type: str = Field(None) + hreflang: str = Field(None) + title: str = Field(None) + uid: URI = Field(None) + rt: URI = Field(None) + interface: URI = Field(None, serialization_alias='if') + + +class UCUMCode(BaseModel): + code: str = Field(...) + label: str = Field(None) + + @field_validator('code', 'label') + @classmethod + def validate_string_fields(cls, v): + if isinstance(v, str) and len(v) > 0: + return v + else: + raise ValueError('code and label must be strings of length > 0') + + +class URI(BaseModel): + href: HttpUrl = Field(...) + label: str = Field(None) + + @field_validator('label') + @classmethod + def validate_label(cls, v): + if isinstance(v, str) and len(v) > 0: + return v + else: + raise ValueError('label must be strings of length > 0') diff --git a/oshconnect/datamodels/commands.py b/oshconnect/datamodels/commands.py new file mode 100644 index 0000000..999b480 --- /dev/null +++ b/oshconnect/datamodels/commands.py @@ -0,0 +1,14 @@ +from datetime import datetime +from typing import Union + +from pydantic import BaseModel, Field + + +class CommandJSON(BaseModel): + """ + A class to represent a command in JSON format + """ + control_id: str = Field(None, serialization_alias="control@id") + issue_time: Union[str, float] = Field(datetime.now().isoformat(), serialization_alias="issueTime") + sender: str = Field(None) + params: Union[dict, list, int, float, str] = Field(None) diff --git a/oshconnect/datamodels/control_streams.py b/oshconnect/datamodels/control_streams.py new file mode 100644 index 0000000..8ee8eb5 --- /dev/null +++ b/oshconnect/datamodels/control_streams.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from typing import Union + +from pydantic import BaseModel, Field, SerializeAsAny + +from oshconnect.datamodels.encoding import Encoding +from oshconnect.datamodels.swe_components import AnyComponentSchema + + +class ControlStreamJSONSchema(BaseModel): + """ + A class to represent the schema of a control stream + """ + id: str = Field(None) + name: str = Field(...) + description: str = Field(None) + deployment_link: str = Field(None, serialization_alias='deployment@link') + ultimate_feature_of_interest_link: str = Field(None, serialization_alias='ultimateFeatureOfInterest@link') + sampling_feature_link: str = Field(None, alias='samplingFeature@link') + valid_time: list = Field(None, serialization_alias='validTime') + input_name: str = Field(None, serialization_alias='inputName') + links: list = Field(None) + control_stream_schema: SerializeAsAny[Union[SWEControlChannelSchema, JSONControlChannelSchema]] = Field(..., + serialization_alias='schema') + + +class SWEControlChannelSchema(BaseModel): + """ + A class to represent the schema of a control channel + """ + command_format: str = Field("application/swe+json", serialization_alias='commandFormat') + encoding: SerializeAsAny[Encoding] = Field(...) + record_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='recordSchema') + + +class JSONControlChannelSchema(BaseModel): + command_format: str = Field("application/cmd+json", serialization_alias='commandFormat') + params_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='paramsSchema') diff --git a/oshconnect/datamodels/datastreams.py b/oshconnect/datamodels/datastreams.py new file mode 100644 index 0000000..11d3986 --- /dev/null +++ b/oshconnect/datamodels/datastreams.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel, Field, field_validator, SerializeAsAny + +from oshconnect.csapi4py.constants import ObservationFormat +from oshconnect.datamodels.encoding import Encoding +from oshconnect.datamodels.swe_components import AnyComponentSchema + + +class DatastreamSchema(BaseModel): + """ + A class to represent the schema of a datastream + """ + obs_format: str = Field(..., serialization_alias='obsFormat') + + +class SWEDatastreamSchema(DatastreamSchema): + encoding: SerializeAsAny[Encoding] = Field(...) + record_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='recordSchema') + + @field_validator('obs_format') + @classmethod + def check_check_obs_format(cls, v): + if v not in [ObservationFormat.SWE_JSON.value, ObservationFormat.SWE_CSV.value, + ObservationFormat.SWE_TEXT.value, ObservationFormat.SWE_BINARY.value]: + raise ValueError('obsFormat must be on of the SWE formats') + return v diff --git a/oshconnect/datamodels/encoding.py b/oshconnect/datamodels/encoding.py new file mode 100644 index 0000000..e2c04d7 --- /dev/null +++ b/oshconnect/datamodels/encoding.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel, Field + + +class Encoding(BaseModel): + id: str = Field(None) + type: str = Field(...) + vector_as_arrays: bool = Field(False, alias='vectorAsArrays') + + +class JSONEncoding(Encoding): + type: str = "JSONEncoding" diff --git a/oshconnect/datamodels/geometry.py b/oshconnect/datamodels/geometry.py new file mode 100644 index 0000000..60bbaab --- /dev/null +++ b/oshconnect/datamodels/geometry.py @@ -0,0 +1,14 @@ +from pydantic import BaseModel, Field + +from oshconnect.csapi4py.constants import GeometryTypes + + +# TODO: Add specific validations for each type +# TODO: update to either use third party Geometry definitions or create a more robust definition +class Geometry(BaseModel): + """ + A class to represent the geometry of a feature + """ + type: GeometryTypes = Field(...) + coordinates: list + bbox: list = None \ No newline at end of file diff --git a/oshconnect/datamodels/network_properties.py b/oshconnect/datamodels/network_properties.py new file mode 100644 index 0000000..51a0b95 --- /dev/null +++ b/oshconnect/datamodels/network_properties.py @@ -0,0 +1,10 @@ +from pydantic import BaseModel, HttpUrl + + +class NetworkProperties(BaseModel): + endpoint_url: HttpUrl + tls: bool = False + stream_protocol: str = 'ws' + mqtt_opts: dict = None + mqtt_endpoint_url: HttpUrl = None + connector_opts: dict = None diff --git a/oshconnect/datamodels/observations.py b/oshconnect/datamodels/observations.py new file mode 100644 index 0000000..3de9dee --- /dev/null +++ b/oshconnect/datamodels/observations.py @@ -0,0 +1,19 @@ +from datetime import datetime +from typing import Union, List + +from pydantic import BaseModel, Field + +from oshconnect.datamodels.api_utils import Link + + +class ObservationOMJSONInline(BaseModel): + """ + A class to represent an observation in OM-JSON format + """ + datastream_id: str = Field(None, serialization_alias="datastream@id") + foi_id: str = Field(None, serialization_alias="foi@id") + phenomenon_time: str = Field(None, serialization_alias="phenomenonTime") + result_time: str = Field(datetime.now().isoformat(), serialization_alias="resultTime") + parameters: dict = Field(None) + result: Union[int, float, str, dict, list] = Field(...) + result_links: List[Link] = Field(None, serialization_alias="result@links") diff --git a/oshconnect/datamodels/swe_components.py b/oshconnect/datamodels/swe_components.py new file mode 100644 index 0000000..911ac52 --- /dev/null +++ b/oshconnect/datamodels/swe_components.py @@ -0,0 +1,200 @@ +from __future__ import annotations + +from numbers import Real +from typing import Union, Any + +from pydantic import BaseModel, Field, field_validator, SerializeAsAny + +from oshconnect.csapi4py.constants import GeometryTypes +from oshconnect.datamodels.api_utils import UCUMCode, URI +from oshconnect.datamodels.geometry import Geometry + +""" + NOTE: The following classes are used to represent the Record Schemas that are required for use with Datastreams +The names are likely to change to include a "Schema" suffix to differentiate them from the actual data structures. +The current scope of the project likely excludes conversion from received data to actual SWE Common data structures, +in the event this is added it will most likely be in a separate module as those structures have use cases outside of +the API solely +""" + + +# TODO: Add field validators that are missing +# TODO: valid string fields that are intended to represent time/date values +# TODO: Validate places where string fields are not allowed to be empty + + +class AnyComponentSchema(BaseModel): + type: str = Field(...) + id: str = Field(None) + label: str = Field(None) + description: str = Field(None) + updatable: bool = Field(False) + optional: bool = Field(False) + definition: str = Field(None) + + +class DataRecordSchema(AnyComponentSchema): + type: str = "DataRecord" + fields: SerializeAsAny[list[AnyComponentSchema]] = Field(...) + + +class VectorSchema(AnyComponentSchema): + label: str = Field(...) + name: str = Field(...) + type: str = "Vector" + definition: str = Field(...) + reference_frame: str = Field(...) + local_frame: str = Field(None) + # TODO: VERIFY might need to be moved further down when these are defined + coordinates: SerializeAsAny[Union[list[CountSchema], list[QuantitySchema], list[TimeSchema]]] = Field(...) + + +class DataArraySchema(AnyComponentSchema): + type: str = "DataArray" + name: str = Field(...) + element_count: dict | str | CountSchema = Field(..., serialization_alias='elementCount') # Should type of Count + element_type: SerializeAsAny[list[AnyComponentSchema]] = Field(..., serialization_alias='elementType') + encoding: str = Field(...) # TODO: implement an encodings class + values: list = Field(None) + + +class MatrixSchema(AnyComponentSchema): + type: str = "Matrix" + element_count: dict | str | CountSchema = Field(..., serialization_alias='elementCount') # Should be type of Count + element_type: SerializeAsAny[list[AnyComponentSchema]] = Field(..., serialization_alias='elementType') + encoding: str = Field(...) # TODO: implement an encodings class + values: list = Field(None) + reference_frame: str = Field(None) + local_frame: str = Field(None) + + +class DataChoiceSchema(AnyComponentSchema): + type: str = "DataChoice" + updatable: bool = Field(False) + optional: bool = Field(False) + choice_value: CategorySchema = Field(..., serialization_alias='choiceValue') # TODO: Might be called "choiceValues" + items: SerializeAsAny[list[AnyComponentSchema]] = Field(...) + + +class GeometrySchema(AnyComponentSchema): + label: str = Field(...) + type: str = "Geometry" + updatable: bool = Field(False) + optional: bool = Field(False) + definition: str = Field(...) + constraint: dict = Field(default_factory=lambda: { + 'geomTypes': [ + GeometryTypes.POINT.value, + GeometryTypes.LINESTRING.value, + GeometryTypes.POLYGON.value, + GeometryTypes.MULTI_POINT.value, + GeometryTypes.MULTI_LINESTRING.value, + GeometryTypes.MULTI_POLYGON.value + ] + }) + nil_values: list = Field(None, serialization_alias='nilValues') + srs: str = Field(...) + value: Geometry = Field(None) + + +class AnySimpleComponentSchema(AnyComponentSchema): + label: str = Field(...) + description: str = Field(None) + type: str = Field(...) + updatable: bool = Field(False) + optional: bool = Field(False) + definition: str = Field(...) + reference_frame: str = Field(None, serialization_alias='referenceFrame') + axis_id: str = Field(None, serialization_alias='axisID') + quality: Union[list[QuantitySchema], list[QuantityRangeSchema], list[CategorySchema], list[TextSchema]] = Field( + None) # TODO: Union[Quantity, QuantityRange, Category, Text] + nil_values: list = Field(None, serialization_alias='nilValues') + constraint: Any = Field(None) + value: Any = Field(None) + name: str = Field(...) + + +class AnyScalarComponentSchema(AnySimpleComponentSchema): + """ + A base class for all scalar components. The structure is essentially that of AnySimpleComponent + """ + pass + + +class BooleanSchema(AnyScalarComponentSchema): + type: str = "Boolean" + value: bool = Field(None) + + +class CountSchema(AnyScalarComponentSchema): + type: str = "Count" + value: int = Field(None) + + +class QuantitySchema(AnyScalarComponentSchema): + type: str = "Quantity" + value: Union[float, str] = Field(None) + uom: Union[UCUMCode, URI] = Field(...) + + @field_validator('value') + @classmethod + def validate_value(cls, v): + if isinstance(v, Real): + return v + elif isinstance(v, str): + if v in ['NaN', 'INFINITY', '+INFINITY', '-INFINITY']: + return v + else: + raise ValueError( + 'string representation of value must be one of the following: NaN, INFINITY, +INFINITY, -INFINITY') + else: + try: + return float(v) + except ValueError: + raise ValueError('value must be a number or a string representing a special value ' + '[NaN, INFINITY, +INFINITY, -INFINITY]') + + +class TimeSchema(AnyScalarComponentSchema): + type: str = "Time" + value: str = Field(None) + reference_time: str = Field(None, serialization_alias='referenceTime') + local_frame: str = Field(None) + uom: Union[UCUMCode, URI] = Field(...) + + +class CategorySchema(AnyScalarComponentSchema): + type: str = "Category" + value: str = Field(None) + code_space: str = Field(None, serialization_alias='codeSpace') + + +class TextSchema(AnyScalarComponentSchema): + type: str = "Text" + value: str = Field(None) + + +class CountRangeSchema(AnySimpleComponentSchema): + type: str = "CountRange" + value: list[int] = Field(None) + uom: Union[UCUMCode, URI] = Field(...) + + +class QuantityRangeSchema(AnySimpleComponentSchema): + type: str = "QuantityRange" + value: list[Union[float, str]] = Field(None) + uom: Union[UCUMCode, URI] = Field(...) + + +class TimeRangeSchema(AnySimpleComponentSchema): + type: str = "TimeRange" + value: list[str] = Field(None) + reference_time: str = Field(None, serialization_alias='referenceTime') + local_frame: str = Field(None) + uom: Union[UCUMCode, URI] = Field(...) + + +class CategoryRangeSchema(AnySimpleComponentSchema): + type: str = "CategoryRange" + value: list[str] = Field(None) + code_space: str = Field(None, serialization_alias='codeSpace') diff --git a/oshconnect/datamodels/system_events_and_history.py b/oshconnect/datamodels/system_events_and_history.py new file mode 100644 index 0000000..f0801ec --- /dev/null +++ b/oshconnect/datamodels/system_events_and_history.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +from pydantic import BaseModel, Field, HttpUrl + +from oshconnect.datamodels.api_utils import Link, URI +from oshconnect.datamodels.geometry import Geometry + + +class SystemEventOMJSON(BaseModel): + """ + A class to represent the schema of a system event + """ + label: str = Field(...) + description: str = Field(None) + definition: HttpUrl = Field(...) + identifiers: list = Field(None) + classifiers: list = Field(None) + contacts: list = Field(None) + documentation: list = Field(None) + time: str = Field(...) + properties: list = Field(None) + configuration: dict = Field(None) + links: list[Link] = Field(None) + + +class SystemHistoryGeoJSON(BaseModel): + """ + A class to represent the schema of a system history + """ + type: str = Field(...) + id: str = Field(None) + properties: SystemHistoryProperties = Field(...) + geometry: Geometry = Field(None) + bbox: list = Field(None) + links: list[Link] = Field(None) + + +class SystemHistoryProperties(BaseModel): + feature_type: str = Field(...) + uid: URI = Field(...) + name: str = Field(...) + description: str = Field(None) + asset_type: str = Field(None) + valid_time: list = Field(None) + parent_system_link: str = Field(None, serialization_alias='parentSystem@link') + procedure_link: str = Field(None, serialization_alias='procedure@link') diff --git a/oshconnect/datasource.py b/oshconnect/datasource.py index dcefa1f..d63c1fb 100644 --- a/oshconnect/datasource.py +++ b/oshconnect/datasource.py @@ -16,9 +16,9 @@ import requests import websockets -from consys4py.constants import APIResourceTypes -from consys4py.datamodels.observations import ObservationOMJSONInline -from consys4py.datamodels.swe_components import DataRecordSchema +from oshconnect.csapi4py.constants import APIResourceTypes +from oshconnect.datamodels.observations import ObservationOMJSONInline +from oshconnect.datamodels.swe_components import DataRecordSchema from .core_datamodels import DatastreamResource, SystemResource, TimePeriod from .timemanagement import TemporalModes diff --git a/oshconnect/osh_connect_datamodels.py b/oshconnect/osh_connect_datamodels.py index 6f1899e..e9d4461 100644 --- a/oshconnect/osh_connect_datamodels.py +++ b/oshconnect/osh_connect_datamodels.py @@ -10,11 +10,11 @@ import uuid from dataclasses import dataclass, field -from consys4py.constants import APIResourceTypes -from consys4py.core.default_api_helpers import APIHelper -from consys4py.datamodels.datastreams import SWEDatastreamSchema -from consys4py.datamodels.encoding import JSONEncoding -from consys4py.datamodels.swe_components import DataRecordSchema +from oshconnect.csapi4py.constants import APIResourceTypes +from oshconnect.csapi4py.core.default_api_helpers import APIHelper +from oshconnect.datamodels.datastreams import SWEDatastreamSchema +from oshconnect.datamodels.encoding import JSONEncoding +from oshconnect.datamodels.swe_components import DataRecordSchema from .core_datamodels import DatastreamResource, ObservationResource, SystemResource from .timemanagement import TimeInstant, TimePeriod, TimeUtils diff --git a/oshconnect/oshconnectapi.py b/oshconnect/oshconnectapi.py index da06767..1ac8fde 100644 --- a/oshconnect/oshconnectapi.py +++ b/oshconnect/oshconnectapi.py @@ -7,7 +7,7 @@ import logging import shelve -from consys4py.core.default_api_helpers import APIHelper +from oshconnect.csapi4py.core.default_api_helpers import APIHelper from .core_datamodels import DatastreamResource, TimePeriod from .datasource import DataStream, DataStreamHandler, MessageWrapper diff --git a/pyproject.toml b/pyproject.toml index 761bb2f..b14cd73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,11 +8,10 @@ authors = [ ] requires-python = "<4.0,>=3.12" dependencies = [ + "paho-mqtt>=2.1.0", "pydantic<3.0.0,>=2.7.4", "shapely<3.0.0,>=2.0.4", "websockets<13.0,>=12.0", - "consys4py<1.0.0,>=0.0.1b3", - "swecommondm<1.0.0,>=0.0.1a0", ] [dependency-groups] diff --git a/rud/README.md b/rud/README.md deleted file mode 100644 index e424fe2..0000000 --- a/rud/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# What is this package? -These files are all either redundant, not needed for the project, or deprecated. They are kept here for reference purposes -as the library moves forward to v1.0 \ No newline at end of file diff --git a/rud/__init__.py b/rud/__init__.py deleted file mode 100644 index 6dc4918..0000000 --- a/rud/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# ============================================================================== -# Copyright (c) 2024 Botts Innovative Research, Inc. -# Date: 2024/6/25 -# Author: Ian Patterson -# Contact Email: ian@botts-inc.com -# ============================================================================== diff --git a/rud/unimpl/__init__.py b/rud/unimpl/__init__.py deleted file mode 100644 index 6dc4918..0000000 --- a/rud/unimpl/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# ============================================================================== -# Copyright (c) 2024 Botts Innovative Research, Inc. -# Date: 2024/6/25 -# Author: Ian Patterson -# Contact Email: ian@botts-inc.com -# ============================================================================== diff --git a/rud/unimpl/datasources/__init__.py b/rud/unimpl/datasources/__init__.py deleted file mode 100644 index 6dc4918..0000000 --- a/rud/unimpl/datasources/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# ============================================================================== -# Copyright (c) 2024 Botts Innovative Research, Inc. -# Date: 2024/6/25 -# Author: Ian Patterson -# Contact Email: ian@botts-inc.com -# ============================================================================== diff --git a/rud/unimpl/datasources/handler.py b/rud/unimpl/datasources/handler.py deleted file mode 100644 index 269391a..0000000 --- a/rud/unimpl/datasources/handler.py +++ /dev/null @@ -1,67 +0,0 @@ - -# ============================================================================== -# Copyright (c) 2024 Botts Innovative Research, Inc. -# Date: 2024/5/28 -# Author: Ian Patterson -# Contact Email: ian@botts-inc.com -# ============================================================================== - -from abc import ABC, abstractmethod - - -# Might not be necessary due to differences in this implementation die to actual multiprocessing -class DataSourceHandler(ABC): - def __init__(self): - self.context = None - self.topic = None - self.broadcast_channel = None - self.values = [] - self.version = 0 - self.properties = { - "batchsize": 1 - } - self._initialized = False - self.datasource_id = None - - @abstractmethod - def create_context(self, properties: dict): - pass - - async def init(self, datasource_id: str, properties: dict, topics: list[str]): - self.datasource_id = datasource_id - self.properties.update(properties) - self.topic = self.set_topics(topics) - # Context doesn't really have to exist in python - self.context = self.create_context(properties) - self.context.on_change_status = self.on_change_status() - self.context.handle_data = self.handle_data() - await self.context.init(self.properties) - self._initialized = True - - # TODO: topics may not be necessary - def set_topics(self, topics): - _topic = topics.data - if self.topic == _topic: - return - return - - def on_change_status(self): - pass - - def handle_data(self): - pass - - def flush(self): - pass - - def connect(self): - pass - - def disconnect(self): - pass - - def is_initialized(self): - return self._initialized - - def is_connected(self): - pass diff --git a/uv.lock b/uv.lock index 1db7aca..4409817 100644 --- a/uv.lock +++ b/uv.lock @@ -1,108 +1,94 @@ version = 1 -revision = 1 +revision = 3 requires-python = ">=3.12, <4.0" [[package]] name = "alabaster" version = "0.7.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776, upload-time = "2024-01-10T00:56:10.189Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511, upload-time = "2024-01-10T00:56:08.388Z" }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] [[package]] name = "babel" version = "2.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, ] [[package]] name = "certifi" version = "2025.1.31" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload-time = "2025-01-31T02:16:47.166Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload-time = "2025-01-31T02:16:45.015Z" }, ] [[package]] name = "charset-normalizer" version = "3.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload-time = "2024-12-24T18:10:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload-time = "2024-12-24T18:10:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload-time = "2024-12-24T18:10:45.492Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload-time = "2024-12-24T18:10:47.898Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload-time = "2024-12-24T18:10:50.589Z" }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload-time = "2024-12-24T18:10:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload-time = "2024-12-24T18:10:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload-time = "2024-12-24T18:10:55.048Z" }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload-time = "2024-12-24T18:10:57.647Z" }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload-time = "2024-12-24T18:10:59.43Z" }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload-time = "2024-12-24T18:11:00.676Z" }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload-time = "2024-12-24T18:11:01.952Z" }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload-time = "2024-12-24T18:11:03.142Z" }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload-time = "2024-12-24T18:11:05.834Z" }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload-time = "2024-12-24T18:11:07.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload-time = "2024-12-24T18:11:08.374Z" }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload-time = "2024-12-24T18:11:09.831Z" }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload-time = "2024-12-24T18:11:12.03Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload-time = "2024-12-24T18:11:13.372Z" }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload-time = "2024-12-24T18:11:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload-time = "2024-12-24T18:11:17.672Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload-time = "2024-12-24T18:11:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload-time = "2024-12-24T18:11:21.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload-time = "2024-12-24T18:11:22.774Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload-time = "2024-12-24T18:11:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload-time = "2024-12-24T18:11:26.535Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] -name = "consys4py" -version = "0.0.1b3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "paho-mqtt" }, - { name = "pydantic" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/26/5185fdf57b9ade4f5b2df9141ed01b938986bd1b8155c534536bd665c6d1/consys4py-0.0.1b3.tar.gz", hash = "sha256:10b8a8a3e3644e811af10b21052d7fa7172fd07b80c8fa60755d0c6ff3d8f816", size = 27114 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/ee/27dd55fc1068a89707a4f8205393c8c3424a77e170d7cde42319742e790b/consys4py-0.0.1b3-py3-none-any.whl", hash = "sha256:968cdd83386f64184dda6b3cd478e5b68ae4e77581901393e83a1df83922dcc5", size = 31383 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "docutils" version = "0.20.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/53/a5da4f2c5739cf66290fac1431ee52aff6851c7c8ffd8264f13affd7bcdd/docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b", size = 2058365 } +sdist = { url = "https://files.pythonhosted.org/packages/1f/53/a5da4f2c5739cf66290fac1431ee52aff6851c7c8ffd8264f13affd7bcdd/docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b", size = 2058365, upload-time = "2023-05-16T23:39:19.748Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", size = 572666 }, + { url = "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", size = 572666, upload-time = "2023-05-16T23:39:15.976Z" }, ] [[package]] @@ -114,36 +100,36 @@ dependencies = [ { name = "pycodestyle" }, { name = "pyflakes" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/c4/5842fc9fc94584c455543540af62fd9900faade32511fab650e9891ec225/flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426", size = 48177, upload-time = "2025-03-29T20:08:39.329Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786 }, + { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786, upload-time = "2025-03-29T20:08:37.902Z" }, ] [[package]] name = "idna" version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] [[package]] name = "imagesize" version = "1.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -153,94 +139,94 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] name = "markupsafe" version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, - { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] [[package]] name = "mccabe" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658 } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350 }, + { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, ] [[package]] name = "numpy" version = "2.2.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156 }, - { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092 }, - { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515 }, - { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558 }, - { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 }, - { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 }, - { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 }, - { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 }, - { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 }, - { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 }, - { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 }, - { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 }, - { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 }, - { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 }, - { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 }, - { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 }, - { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 }, - { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 }, - { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 }, - { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 }, - { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 }, - { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 }, - { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 }, - { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 }, - { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 }, - { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 }, - { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 }, - { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 }, - { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 }, - { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, +sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701, upload-time = "2025-03-16T18:27:00.648Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156, upload-time = "2025-03-16T18:09:51.975Z" }, + { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092, upload-time = "2025-03-16T18:10:16.329Z" }, + { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515, upload-time = "2025-03-16T18:10:26.19Z" }, + { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558, upload-time = "2025-03-16T18:10:38.996Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742, upload-time = "2025-03-16T18:11:02.76Z" }, + { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051, upload-time = "2025-03-16T18:11:32.767Z" }, + { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972, upload-time = "2025-03-16T18:11:59.877Z" }, + { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106, upload-time = "2025-03-16T18:12:31.487Z" }, + { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190, upload-time = "2025-03-16T18:12:44.46Z" }, + { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305, upload-time = "2025-03-16T18:13:06.864Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623, upload-time = "2025-03-16T18:13:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681, upload-time = "2025-03-16T18:14:08.031Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759, upload-time = "2025-03-16T18:14:18.613Z" }, + { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092, upload-time = "2025-03-16T18:14:31.386Z" }, + { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422, upload-time = "2025-03-16T18:14:54.83Z" }, + { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202, upload-time = "2025-03-16T18:15:22.035Z" }, + { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131, upload-time = "2025-03-16T18:15:48.546Z" }, + { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270, upload-time = "2025-03-16T18:16:20.274Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141, upload-time = "2025-03-16T18:20:15.297Z" }, + { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885, upload-time = "2025-03-16T18:20:36.982Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829, upload-time = "2025-03-16T18:16:56.191Z" }, + { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419, upload-time = "2025-03-16T18:17:22.811Z" }, + { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414, upload-time = "2025-03-16T18:17:34.066Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379, upload-time = "2025-03-16T18:17:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725, upload-time = "2025-03-16T18:18:11.904Z" }, + { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638, upload-time = "2025-03-16T18:18:40.749Z" }, + { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717, upload-time = "2025-03-16T18:19:04.512Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998, upload-time = "2025-03-16T18:19:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896, upload-time = "2025-03-16T18:19:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119, upload-time = "2025-03-16T18:20:03.94Z" }, ] [[package]] @@ -248,10 +234,9 @@ name = "oshconnect" version = "0.2.4.post1" source = { virtual = "." } dependencies = [ - { name = "consys4py" }, + { name = "paho-mqtt" }, { name = "pydantic" }, { name = "shapely" }, - { name = "swecommondm" }, { name = "websockets" }, ] @@ -265,10 +250,9 @@ dev = [ [package.metadata] requires-dist = [ - { name = "consys4py", specifier = ">=0.0.1b3,<1.0.0" }, + { name = "paho-mqtt", specifier = ">=2.1.0" }, { name = "pydantic", specifier = ">=2.7.4,<3.0.0" }, { name = "shapely", specifier = ">=2.0.4,<3.0.0" }, - { name = "swecommondm", specifier = ">=0.0.1a0,<1.0.0" }, { name = "websockets", specifier = ">=12.0,<13.0" }, ] @@ -284,36 +268,36 @@ dev = [ name = "packaging" version = "24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, ] [[package]] name = "paho-mqtt" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/39/15/0a6214e76d4d32e7f663b109cf71fb22561c2be0f701d67f93950cd40542/paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834", size = 148848 } +sdist = { url = "https://files.pythonhosted.org/packages/39/15/0a6214e76d4d32e7f663b109cf71fb22561c2be0f701d67f93950cd40542/paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834", size = 148848, upload-time = "2024-04-29T19:52:55.591Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219 }, + { url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219, upload-time = "2024-04-29T19:52:48.345Z" }, ] [[package]] name = "pluggy" version = "1.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] [[package]] name = "pycodestyle" version = "2.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312 } +sdist = { url = "https://files.pythonhosted.org/packages/04/6e/1f4a62078e4d95d82367f24e685aef3a672abfd27d1a868068fed4ed2254/pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae", size = 39312, upload-time = "2025-03-29T17:33:30.669Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424 }, + { url = "https://files.pythonhosted.org/packages/07/be/b00116df1bfb3e0bb5b45e29d604799f7b91dd861637e4d448b4e09e6a3e/pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9", size = 31424, upload-time = "2025-03-29T17:33:29.405Z" }, ] [[package]] @@ -326,9 +310,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513 } +sdist = { url = "https://files.pythonhosted.org/packages/10/2e/ca897f093ee6c5f3b0bee123ee4465c50e75431c3d5b6a3b44a47134e891/pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3", size = 785513, upload-time = "2025-04-08T13:27:06.399Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591 }, + { url = "https://files.pythonhosted.org/packages/b0/1d/407b29780a289868ed696d1616f4aad49d6388e5a77f567dcd2629dcd7b8/pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f", size = 443591, upload-time = "2025-04-08T13:27:03.789Z" }, ] [[package]] @@ -338,57 +322,57 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 }, - { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 }, - { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 }, - { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 }, - { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 }, - { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 }, - { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 }, - { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 }, - { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 }, - { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 }, - { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 }, - { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 }, - { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 }, - { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 }, - { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 }, - { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 }, - { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 }, - { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 }, - { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 }, - { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 }, - { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 }, - { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 }, - { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 }, - { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 }, - { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 }, - { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 }, - { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 }, - { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 }, - { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 }, - { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 }, - { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 }, +sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395, upload-time = "2025-04-02T09:49:41.8Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640, upload-time = "2025-04-02T09:47:25.394Z" }, + { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649, upload-time = "2025-04-02T09:47:27.417Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472, upload-time = "2025-04-02T09:47:29.006Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509, upload-time = "2025-04-02T09:47:33.464Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702, upload-time = "2025-04-02T09:47:34.812Z" }, + { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428, upload-time = "2025-04-02T09:47:37.315Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753, upload-time = "2025-04-02T09:47:39.013Z" }, + { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849, upload-time = "2025-04-02T09:47:40.427Z" }, + { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541, upload-time = "2025-04-02T09:47:42.01Z" }, + { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225, upload-time = "2025-04-02T09:47:43.425Z" }, + { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373, upload-time = "2025-04-02T09:47:44.979Z" }, + { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034, upload-time = "2025-04-02T09:47:46.843Z" }, + { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848, upload-time = "2025-04-02T09:47:48.404Z" }, + { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986, upload-time = "2025-04-02T09:47:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551, upload-time = "2025-04-02T09:47:51.648Z" }, + { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785, upload-time = "2025-04-02T09:47:53.149Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758, upload-time = "2025-04-02T09:47:55.006Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109, upload-time = "2025-04-02T09:47:56.532Z" }, + { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159, upload-time = "2025-04-02T09:47:58.088Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222, upload-time = "2025-04-02T09:47:59.591Z" }, + { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980, upload-time = "2025-04-02T09:48:01.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840, upload-time = "2025-04-02T09:48:03.056Z" }, + { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518, upload-time = "2025-04-02T09:48:04.662Z" }, + { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025, upload-time = "2025-04-02T09:48:06.226Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991, upload-time = "2025-04-02T09:48:08.114Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262, upload-time = "2025-04-02T09:48:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626, upload-time = "2025-04-02T09:48:11.288Z" }, + { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590, upload-time = "2025-04-02T09:48:12.861Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963, upload-time = "2025-04-02T09:48:14.553Z" }, + { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896, upload-time = "2025-04-02T09:48:16.222Z" }, + { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810, upload-time = "2025-04-02T09:48:17.97Z" }, ] [[package]] name = "pyflakes" version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175 } +sdist = { url = "https://files.pythonhosted.org/packages/af/cc/1df338bd7ed1fa7c317081dcf29bf2f01266603b301e6858856d346a12b3/pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b", size = 64175, upload-time = "2025-03-31T13:21:20.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164 }, + { url = "https://files.pythonhosted.org/packages/15/40/b293a4fa769f3b02ab9e387c707c4cbdc34f073f945de0386107d4e669e6/pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a", size = 63164, upload-time = "2025-03-31T13:21:18.503Z" }, ] [[package]] name = "pygments" version = "2.19.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, ] [[package]] @@ -401,9 +385,9 @@ dependencies = [ { name = "packaging" }, { name = "pluggy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, ] [[package]] @@ -416,9 +400,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, ] [[package]] @@ -428,41 +412,41 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/fe/3b0d2f828ffaceadcdcb51b75b9c62d98e62dd95ce575278de35f24a1c20/shapely-2.1.0.tar.gz", hash = "sha256:2cbe90e86fa8fc3ca8af6ffb00a77b246b918c7cf28677b7c21489b678f6b02e", size = 313617 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/d1/6a9371ec39d3ef08e13225594e6c55b045209629afd9e6d403204507c2a8/shapely-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53e7ee8bd8609cf12ee6dce01ea5affe676976cf7049315751d53d8db6d2b4b2", size = 1830732 }, - { url = "https://files.pythonhosted.org/packages/32/87/799e3e48be7ce848c08509b94d2180f4ddb02e846e3c62d0af33da4d78d3/shapely-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cab20b665d26dbec0b380e15749bea720885a481fa7b1eedc88195d4a98cfa4", size = 1638404 }, - { url = "https://files.pythonhosted.org/packages/85/00/6665d77f9dd09478ab0993b8bc31668aec4fd3e5f1ddd1b28dd5830e47be/shapely-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4a38b39a09340273c3c92b3b9a374272a12cc7e468aeeea22c1c46217a03e5c", size = 2945316 }, - { url = "https://files.pythonhosted.org/packages/34/49/738e07d10bbc67cae0dcfe5a484c6e518a517f4f90550dda2adf3a78b9f2/shapely-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edaec656bdd9b71278b98e6f77c464b1c3b2daa9eace78012ff0f0b4b5b15b04", size = 3063099 }, - { url = "https://files.pythonhosted.org/packages/88/b8/138098674559362ab29f152bff3b6630de423378fbb0324812742433a4ef/shapely-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c8a732ddd9b25e7a54aa748e7df8fd704e23e5d5d35b7d376d80bffbfc376d04", size = 3887873 }, - { url = "https://files.pythonhosted.org/packages/67/a8/fdae7c2db009244991d86f4d2ca09d2f5ccc9d41c312c3b1ee1404dc55da/shapely-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9c93693ad8adfdc9138a5a2d42da02da94f728dd2e82d2f0f442f10e25027f5f", size = 4067004 }, - { url = "https://files.pythonhosted.org/packages/ed/78/17e17d91b489019379df3ee1afc4bd39787b232aaa1d540f7d376f0280b7/shapely-2.1.0-cp312-cp312-win32.whl", hash = "sha256:d8ac6604eefe807e71a908524de23a37920133a1729fe3a4dfe0ed82c044cbf4", size = 1527366 }, - { url = "https://files.pythonhosted.org/packages/b8/bd/9249bd6dda948441e25e4fb14cbbb5205146b0fff12c66b19331f1ff2141/shapely-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f4f47e631aa4f9ec5576eac546eb3f38802e2f82aeb0552f9612cb9a14ece1db", size = 1708265 }, - { url = "https://files.pythonhosted.org/packages/8d/77/4e368704b2193e74498473db4461d697cc6083c96f8039367e59009d78bd/shapely-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b64423295b563f43a043eb786e7a03200ebe68698e36d2b4b1c39f31dfb50dfb", size = 1830029 }, - { url = "https://files.pythonhosted.org/packages/71/3c/d888597bda680e4de987316b05ca9db07416fa29523beff64f846503302f/shapely-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1b5578f45adc25b235b22d1ccb9a0348c8dc36f31983e57ea129a88f96f7b870", size = 1637999 }, - { url = "https://files.pythonhosted.org/packages/03/8d/ee0e23b7ef88fba353c63a81f1f329c77f5703835db7b165e7c0b8b7f839/shapely-2.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a7e83d383b27f02b684e50ab7f34e511c92e33b6ca164a6a9065705dd64bcb", size = 2929348 }, - { url = "https://files.pythonhosted.org/packages/d1/a7/5c9cb413e4e2ce52c16be717e94abd40ce91b1f8974624d5d56154c5d40b/shapely-2.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:942031eb4d8f7b3b22f43ba42c09c7aa3d843aa10d5cc1619fe816e923b66e55", size = 3048973 }, - { url = "https://files.pythonhosted.org/packages/84/23/45b90c0bd2157b238490ca56ef2eedf959d3514c7d05475f497a2c88b6d9/shapely-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d2843c456a2e5627ee6271800f07277c0d2652fb287bf66464571a057dbc00b3", size = 3873148 }, - { url = "https://files.pythonhosted.org/packages/c0/bc/ed7d5d37f5395166042576f0c55a12d7e56102799464ba7ea3a72a38c769/shapely-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8c4b17469b7f39a5e6a7cfea79f38ae08a275427f41fe8b48c372e1449147908", size = 4052655 }, - { url = "https://files.pythonhosted.org/packages/c0/8f/a1dafbb10d20d1c569f2db3fb1235488f624dafe8469e8ce65356800ba31/shapely-2.1.0-cp313-cp313-win32.whl", hash = "sha256:30e967abd08fce49513d4187c01b19f139084019f33bec0673e8dbeb557c45e4", size = 1526600 }, - { url = "https://files.pythonhosted.org/packages/e3/f0/9f8cdf2258d7aed742459cea51c70d184de92f5d2d6f5f7f1ded90a18c31/shapely-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:1dc8d4364483a14aba4c844b7bd16a6fa3728887e2c33dfa1afa34a3cf4d08a5", size = 1707115 }, - { url = "https://files.pythonhosted.org/packages/75/ed/32952df461753a65b3e5d24c8efb361d3a80aafaef0b70d419063f6f2c11/shapely-2.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:673e073fea099d1c82f666fb7ab0a00a77eff2999130a69357ce11941260d855", size = 1824847 }, - { url = "https://files.pythonhosted.org/packages/ff/b9/2284de512af30b02f93ddcdd2e5c79834a3cf47fa3ca11b0f74396feb046/shapely-2.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6d1513f915a56de67659fe2047c1ad5ff0f8cbff3519d1e74fced69c9cb0e7da", size = 1631035 }, - { url = "https://files.pythonhosted.org/packages/35/16/a59f252a7e736b73008f10d0950ffeeb0d5953be7c0bdffd39a02a6ba310/shapely-2.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d6a7043178890b9e028d80496ff4c79dc7629bff4d78a2f25323b661756bab8", size = 2968639 }, - { url = "https://files.pythonhosted.org/packages/a5/0a/6a20eca7b0092cfa243117e8e145a58631a4833a0a519ec9b445172e83a0/shapely-2.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb638378dc3d76f7e85b67d7e2bb1366811912430ac9247ac00c127c2b444cdc", size = 3055713 }, - { url = "https://files.pythonhosted.org/packages/fb/44/eeb0c7583b1453d1cf7a319a1d738e08f98a5dc993fa1ef3c372983e4cb5/shapely-2.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:737124e87d91d616acf9a911f74ac55e05db02a43a6a7245b3d663817b876055", size = 3890478 }, - { url = "https://files.pythonhosted.org/packages/5d/6e/37ff3c6af1d408cacb0a7d7bfea7b8ab163a5486e35acb08997eae9d8756/shapely-2.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e6c229e7bb87aae5df82fa00b6718987a43ec168cc5affe095cca59d233f314", size = 4036148 }, - { url = "https://files.pythonhosted.org/packages/c8/6a/8c0b7de3aeb5014a23f06c5e9d3c7852ebcf0d6b00fe660b93261e310e24/shapely-2.1.0-cp313-cp313t-win32.whl", hash = "sha256:a9580bda119b1f42f955aa8e52382d5c73f7957e0203bc0c0c60084846f3db94", size = 1535993 }, - { url = "https://files.pythonhosted.org/packages/a8/91/ae80359a58409d52e4d62c7eacc7eb3ddee4b9135f1db884b6a43cf2e174/shapely-2.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e8ff4e5cfd799ba5b6f37b5d5527dbd85b4a47c65b6d459a03d0962d2a9d4d10", size = 1717777 }, +sdist = { url = "https://files.pythonhosted.org/packages/fb/fe/3b0d2f828ffaceadcdcb51b75b9c62d98e62dd95ce575278de35f24a1c20/shapely-2.1.0.tar.gz", hash = "sha256:2cbe90e86fa8fc3ca8af6ffb00a77b246b918c7cf28677b7c21489b678f6b02e", size = 313617, upload-time = "2025-04-03T09:15:05.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/d1/6a9371ec39d3ef08e13225594e6c55b045209629afd9e6d403204507c2a8/shapely-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53e7ee8bd8609cf12ee6dce01ea5affe676976cf7049315751d53d8db6d2b4b2", size = 1830732, upload-time = "2025-04-03T09:14:25.047Z" }, + { url = "https://files.pythonhosted.org/packages/32/87/799e3e48be7ce848c08509b94d2180f4ddb02e846e3c62d0af33da4d78d3/shapely-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cab20b665d26dbec0b380e15749bea720885a481fa7b1eedc88195d4a98cfa4", size = 1638404, upload-time = "2025-04-03T09:14:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/85/00/6665d77f9dd09478ab0993b8bc31668aec4fd3e5f1ddd1b28dd5830e47be/shapely-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4a38b39a09340273c3c92b3b9a374272a12cc7e468aeeea22c1c46217a03e5c", size = 2945316, upload-time = "2025-04-03T09:14:28.266Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/738e07d10bbc67cae0dcfe5a484c6e518a517f4f90550dda2adf3a78b9f2/shapely-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edaec656bdd9b71278b98e6f77c464b1c3b2daa9eace78012ff0f0b4b5b15b04", size = 3063099, upload-time = "2025-04-03T09:14:30.067Z" }, + { url = "https://files.pythonhosted.org/packages/88/b8/138098674559362ab29f152bff3b6630de423378fbb0324812742433a4ef/shapely-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c8a732ddd9b25e7a54aa748e7df8fd704e23e5d5d35b7d376d80bffbfc376d04", size = 3887873, upload-time = "2025-04-03T09:14:31.912Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fdae7c2db009244991d86f4d2ca09d2f5ccc9d41c312c3b1ee1404dc55da/shapely-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9c93693ad8adfdc9138a5a2d42da02da94f728dd2e82d2f0f442f10e25027f5f", size = 4067004, upload-time = "2025-04-03T09:14:33.976Z" }, + { url = "https://files.pythonhosted.org/packages/ed/78/17e17d91b489019379df3ee1afc4bd39787b232aaa1d540f7d376f0280b7/shapely-2.1.0-cp312-cp312-win32.whl", hash = "sha256:d8ac6604eefe807e71a908524de23a37920133a1729fe3a4dfe0ed82c044cbf4", size = 1527366, upload-time = "2025-04-03T09:14:35.348Z" }, + { url = "https://files.pythonhosted.org/packages/b8/bd/9249bd6dda948441e25e4fb14cbbb5205146b0fff12c66b19331f1ff2141/shapely-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f4f47e631aa4f9ec5576eac546eb3f38802e2f82aeb0552f9612cb9a14ece1db", size = 1708265, upload-time = "2025-04-03T09:14:36.878Z" }, + { url = "https://files.pythonhosted.org/packages/8d/77/4e368704b2193e74498473db4461d697cc6083c96f8039367e59009d78bd/shapely-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b64423295b563f43a043eb786e7a03200ebe68698e36d2b4b1c39f31dfb50dfb", size = 1830029, upload-time = "2025-04-03T09:14:38.795Z" }, + { url = "https://files.pythonhosted.org/packages/71/3c/d888597bda680e4de987316b05ca9db07416fa29523beff64f846503302f/shapely-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1b5578f45adc25b235b22d1ccb9a0348c8dc36f31983e57ea129a88f96f7b870", size = 1637999, upload-time = "2025-04-03T09:14:40.209Z" }, + { url = "https://files.pythonhosted.org/packages/03/8d/ee0e23b7ef88fba353c63a81f1f329c77f5703835db7b165e7c0b8b7f839/shapely-2.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a7e83d383b27f02b684e50ab7f34e511c92e33b6ca164a6a9065705dd64bcb", size = 2929348, upload-time = "2025-04-03T09:14:42.11Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a7/5c9cb413e4e2ce52c16be717e94abd40ce91b1f8974624d5d56154c5d40b/shapely-2.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:942031eb4d8f7b3b22f43ba42c09c7aa3d843aa10d5cc1619fe816e923b66e55", size = 3048973, upload-time = "2025-04-03T09:14:43.841Z" }, + { url = "https://files.pythonhosted.org/packages/84/23/45b90c0bd2157b238490ca56ef2eedf959d3514c7d05475f497a2c88b6d9/shapely-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d2843c456a2e5627ee6271800f07277c0d2652fb287bf66464571a057dbc00b3", size = 3873148, upload-time = "2025-04-03T09:14:45.924Z" }, + { url = "https://files.pythonhosted.org/packages/c0/bc/ed7d5d37f5395166042576f0c55a12d7e56102799464ba7ea3a72a38c769/shapely-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8c4b17469b7f39a5e6a7cfea79f38ae08a275427f41fe8b48c372e1449147908", size = 4052655, upload-time = "2025-04-03T09:14:47.475Z" }, + { url = "https://files.pythonhosted.org/packages/c0/8f/a1dafbb10d20d1c569f2db3fb1235488f624dafe8469e8ce65356800ba31/shapely-2.1.0-cp313-cp313-win32.whl", hash = "sha256:30e967abd08fce49513d4187c01b19f139084019f33bec0673e8dbeb557c45e4", size = 1526600, upload-time = "2025-04-03T09:14:48.952Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f0/9f8cdf2258d7aed742459cea51c70d184de92f5d2d6f5f7f1ded90a18c31/shapely-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:1dc8d4364483a14aba4c844b7bd16a6fa3728887e2c33dfa1afa34a3cf4d08a5", size = 1707115, upload-time = "2025-04-03T09:14:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/75/ed/32952df461753a65b3e5d24c8efb361d3a80aafaef0b70d419063f6f2c11/shapely-2.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:673e073fea099d1c82f666fb7ab0a00a77eff2999130a69357ce11941260d855", size = 1824847, upload-time = "2025-04-03T09:14:52.358Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b9/2284de512af30b02f93ddcdd2e5c79834a3cf47fa3ca11b0f74396feb046/shapely-2.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6d1513f915a56de67659fe2047c1ad5ff0f8cbff3519d1e74fced69c9cb0e7da", size = 1631035, upload-time = "2025-04-03T09:14:53.739Z" }, + { url = "https://files.pythonhosted.org/packages/35/16/a59f252a7e736b73008f10d0950ffeeb0d5953be7c0bdffd39a02a6ba310/shapely-2.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d6a7043178890b9e028d80496ff4c79dc7629bff4d78a2f25323b661756bab8", size = 2968639, upload-time = "2025-04-03T09:14:55.674Z" }, + { url = "https://files.pythonhosted.org/packages/a5/0a/6a20eca7b0092cfa243117e8e145a58631a4833a0a519ec9b445172e83a0/shapely-2.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb638378dc3d76f7e85b67d7e2bb1366811912430ac9247ac00c127c2b444cdc", size = 3055713, upload-time = "2025-04-03T09:14:57.564Z" }, + { url = "https://files.pythonhosted.org/packages/fb/44/eeb0c7583b1453d1cf7a319a1d738e08f98a5dc993fa1ef3c372983e4cb5/shapely-2.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:737124e87d91d616acf9a911f74ac55e05db02a43a6a7245b3d663817b876055", size = 3890478, upload-time = "2025-04-03T09:14:59.139Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6e/37ff3c6af1d408cacb0a7d7bfea7b8ab163a5486e35acb08997eae9d8756/shapely-2.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e6c229e7bb87aae5df82fa00b6718987a43ec168cc5affe095cca59d233f314", size = 4036148, upload-time = "2025-04-03T09:15:01.328Z" }, + { url = "https://files.pythonhosted.org/packages/c8/6a/8c0b7de3aeb5014a23f06c5e9d3c7852ebcf0d6b00fe660b93261e310e24/shapely-2.1.0-cp313-cp313t-win32.whl", hash = "sha256:a9580bda119b1f42f955aa8e52382d5c73f7957e0203bc0c0c60084846f3db94", size = 1535993, upload-time = "2025-04-03T09:15:02.973Z" }, + { url = "https://files.pythonhosted.org/packages/a8/91/ae80359a58409d52e4d62c7eacc7eb3ddee4b9135f1db884b6a43cf2e174/shapely-2.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e8ff4e5cfd799ba5b6f37b5d5527dbd85b4a47c65b6d459a03d0962d2a9d4d10", size = 1717777, upload-time = "2025-04-03T09:15:04.461Z" }, ] [[package]] name = "snowballstemmer" version = "2.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699, upload-time = "2021-11-16T18:38:38.009Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002, upload-time = "2021-11-16T18:38:34.792Z" }, ] [[package]] @@ -487,9 +471,9 @@ dependencies = [ { name = "sphinxcontrib-qthelp" }, { name = "sphinxcontrib-serializinghtml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911 } +sdist = { url = "https://files.pythonhosted.org/packages/5b/be/50e50cb4f2eff47df05673d361095cafd95521d2a22521b920c67a372dcb/sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", size = 8067911, upload-time = "2024-07-20T14:46:56.059Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624 }, + { url = "https://files.pythonhosted.org/packages/0d/ef/153f6803c5d5f8917dbb7f7fcf6d34a871ede3296fa89c2c703f5f8a6c8e/sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239", size = 3401624, upload-time = "2024-07-20T14:46:52.142Z" }, ] [[package]] @@ -501,36 +485,36 @@ dependencies = [ { name = "sphinx" }, { name = "sphinxcontrib-jquery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/33/2a35a9cdbfda9086bda11457bcc872173ab3565b16b6d7f6b3efaa6dc3d6/sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b", size = 2785005 } +sdist = { url = "https://files.pythonhosted.org/packages/fe/33/2a35a9cdbfda9086bda11457bcc872173ab3565b16b6d7f6b3efaa6dc3d6/sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b", size = 2785005, upload-time = "2023-11-28T04:14:03.104Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/46/00fda84467815c29951a9c91e3ae7503c409ddad04373e7cfc78daad4300/sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586", size = 2824721 }, + { url = "https://files.pythonhosted.org/packages/ea/46/00fda84467815c29951a9c91e3ae7503c409ddad04373e7cfc78daad4300/sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586", size = 2824721, upload-time = "2023-11-28T04:13:59.589Z" }, ] [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, ] [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, ] [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, ] [[package]] @@ -540,53 +524,45 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331 } +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104 }, + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, ] [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, ] [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, ] [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, -] - -[[package]] -name = "swecommondm" -version = "0.0.1a0" -source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/89/a88489999fd0d1b3409c198439962d9c41db98f1af239d79329f38554c73/swecommondm-0.0.1a0-py3-none-any.whl", hash = "sha256:e0d4fe9b15aa1516e10ce4f20c592ebbf544a76ef3af644c9efd8654de74f8f7", size = 13502 }, + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, ] [[package]] name = "typing-extensions" version = "4.13.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, ] [[package]] @@ -596,36 +572,36 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, ] [[package]] name = "urllib3" version = "2.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" }, ] [[package]] name = "websockets" version = "12.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/62/7a7874b7285413c954a4cca3c11fd851f11b2fe5b4ae2d9bee4f6d9bdb10/websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", size = 104994 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/6d/23cc898647c8a614a0d9ca703695dd04322fb5135096a20c2684b7c852b6/websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", size = 124061 }, - { url = "https://files.pythonhosted.org/packages/39/34/364f30fdf1a375e4002a26ee3061138d1571dfda6421126127d379d13930/websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", size = 121296 }, - { url = "https://files.pythonhosted.org/packages/2e/00/96ae1c9dcb3bc316ef683f2febd8c97dde9f254dc36c3afc65c7645f734c/websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", size = 121326 }, - { url = "https://files.pythonhosted.org/packages/af/f1/bba1e64430685dd456c1a1fd6b0c791ae33104967b928aefeff261761e8d/websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", size = 131807 }, - { url = "https://files.pythonhosted.org/packages/62/3b/98ee269712f37d892b93852ce07b3e6d7653160ca4c0d4f8c8663f8021f8/websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", size = 130751 }, - { url = "https://files.pythonhosted.org/packages/f1/00/d6f01ca2b191f8b0808e4132ccd2e7691f0453cbd7d0f72330eb97453c3a/websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", size = 131176 }, - { url = "https://files.pythonhosted.org/packages/af/9c/703ff3cd8109dcdee6152bae055d852ebaa7750117760ded697ab836cbcf/websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", size = 136246 }, - { url = "https://files.pythonhosted.org/packages/0b/a5/1a38fb85a456b9dc874ec984f3ff34f6550eafd17a3da28753cd3c1628e8/websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", size = 135466 }, - { url = "https://files.pythonhosted.org/packages/3c/98/1261f289dff7e65a38d59d2f591de6ed0a2580b729aebddec033c4d10881/websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", size = 136083 }, - { url = "https://files.pythonhosted.org/packages/a9/1c/f68769fba63ccb9c13fe0a25b616bd5aebeef1c7ddebc2ccc32462fb784d/websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", size = 124460 }, - { url = "https://files.pythonhosted.org/packages/20/52/8915f51f9aaef4e4361c89dd6cf69f72a0159f14e0d25026c81b6ad22525/websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", size = 124985 }, - { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370 }, +sdist = { url = "https://files.pythonhosted.org/packages/2e/62/7a7874b7285413c954a4cca3c11fd851f11b2fe5b4ae2d9bee4f6d9bdb10/websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b", size = 104994, upload-time = "2023-10-21T14:21:11.88Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/6d/23cc898647c8a614a0d9ca703695dd04322fb5135096a20c2684b7c852b6/websockets-12.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0e6e2711d5a8e6e482cacb927a49a3d432345dfe7dea8ace7b5790df5932e4df", size = 124061, upload-time = "2023-10-21T14:20:02.221Z" }, + { url = "https://files.pythonhosted.org/packages/39/34/364f30fdf1a375e4002a26ee3061138d1571dfda6421126127d379d13930/websockets-12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbcf72a37f0b3316e993e13ecf32f10c0e1259c28ffd0a85cee26e8549595fbc", size = 121296, upload-time = "2023-10-21T14:20:03.591Z" }, + { url = "https://files.pythonhosted.org/packages/2e/00/96ae1c9dcb3bc316ef683f2febd8c97dde9f254dc36c3afc65c7645f734c/websockets-12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12743ab88ab2af1d17dd4acb4645677cb7063ef4db93abffbf164218a5d54c6b", size = 121326, upload-time = "2023-10-21T14:20:04.956Z" }, + { url = "https://files.pythonhosted.org/packages/af/f1/bba1e64430685dd456c1a1fd6b0c791ae33104967b928aefeff261761e8d/websockets-12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b645f491f3c48d3f8a00d1fce07445fab7347fec54a3e65f0725d730d5b99cb", size = 131807, upload-time = "2023-10-21T14:20:06.153Z" }, + { url = "https://files.pythonhosted.org/packages/62/3b/98ee269712f37d892b93852ce07b3e6d7653160ca4c0d4f8c8663f8021f8/websockets-12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9893d1aa45a7f8b3bc4510f6ccf8db8c3b62120917af15e3de247f0780294b92", size = 130751, upload-time = "2023-10-21T14:20:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/f1/00/d6f01ca2b191f8b0808e4132ccd2e7691f0453cbd7d0f72330eb97453c3a/websockets-12.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f38a7b376117ef7aff996e737583172bdf535932c9ca021746573bce40165ed", size = 131176, upload-time = "2023-10-21T14:20:09.212Z" }, + { url = "https://files.pythonhosted.org/packages/af/9c/703ff3cd8109dcdee6152bae055d852ebaa7750117760ded697ab836cbcf/websockets-12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5", size = 136246, upload-time = "2023-10-21T14:20:10.423Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a5/1a38fb85a456b9dc874ec984f3ff34f6550eafd17a3da28753cd3c1628e8/websockets-12.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1e4b3f8ea6a9cfa8be8484c9221ec0257508e3a1ec43c36acdefb2a9c3b00aa2", size = 135466, upload-time = "2023-10-21T14:20:11.826Z" }, + { url = "https://files.pythonhosted.org/packages/3c/98/1261f289dff7e65a38d59d2f591de6ed0a2580b729aebddec033c4d10881/websockets-12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9fdf06fd06c32205a07e47328ab49c40fc1407cdec801d698a7c41167ea45113", size = 136083, upload-time = "2023-10-21T14:20:13.451Z" }, + { url = "https://files.pythonhosted.org/packages/a9/1c/f68769fba63ccb9c13fe0a25b616bd5aebeef1c7ddebc2ccc32462fb784d/websockets-12.0-cp312-cp312-win32.whl", hash = "sha256:baa386875b70cbd81798fa9f71be689c1bf484f65fd6fb08d051a0ee4e79924d", size = 124460, upload-time = "2023-10-21T14:20:14.719Z" }, + { url = "https://files.pythonhosted.org/packages/20/52/8915f51f9aaef4e4361c89dd6cf69f72a0159f14e0d25026c81b6ad22525/websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", size = 124985, upload-time = "2023-10-21T14:20:15.817Z" }, + { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370, upload-time = "2023-10-21T14:21:10.075Z" }, ] From 78371d0360f09da759f46441d0f42801c251f9e0 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Tue, 16 Sep 2025 22:47:57 -0500 Subject: [PATCH 15/31] Fix noted bugs from out-of-date imports --- oshconnect/__init__.py | 4 ++-- oshconnect/csapi4py/part_2/__init__.py | 6 ++++++ oshconnect/datamodels/__init__.py | 10 ++++++++++ oshconnect/datamodels/geometry.py | 3 ++- oshconnect/datasource.py | 3 ++- oshconnect/oshconnectapi.py | 13 ++++++++----- oshconnect/timemanagement.py | 11 +++++------ pyproject.toml | 16 ++++++++-------- tests/test_api_update.py | 6 ++++++ tests/test_oshconnect.py | 2 +- uv.lock | 12 ++++++------ 11 files changed, 56 insertions(+), 30 deletions(-) create mode 100644 tests/test_api_update.py diff --git a/oshconnect/__init__.py b/oshconnect/__init__.py index e54aa51..256a5e3 100644 --- a/oshconnect/__init__.py +++ b/oshconnect/__init__.py @@ -5,5 +5,5 @@ # Contact Email: ian@botts-inc.com # ============================================================================== -class Example: - pass +from .oshconnectapi import OSHConnect, Node +from .osh_connect_datamodels import System, Node, Datastream, Observation, ControlChannel \ No newline at end of file diff --git a/oshconnect/csapi4py/part_2/__init__.py b/oshconnect/csapi4py/part_2/__init__.py index e69de29..7ddbce4 100644 --- a/oshconnect/csapi4py/part_2/__init__.py +++ b/oshconnect/csapi4py/part_2/__init__.py @@ -0,0 +1,6 @@ +# import commands +# import control_channels +# import datastreams +# import observations +# import system_events +# import system_history \ No newline at end of file diff --git a/oshconnect/datamodels/__init__.py b/oshconnect/datamodels/__init__.py index e69de29..96c9d10 100644 --- a/oshconnect/datamodels/__init__.py +++ b/oshconnect/datamodels/__init__.py @@ -0,0 +1,10 @@ +# import api_utils +# import commands +# import control_streams +# import datastreams +# import encoding +# import geometry +# import network_properties +# import observations +# import swe_components +# import system_events_and_history diff --git a/oshconnect/datamodels/geometry.py b/oshconnect/datamodels/geometry.py index 60bbaab..5a88562 100644 --- a/oshconnect/datamodels/geometry.py +++ b/oshconnect/datamodels/geometry.py @@ -1,10 +1,11 @@ from pydantic import BaseModel, Field from oshconnect.csapi4py.constants import GeometryTypes +from shapely import Geometry # TODO: Add specific validations for each type -# TODO: update to either use third party Geometry definitions or create a more robust definition +# TODO: determine if serializing 'shapely' objects gives valid JSON structures from our own serialization class Geometry(BaseModel): """ A class to represent the geometry of a feature diff --git a/oshconnect/datasource.py b/oshconnect/datasource.py index d63c1fb..ccd0f9b 100644 --- a/oshconnect/datasource.py +++ b/oshconnect/datasource.py @@ -21,7 +21,7 @@ from oshconnect.datamodels.swe_components import DataRecordSchema from .core_datamodels import DatastreamResource, SystemResource, TimePeriod -from .timemanagement import TemporalModes +from .timemanagement import TemporalModes, Synchronizer # from swecommondm.component_implementations import DataRecord @@ -48,6 +48,7 @@ class DataStream: _playback_websocket: websockets.WebSocketClientProtocol = None _extra_headers: dict = None _result_schema: DataRecordSchema = None + _synchronizer: Synchronizer = None def __init__(self, name: str, datastream: DatastreamResource, parent_system: SystemResource): diff --git a/oshconnect/oshconnectapi.py b/oshconnect/oshconnectapi.py index 1ac8fde..0a24efe 100644 --- a/oshconnect/oshconnectapi.py +++ b/oshconnect/oshconnectapi.py @@ -8,7 +8,6 @@ import shelve from oshconnect.csapi4py.core.default_api_helpers import APIHelper - from .core_datamodels import DatastreamResource, TimePeriod from .datasource import DataStream, DataStreamHandler, MessageWrapper from .datastore import DataStore @@ -26,7 +25,7 @@ class OSHConnect: _systems: list[System] = [] _cs_api_builder: APIHelper = None _datasource_handler: DataStreamHandler = None - _datafeeds: list[DataStream] = [] + _datastreams: list[DataStream] = [] _datataskers: list[DataStore] = [] _datagroups: list = [] _tasks: list = [] @@ -150,7 +149,7 @@ def discover_datastreams(self): DataStream(name=ds.name, datastream=ds, parent_system=system) for ds in res_datastreams] - self._datafeeds.extend(new_datasource) + self._datastreams.extend(new_datasource) list(map(self._datasource_handler.add_datasource, new_datasource)) def discover_systems(self, nodes: list[str] = None): @@ -199,7 +198,7 @@ def get_message_list(self) -> list[MessageWrapper]: """ return self._datasource_handler.get_messages() - def insert_system(self, system: System, target_node: Node): + def _insert_system(self, system: System, target_node: Node): """ Create a system on the target node. :param system: System object @@ -227,7 +226,7 @@ def insert_datastream(self, datastream: DatastreamResource, system: str | System sys_obj.add_insert_datastream(datastream) - self._datafeeds.append(datastream) + self._datastreams.append(datastream) def find_system(self, system_id: str) -> System | None: """ @@ -270,3 +269,7 @@ def create_and_insert_system(self, system_opts: dict, target_node: Node): def remove_system(self, system_id: str): pass + + # DataStream Helpers + def get_datastreams(self) -> list[DataStream]: + return self._datastreams diff --git a/oshconnect/timemanagement.py b/oshconnect/timemanagement.py index c7f9bb2..b7bdd8b 100644 --- a/oshconnect/timemanagement.py +++ b/oshconnect/timemanagement.py @@ -299,12 +299,6 @@ def does_timeperiod_overlap(self, checked_timeperiod: TimePeriod) -> bool: return True -class Utilities: - pass - # @staticmethod - # def parse_systems_result(result) -> System: - - class TimeManagement: time_range: TimePeriod time_controller: TimeController @@ -329,6 +323,7 @@ class TimeController: _timeline_begin: TimeInstant _timeline_end: TimeInstant _current_time: TimeInstant + _synchronizer: Synchronizer def __new__(cls, *args, **kwargs): if cls._instance is None: @@ -420,6 +415,10 @@ def _compute_time_range(self): class Synchronizer: _buffer: any + _buffering_time: int def synchronize(self, systems: list): pass + + def check_in_sync(self): + pass diff --git a/pyproject.toml b/pyproject.toml index b14cd73..1501b3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oshconnect" -version = "0.2.4-1" +version = "0.3.0a1" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." readme = "README.md" authors = [ @@ -9,17 +9,17 @@ authors = [ requires-python = "<4.0,>=3.12" dependencies = [ "paho-mqtt>=2.1.0", - "pydantic<3.0.0,>=2.7.4", - "shapely<3.0.0,>=2.0.4", - "websockets<13.0,>=12.0", + "pydantic>=2.7.4,<3.0.0", + "shapely>=2.0.4,<3.0.0", + "websockets>=12.0,<16.0", ] [dependency-groups] dev = [ - "pytest<9.0.0,>=8.2.2", - "sphinx<8.0.0,>=7.3.7", - "flake8<8.0.0,>=7.1.0", - "sphinx-rtd-theme<3.0.0,>=2.0.0", + "flake8>=7.2.0", + "pytest>=8.3.5", + "sphinx>=7.4.7", + "sphinx-rtd-theme>=2.0.0", ] [tool.setuptools] diff --git a/tests/test_api_update.py b/tests/test_api_update.py new file mode 100644 index 0000000..3eff373 --- /dev/null +++ b/tests/test_api_update.py @@ -0,0 +1,6 @@ +import pytest + +from oshconnect import OSHConnect, System, Node, Datastream + +node = Node() +app = OSHConnect() \ No newline at end of file diff --git a/tests/test_oshconnect.py b/tests/test_oshconnect.py index 29013e4..7457fe7 100644 --- a/tests/test_oshconnect.py +++ b/tests/test_oshconnect.py @@ -68,7 +68,7 @@ def test_oshconnect_find_datastreams(self): app.discover_systems() app.discover_datastreams() - assert len(app._datafeeds) > 0 + assert len(app._datastreams) > 0 async def test_obs_ws_stream(self): ds_url = ("ws://localhost:8585/sensorhub/api/datastreams/e07n5sbjqvalm/observations?f=application%2Fjson" diff --git a/uv.lock b/uv.lock index 4409817..ed74425 100644 --- a/uv.lock +++ b/uv.lock @@ -231,7 +231,7 @@ wheels = [ [[package]] name = "oshconnect" -version = "0.2.4.post1" +version = "0.3.0a1" source = { virtual = "." } dependencies = [ { name = "paho-mqtt" }, @@ -253,15 +253,15 @@ requires-dist = [ { name = "paho-mqtt", specifier = ">=2.1.0" }, { name = "pydantic", specifier = ">=2.7.4,<3.0.0" }, { name = "shapely", specifier = ">=2.0.4,<3.0.0" }, - { name = "websockets", specifier = ">=12.0,<13.0" }, + { name = "websockets", specifier = ">=12.0,<16.0" }, ] [package.metadata.requires-dev] dev = [ - { name = "flake8", specifier = ">=7.1.0,<8.0.0" }, - { name = "pytest", specifier = ">=8.2.2,<9.0.0" }, - { name = "sphinx", specifier = ">=7.3.7,<8.0.0" }, - { name = "sphinx-rtd-theme", specifier = ">=2.0.0,<3.0.0" }, + { name = "flake8", specifier = ">=7.2.0" }, + { name = "pytest", specifier = ">=8.3.5" }, + { name = "sphinx", specifier = ">=7.4.7" }, + { name = "sphinx-rtd-theme", specifier = ">=2.0.0" }, ] [[package]] From 8b324fb6ba0daca142f76d3bdd68f8b38296d887 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Thu, 18 Sep 2025 00:09:16 -0500 Subject: [PATCH 16/31] fix simple errors from flake8 --- oshconnect/__init__.py | 4 ++-- oshconnect/csapi4py/part_2/__init__.py | 2 +- oshconnect/datamodels/geometry.py | 3 +-- tox.ini | 3 ++- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/oshconnect/__init__.py b/oshconnect/__init__.py index 256a5e3..b1e50e6 100644 --- a/oshconnect/__init__.py +++ b/oshconnect/__init__.py @@ -5,5 +5,5 @@ # Contact Email: ian@botts-inc.com # ============================================================================== -from .oshconnectapi import OSHConnect, Node -from .osh_connect_datamodels import System, Node, Datastream, Observation, ControlChannel \ No newline at end of file +from .oshconnectapi import OSHConnect +from .osh_connect_datamodels import System, Node, Datastream, Observation, ControlChannel diff --git a/oshconnect/csapi4py/part_2/__init__.py b/oshconnect/csapi4py/part_2/__init__.py index 7ddbce4..419373c 100644 --- a/oshconnect/csapi4py/part_2/__init__.py +++ b/oshconnect/csapi4py/part_2/__init__.py @@ -3,4 +3,4 @@ # import datastreams # import observations # import system_events -# import system_history \ No newline at end of file +# import system_history diff --git a/oshconnect/datamodels/geometry.py b/oshconnect/datamodels/geometry.py index 5a88562..ced5988 100644 --- a/oshconnect/datamodels/geometry.py +++ b/oshconnect/datamodels/geometry.py @@ -1,7 +1,6 @@ from pydantic import BaseModel, Field from oshconnect.csapi4py.constants import GeometryTypes -from shapely import Geometry # TODO: Add specific validations for each type @@ -12,4 +11,4 @@ class Geometry(BaseModel): """ type: GeometryTypes = Field(...) coordinates: list - bbox: list = None \ No newline at end of file + bbox: list = None diff --git a/tox.ini b/tox.ini index 0d03a58..9fd34f9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,2 +1,3 @@ [flake8] -extend-ignore = E501 \ No newline at end of file +extend-ignore = E501 +exclude = .venv,tests \ No newline at end of file From fd103a7a10e7a1d6f8ef3d8f6da61495812b9362 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Thu, 18 Sep 2025 01:06:58 -0500 Subject: [PATCH 17/31] add publish workflow --- .github/workflows/publish.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..a1e3aaa --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,27 @@ +name: publish.yml +on: + push: + tags: + # publishes any tag starting with 'v' as in 'v.1.0' + - v* + +jobs: + run: + runs-on: ubuntu-latest + environment: + name: pypi + permissions: + id-token: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 + - name: Install Python 3.13 + run: uv python install 3.13 + - name: Build + run: uv build + # Need to add a test that verifies the builds + - name: Publish + run: uv publish From 5442e552dc3dddff21c8b8d625dff7af93d2d783 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Thu, 18 Sep 2025 01:32:13 -0500 Subject: [PATCH 18/31] update workflows to newer versions --- .github/workflows/docs_pages.yaml | 43 ++++++++++++++++--------------- .github/workflows/linting.yaml | 16 +++++++----- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/.github/workflows/docs_pages.yaml b/.github/workflows/docs_pages.yaml index f5fc5d6..888ef29 100644 --- a/.github/workflows/docs_pages.yaml +++ b/.github/workflows/docs_pages.yaml @@ -1,5 +1,5 @@ name: Docs2Pages -on: [push, pull_request, workflow_dispatch] +on: [ push, pull_request, workflow_dispatch ] permissions: contents: write @@ -7,26 +7,27 @@ jobs: build-docs: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' + - name: Checkout + uses: actions/checkout@v5 - - name: Install dependencies - run: | - pip install uv - uv sync --all-extras + - name: Install uv + uses: astral-sh/setup-uv@v6 - - name: Sphinx build - run: | - uv run sphinx-build -b html docs/source docs/build/html + - name: Install Python 3.13 + run: uv python install 3.13 - - name: Deploy documentation - uses: peaceiris/actions-gh-pages@v4 - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - publish_branch: gh-pages - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/build/html - force_orphan: true \ No newline at end of file + - name: Install dependencies + run: uv sync --all-extras + + - name: Sphinx build + run: | + uv run sphinx-build -b html docs/source docs/build/html + + - name: Deploy documentation + uses: peaceiris/actions-gh-pages@v4 + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + publish_branch: gh-pages + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/build/html + force_orphan: true \ No newline at end of file diff --git a/.github/workflows/linting.yaml b/.github/workflows/linting.yaml index 3bb0167..9a13d9c 100644 --- a/.github/workflows/linting.yaml +++ b/.github/workflows/linting.yaml @@ -4,15 +4,17 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' + - name: Checkout + uses: actions/checkout@v5 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + + - name: Install Python 3.13 + run: uv python install 3.13 - name: Install dependencies - run: | - pip install uv - uv sync --all-extras + run: uv sync --all-extras - name: Lint run: | From 28549ad6b62ced85176a16c5fa7ce2c5c4ed6d97 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Thu, 18 Sep 2025 01:43:03 -0500 Subject: [PATCH 19/31] update tox.ini to ignore 401 errors in init files --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 9fd34f9..86d7101 100644 --- a/tox.ini +++ b/tox.ini @@ -1,3 +1,4 @@ [flake8] extend-ignore = E501 -exclude = .venv,tests \ No newline at end of file +exclude = .venv,tests +per-file-ignores = __init__.py:F401 \ No newline at end of file From 266abfef14d6dfaa441726c887ea1c229eb130b4 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Thu, 18 Sep 2025 17:35:34 -0500 Subject: [PATCH 20/31] corrected environment name in publish.yml --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a1e3aaa..6e2ee91 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,14 +2,14 @@ name: publish.yml on: push: tags: - # publishes any tag starting with 'v' as in 'v.1.0' + # publishes any tag starting with 'v' as in 'v1.0' - v* jobs: run: runs-on: ubuntu-latest environment: - name: pypi + name: publish permissions: id-token: write contents: read From e95e7880fb95b59a6e64a375e4f69f771eb7bc5e Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Thu, 18 Sep 2025 17:40:32 -0500 Subject: [PATCH 21/31] bump version for prerelease --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1501b3b..edcb462 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oshconnect" -version = "0.3.0a1" +version = "0.3.0a2" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." readme = "README.md" authors = [ From afcbc0d1ba0c79e02426496e204dc32e35c59ee9 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Fri, 19 Sep 2025 00:04:23 -0500 Subject: [PATCH 22/31] add missing sub-packages to pyproject.toml --- pyproject.toml | 4 ++-- uv.lock | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index edcb462..f2ea75f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oshconnect" -version = "0.3.0a2" +version = "0.3.0a3" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." readme = "README.md" authors = [ @@ -23,4 +23,4 @@ dev = [ ] [tool.setuptools] -packages = ["oshconnect"] +packages = ["oshconnect", "oshconnect.csapi4py", "oshconnect.datamodels"] diff --git a/uv.lock b/uv.lock index ed74425..c3056e9 100644 --- a/uv.lock +++ b/uv.lock @@ -231,7 +231,7 @@ wheels = [ [[package]] name = "oshconnect" -version = "0.3.0a1" +version = "0.3.0a3" source = { virtual = "." } dependencies = [ { name = "paho-mqtt" }, From 418df707419d5172b14055e5b10d572f6b9fa300 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Fri, 19 Sep 2025 01:32:06 -0500 Subject: [PATCH 23/31] postfix for 0.3.0a3 with more missing files --- pyproject.toml | 5 +++-- {oshconnect => src/oshconnect}/__init__.py | 0 {oshconnect => src/oshconnect}/control.py | 8 ++++---- {oshconnect => src/oshconnect}/core_datamodels.py | 8 ++++---- {oshconnect => src/oshconnect}/csapi4py/__init__.py | 0 .../oshconnect}/csapi4py/comm/__init__.py | 0 {oshconnect => src/oshconnect}/csapi4py/comm/mqtt.py | 0 {oshconnect => src/oshconnect}/csapi4py/con_sys_api.py | 4 ++-- {oshconnect => src/oshconnect}/csapi4py/constants.py | 0 .../oshconnect}/csapi4py/core/__init__.py | 0 .../oshconnect}/csapi4py/core/default_api_helpers.py | 4 ++-- {oshconnect => src/oshconnect}/csapi4py/endpoints.py | 2 +- .../oshconnect}/csapi4py/part_1/__init__.py | 0 .../oshconnect}/csapi4py/part_1/capabilities.py | 4 ++-- .../oshconnect}/csapi4py/part_1/collections_ep.py | 4 ++-- .../oshconnect}/csapi4py/part_1/deployments.py | 4 ++-- .../oshconnect}/csapi4py/part_1/procedures.py | 4 ++-- .../oshconnect}/csapi4py/part_1/properties.py | 4 ++-- .../oshconnect}/csapi4py/part_1/sampling_features.py | 4 ++-- .../oshconnect}/csapi4py/part_1/systems.py | 6 +++--- .../oshconnect}/csapi4py/part_2/__init__.py | 0 .../oshconnect}/csapi4py/part_2/commands.py | 4 ++-- .../oshconnect}/csapi4py/part_2/control_channels.py | 4 ++-- .../oshconnect}/csapi4py/part_2/datastreams.py | 4 ++-- .../oshconnect}/csapi4py/part_2/observations.py | 4 ++-- .../oshconnect}/csapi4py/part_2/system_events.py | 4 ++-- .../oshconnect}/csapi4py/part_2/system_history.py | 4 ++-- {oshconnect => src/oshconnect}/csapi4py/querymodel.py | 0 .../oshconnect}/csapi4py/request_bodies.py | 8 ++++---- .../oshconnect}/csapi4py/request_wrappers.py | 0 .../oshconnect}/csapi4py/sensor_ml/__init__.py | 0 .../oshconnect}/csapi4py/sensor_ml/sml.py | 0 .../oshconnect}/csapi4py/utilities/__init__.py | 0 .../oshconnect}/csapi4py/utilities/model_utils.py | 0 {oshconnect => src/oshconnect}/datamodels/__init__.py | 0 {oshconnect => src/oshconnect}/datamodels/api_utils.py | 0 {oshconnect => src/oshconnect}/datamodels/commands.py | 0 .../oshconnect}/datamodels/control_streams.py | 4 ++-- .../oshconnect}/datamodels/datastreams.py | 6 +++--- {oshconnect => src/oshconnect}/datamodels/encoding.py | 0 {oshconnect => src/oshconnect}/datamodels/geometry.py | 2 +- .../oshconnect}/datamodels/network_properties.py | 0 .../oshconnect}/datamodels/observations.py | 2 +- .../oshconnect}/datamodels/swe_components.py | 6 +++--- .../datamodels/system_events_and_history.py | 4 ++-- {oshconnect => src/oshconnect}/datasource.py | 6 +++--- {oshconnect => src/oshconnect}/datastore.py | 0 .../oshconnect}/osh_connect_datamodels.py | 10 +++++----- {oshconnect => src/oshconnect}/oshconnectapi.py | 2 +- {oshconnect => src/oshconnect}/styling.py | 0 {oshconnect => src/oshconnect}/timemanagement.py | 0 tests/test_api_update.py | 4 +--- tests/test_oshconnect.py | 8 ++++---- uv.lock | 2 +- 54 files changed, 74 insertions(+), 75 deletions(-) rename {oshconnect => src/oshconnect}/__init__.py (100%) rename {oshconnect => src/oshconnect}/control.py (88%) rename {oshconnect => src/oshconnect}/core_datamodels.py (95%) rename {oshconnect => src/oshconnect}/csapi4py/__init__.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/comm/__init__.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/comm/mqtt.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/con_sys_api.py (95%) rename {oshconnect => src/oshconnect}/csapi4py/constants.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/core/__init__.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/core/default_api_helpers.py (98%) rename {oshconnect => src/oshconnect}/csapi4py/endpoints.py (98%) rename {oshconnect => src/oshconnect}/csapi4py/part_1/__init__.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/part_1/capabilities.py (86%) rename {oshconnect => src/oshconnect}/csapi4py/part_1/collections_ep.py (94%) rename {oshconnect => src/oshconnect}/csapi4py/part_1/deployments.py (98%) rename {oshconnect => src/oshconnect}/csapi4py/part_1/procedures.py (96%) rename {oshconnect => src/oshconnect}/csapi4py/part_1/properties.py (95%) rename {oshconnect => src/oshconnect}/csapi4py/part_1/sampling_features.py (97%) rename {oshconnect => src/oshconnect}/csapi4py/part_1/systems.py (97%) rename {oshconnect => src/oshconnect}/csapi4py/part_2/__init__.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/part_2/commands.py (98%) rename {oshconnect => src/oshconnect}/csapi4py/part_2/control_channels.py (97%) rename {oshconnect => src/oshconnect}/csapi4py/part_2/datastreams.py (97%) rename {oshconnect => src/oshconnect}/csapi4py/part_2/observations.py (97%) rename {oshconnect => src/oshconnect}/csapi4py/part_2/system_events.py (97%) rename {oshconnect => src/oshconnect}/csapi4py/part_2/system_history.py (96%) rename {oshconnect => src/oshconnect}/csapi4py/querymodel.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/request_bodies.py (93%) rename {oshconnect => src/oshconnect}/csapi4py/request_wrappers.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/sensor_ml/__init__.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/sensor_ml/sml.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/utilities/__init__.py (100%) rename {oshconnect => src/oshconnect}/csapi4py/utilities/model_utils.py (100%) rename {oshconnect => src/oshconnect}/datamodels/__init__.py (100%) rename {oshconnect => src/oshconnect}/datamodels/api_utils.py (100%) rename {oshconnect => src/oshconnect}/datamodels/commands.py (100%) rename {oshconnect => src/oshconnect}/datamodels/control_streams.py (92%) rename {oshconnect => src/oshconnect}/datamodels/datastreams.py (80%) rename {oshconnect => src/oshconnect}/datamodels/encoding.py (100%) rename {oshconnect => src/oshconnect}/datamodels/geometry.py (86%) rename {oshconnect => src/oshconnect}/datamodels/network_properties.py (100%) rename {oshconnect => src/oshconnect}/datamodels/observations.py (92%) rename {oshconnect => src/oshconnect}/datamodels/swe_components.py (97%) rename {oshconnect => src/oshconnect}/datamodels/system_events_and_history.py (91%) rename {oshconnect => src/oshconnect}/datasource.py (98%) rename {oshconnect => src/oshconnect}/datastore.py (100%) rename {oshconnect => src/oshconnect}/osh_connect_datamodels.py (97%) rename {oshconnect => src/oshconnect}/oshconnectapi.py (99%) rename {oshconnect => src/oshconnect}/styling.py (100%) rename {oshconnect => src/oshconnect}/timemanagement.py (100%) diff --git a/pyproject.toml b/pyproject.toml index f2ea75f..44b3e04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oshconnect" -version = "0.3.0a3" +version = "0.3.0a3.post1" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." readme = "README.md" authors = [ @@ -23,4 +23,5 @@ dev = [ ] [tool.setuptools] -packages = ["oshconnect", "oshconnect.csapi4py", "oshconnect.datamodels"] +#packages = ["oshconnect", "oshconnect.csapi4py", "oshconnect.datamodels", "oshconnect.csapi4py.comm"] +packages = {find = { where = ["src/"]}} diff --git a/oshconnect/__init__.py b/src/oshconnect/__init__.py similarity index 100% rename from oshconnect/__init__.py rename to src/oshconnect/__init__.py diff --git a/oshconnect/control.py b/src/oshconnect/control.py similarity index 88% rename from oshconnect/control.py rename to src/oshconnect/control.py index 01f2c45..3686472 100644 --- a/oshconnect/control.py +++ b/src/oshconnect/control.py @@ -5,11 +5,11 @@ # Contact Email: ian@botts-inc.com # ============================================================================== import websockets -from oshconnect.csapi4py.comm.mqtt import MQTTCommClient -from oshconnect.datamodels.commands import CommandJSON -from oshconnect.datamodels.control_streams import ControlStreamJSONSchema +from src.oshconnect.csapi4py.comm.mqtt import MQTTCommClient +from src.oshconnect.datamodels.commands import CommandJSON +from src.oshconnect.datamodels.control_streams import ControlStreamJSONSchema -from oshconnect.osh_connect_datamodels import System +from src.oshconnect.osh_connect_datamodels import System class ControlSchema: diff --git a/oshconnect/core_datamodels.py b/src/oshconnect/core_datamodels.py similarity index 95% rename from oshconnect/core_datamodels.py rename to src/oshconnect/core_datamodels.py index 83fced5..eebcee2 100644 --- a/oshconnect/core_datamodels.py +++ b/src/oshconnect/core_datamodels.py @@ -8,13 +8,13 @@ from typing import List -from oshconnect.datamodels.geometry import Geometry -from oshconnect.datamodels.datastreams import DatastreamSchema -from oshconnect.datamodels.api_utils import Link +from src.oshconnect.datamodels.geometry import Geometry +from src.oshconnect.datamodels.datastreams import DatastreamSchema +from src.oshconnect.datamodels.api_utils import Link from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny from shapely import Point -from oshconnect.timemanagement import DateTimeSchema, TimePeriod +from src.oshconnect.timemanagement import DateTimeSchema, TimePeriod class BoundingBox(BaseModel): diff --git a/oshconnect/csapi4py/__init__.py b/src/oshconnect/csapi4py/__init__.py similarity index 100% rename from oshconnect/csapi4py/__init__.py rename to src/oshconnect/csapi4py/__init__.py diff --git a/oshconnect/csapi4py/comm/__init__.py b/src/oshconnect/csapi4py/comm/__init__.py similarity index 100% rename from oshconnect/csapi4py/comm/__init__.py rename to src/oshconnect/csapi4py/comm/__init__.py diff --git a/oshconnect/csapi4py/comm/mqtt.py b/src/oshconnect/csapi4py/comm/mqtt.py similarity index 100% rename from oshconnect/csapi4py/comm/mqtt.py rename to src/oshconnect/csapi4py/comm/mqtt.py diff --git a/oshconnect/csapi4py/con_sys_api.py b/src/oshconnect/csapi4py/con_sys_api.py similarity index 95% rename from oshconnect/csapi4py/con_sys_api.py rename to src/oshconnect/csapi4py/con_sys_api.py index 120ba32..18237b5 100644 --- a/oshconnect/csapi4py/con_sys_api.py +++ b/src/oshconnect/csapi4py/con_sys_api.py @@ -2,8 +2,8 @@ from pydantic import BaseModel, HttpUrl, Field -from oshconnect.csapi4py.endpoints import Endpoint -from oshconnect.csapi4py.request_wrappers import post_request, put_request, get_request, delete_request +from src.oshconnect.csapi4py.endpoints import Endpoint +from src.oshconnect.csapi4py.request_wrappers import post_request, put_request, get_request, delete_request class ConnectedSystemAPIRequest(BaseModel): diff --git a/oshconnect/csapi4py/constants.py b/src/oshconnect/csapi4py/constants.py similarity index 100% rename from oshconnect/csapi4py/constants.py rename to src/oshconnect/csapi4py/constants.py diff --git a/oshconnect/csapi4py/core/__init__.py b/src/oshconnect/csapi4py/core/__init__.py similarity index 100% rename from oshconnect/csapi4py/core/__init__.py rename to src/oshconnect/csapi4py/core/__init__.py diff --git a/oshconnect/csapi4py/core/default_api_helpers.py b/src/oshconnect/csapi4py/core/default_api_helpers.py similarity index 98% rename from oshconnect/csapi4py/core/default_api_helpers.py rename to src/oshconnect/csapi4py/core/default_api_helpers.py index d034876..73ccdb8 100644 --- a/oshconnect/csapi4py/core/default_api_helpers.py +++ b/src/oshconnect/csapi4py/core/default_api_helpers.py @@ -5,8 +5,8 @@ from pydantic import BaseModel, Field -from oshconnect.csapi4py.con_sys_api import ConnectedSystemAPIRequest -from oshconnect.csapi4py.constants import APIResourceTypes, EncodingSchema, APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemAPIRequest +from src.oshconnect.csapi4py.constants import APIResourceTypes, EncodingSchema, APITerms def determine_parent_type(res_type: APIResourceTypes): diff --git a/oshconnect/csapi4py/endpoints.py b/src/oshconnect/csapi4py/endpoints.py similarity index 98% rename from oshconnect/csapi4py/endpoints.py rename to src/oshconnect/csapi4py/endpoints.py index 5943fe7..bf26470 100644 --- a/oshconnect/csapi4py/endpoints.py +++ b/src/oshconnect/csapi4py/endpoints.py @@ -4,7 +4,7 @@ # import websockets from pydantic import BaseModel, Field -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.constants import APITerms class Endpoint(BaseModel): diff --git a/oshconnect/csapi4py/part_1/__init__.py b/src/oshconnect/csapi4py/part_1/__init__.py similarity index 100% rename from oshconnect/csapi4py/part_1/__init__.py rename to src/oshconnect/csapi4py/part_1/__init__.py diff --git a/oshconnect/csapi4py/part_1/capabilities.py b/src/oshconnect/csapi4py/part_1/capabilities.py similarity index 86% rename from oshconnect/csapi4py/part_1/capabilities.py rename to src/oshconnect/csapi4py/part_1/capabilities.py index e094aa5..073c523 100644 --- a/oshconnect/csapi4py/part_1/capabilities.py +++ b/src/oshconnect/csapi4py/part_1/capabilities.py @@ -1,7 +1,7 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def get_landing_page(server_addr: HttpUrl, api_root: str = APITerms.API.value): diff --git a/oshconnect/csapi4py/part_1/collections_ep.py b/src/oshconnect/csapi4py/part_1/collections_ep.py similarity index 94% rename from oshconnect/csapi4py/part_1/collections_ep.py rename to src/oshconnect/csapi4py/part_1/collections_ep.py index 0c8dbfc..a391f2f 100644 --- a/oshconnect/csapi4py/part_1/collections_ep.py +++ b/src/oshconnect/csapi4py/part_1/collections_ep.py @@ -1,7 +1,7 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_all_collections(server_addr: HttpUrl, api_root: str = APITerms.API.value): diff --git a/oshconnect/csapi4py/part_1/deployments.py b/src/oshconnect/csapi4py/part_1/deployments.py similarity index 98% rename from oshconnect/csapi4py/part_1/deployments.py rename to src/oshconnect/csapi4py/part_1/deployments.py index 0f8fe60..0ed7802 100644 --- a/oshconnect/csapi4py/part_1/deployments.py +++ b/src/oshconnect/csapi4py/part_1/deployments.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_all_deployments(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/oshconnect/csapi4py/part_1/procedures.py b/src/oshconnect/csapi4py/part_1/procedures.py similarity index 96% rename from oshconnect/csapi4py/part_1/procedures.py rename to src/oshconnect/csapi4py/part_1/procedures.py index 41e58a5..d5df4b2 100644 --- a/oshconnect/csapi4py/part_1/procedures.py +++ b/src/oshconnect/csapi4py/part_1/procedures.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_all_procedures(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/oshconnect/csapi4py/part_1/properties.py b/src/oshconnect/csapi4py/part_1/properties.py similarity index 95% rename from oshconnect/csapi4py/part_1/properties.py rename to src/oshconnect/csapi4py/part_1/properties.py index 8347424..df5f21f 100644 --- a/oshconnect/csapi4py/part_1/properties.py +++ b/src/oshconnect/csapi4py/part_1/properties.py @@ -1,7 +1,7 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_all_properties(server_addr: HttpUrl, api_root: str = APITerms.API.value): diff --git a/oshconnect/csapi4py/part_1/sampling_features.py b/src/oshconnect/csapi4py/part_1/sampling_features.py similarity index 97% rename from oshconnect/csapi4py/part_1/sampling_features.py rename to src/oshconnect/csapi4py/part_1/sampling_features.py index 5ae3d5c..59dc523 100644 --- a/oshconnect/csapi4py/part_1/sampling_features.py +++ b/src/oshconnect/csapi4py/part_1/sampling_features.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_all_sampling_features(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers=None): diff --git a/oshconnect/csapi4py/part_1/systems.py b/src/oshconnect/csapi4py/part_1/systems.py similarity index 97% rename from oshconnect/csapi4py/part_1/systems.py rename to src/oshconnect/csapi4py/part_1/systems.py index d746b24..876a093 100644 --- a/oshconnect/csapi4py/part_1/systems.py +++ b/src/oshconnect/csapi4py/part_1/systems.py @@ -3,9 +3,9 @@ import requests from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms -from oshconnect.csapi4py.request_wrappers import post_request +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.request_wrappers import post_request def list_all_systems(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/oshconnect/csapi4py/part_2/__init__.py b/src/oshconnect/csapi4py/part_2/__init__.py similarity index 100% rename from oshconnect/csapi4py/part_2/__init__.py rename to src/oshconnect/csapi4py/part_2/__init__.py diff --git a/oshconnect/csapi4py/part_2/commands.py b/src/oshconnect/csapi4py/part_2/commands.py similarity index 98% rename from oshconnect/csapi4py/part_2/commands.py rename to src/oshconnect/csapi4py/part_2/commands.py index ac26dbf..4874b8d 100644 --- a/oshconnect/csapi4py/part_2/commands.py +++ b/src/oshconnect/csapi4py/part_2/commands.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_all_commands(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/oshconnect/csapi4py/part_2/control_channels.py b/src/oshconnect/csapi4py/part_2/control_channels.py similarity index 97% rename from oshconnect/csapi4py/part_2/control_channels.py rename to src/oshconnect/csapi4py/part_2/control_channels.py index 3b4d995..3c9e487 100644 --- a/oshconnect/csapi4py/part_2/control_channels.py +++ b/src/oshconnect/csapi4py/part_2/control_channels.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_all_control_streams(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/oshconnect/csapi4py/part_2/datastreams.py b/src/oshconnect/csapi4py/part_2/datastreams.py similarity index 97% rename from oshconnect/csapi4py/part_2/datastreams.py rename to src/oshconnect/csapi4py/part_2/datastreams.py index 3a21b3a..0917a39 100644 --- a/oshconnect/csapi4py/part_2/datastreams.py +++ b/src/oshconnect/csapi4py/part_2/datastreams.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_all_datastreams(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/oshconnect/csapi4py/part_2/observations.py b/src/oshconnect/csapi4py/part_2/observations.py similarity index 97% rename from oshconnect/csapi4py/part_2/observations.py rename to src/oshconnect/csapi4py/part_2/observations.py index 788407b..2c8e742 100644 --- a/oshconnect/csapi4py/part_2/observations.py +++ b/src/oshconnect/csapi4py/part_2/observations.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_all_observations(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers=None): diff --git a/oshconnect/csapi4py/part_2/system_events.py b/src/oshconnect/csapi4py/part_2/system_events.py similarity index 97% rename from oshconnect/csapi4py/part_2/system_events.py rename to src/oshconnect/csapi4py/part_2/system_events.py index afbbb47..3ee288e 100644 --- a/oshconnect/csapi4py/part_2/system_events.py +++ b/src/oshconnect/csapi4py/part_2/system_events.py @@ -1,7 +1,7 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_system_events(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/oshconnect/csapi4py/part_2/system_history.py b/src/oshconnect/csapi4py/part_2/system_history.py similarity index 96% rename from oshconnect/csapi4py/part_2/system_history.py rename to src/oshconnect/csapi4py/part_2/system_history.py index dc73c5c..96fe909 100644 --- a/oshconnect/csapi4py/part_2/system_history.py +++ b/src/oshconnect/csapi4py/part_2/system_history.py @@ -1,7 +1,7 @@ from pydantic import HttpUrl -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms +from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from src.oshconnect.csapi4py.constants import APITerms def list_system_history(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/oshconnect/csapi4py/querymodel.py b/src/oshconnect/csapi4py/querymodel.py similarity index 100% rename from oshconnect/csapi4py/querymodel.py rename to src/oshconnect/csapi4py/querymodel.py diff --git a/oshconnect/csapi4py/request_bodies.py b/src/oshconnect/csapi4py/request_bodies.py similarity index 93% rename from oshconnect/csapi4py/request_bodies.py rename to src/oshconnect/csapi4py/request_bodies.py index c9bbb93..c0f3daf 100644 --- a/oshconnect/csapi4py/request_bodies.py +++ b/src/oshconnect/csapi4py/request_bodies.py @@ -2,10 +2,10 @@ from pydantic import BaseModel, HttpUrl, Field, model_serializer, RootModel, SerializeAsAny -from oshconnect.csapi4py.constants import DatastreamResultTypes -from oshconnect.datamodels.datastreams import DatastreamSchema -from oshconnect.datamodels.geometry import Geometry -from oshconnect.csapi4py.sensor_ml.sml import TypeOf +from src.oshconnect.csapi4py.constants import DatastreamResultTypes +from src.oshconnect.datamodels.datastreams import DatastreamSchema +from src.oshconnect.datamodels.geometry import Geometry +from src.oshconnect.csapi4py.sensor_ml.sml import TypeOf # TODO: Consider some sort of Abstract Base Class for all valid request bodies to inherit from to reduce the complexity diff --git a/oshconnect/csapi4py/request_wrappers.py b/src/oshconnect/csapi4py/request_wrappers.py similarity index 100% rename from oshconnect/csapi4py/request_wrappers.py rename to src/oshconnect/csapi4py/request_wrappers.py diff --git a/oshconnect/csapi4py/sensor_ml/__init__.py b/src/oshconnect/csapi4py/sensor_ml/__init__.py similarity index 100% rename from oshconnect/csapi4py/sensor_ml/__init__.py rename to src/oshconnect/csapi4py/sensor_ml/__init__.py diff --git a/oshconnect/csapi4py/sensor_ml/sml.py b/src/oshconnect/csapi4py/sensor_ml/sml.py similarity index 100% rename from oshconnect/csapi4py/sensor_ml/sml.py rename to src/oshconnect/csapi4py/sensor_ml/sml.py diff --git a/oshconnect/csapi4py/utilities/__init__.py b/src/oshconnect/csapi4py/utilities/__init__.py similarity index 100% rename from oshconnect/csapi4py/utilities/__init__.py rename to src/oshconnect/csapi4py/utilities/__init__.py diff --git a/oshconnect/csapi4py/utilities/model_utils.py b/src/oshconnect/csapi4py/utilities/model_utils.py similarity index 100% rename from oshconnect/csapi4py/utilities/model_utils.py rename to src/oshconnect/csapi4py/utilities/model_utils.py diff --git a/oshconnect/datamodels/__init__.py b/src/oshconnect/datamodels/__init__.py similarity index 100% rename from oshconnect/datamodels/__init__.py rename to src/oshconnect/datamodels/__init__.py diff --git a/oshconnect/datamodels/api_utils.py b/src/oshconnect/datamodels/api_utils.py similarity index 100% rename from oshconnect/datamodels/api_utils.py rename to src/oshconnect/datamodels/api_utils.py diff --git a/oshconnect/datamodels/commands.py b/src/oshconnect/datamodels/commands.py similarity index 100% rename from oshconnect/datamodels/commands.py rename to src/oshconnect/datamodels/commands.py diff --git a/oshconnect/datamodels/control_streams.py b/src/oshconnect/datamodels/control_streams.py similarity index 92% rename from oshconnect/datamodels/control_streams.py rename to src/oshconnect/datamodels/control_streams.py index 8ee8eb5..b11dac9 100644 --- a/oshconnect/datamodels/control_streams.py +++ b/src/oshconnect/datamodels/control_streams.py @@ -4,8 +4,8 @@ from pydantic import BaseModel, Field, SerializeAsAny -from oshconnect.datamodels.encoding import Encoding -from oshconnect.datamodels.swe_components import AnyComponentSchema +from src.oshconnect.datamodels.encoding import Encoding +from src.oshconnect.datamodels.swe_components import AnyComponentSchema class ControlStreamJSONSchema(BaseModel): diff --git a/oshconnect/datamodels/datastreams.py b/src/oshconnect/datamodels/datastreams.py similarity index 80% rename from oshconnect/datamodels/datastreams.py rename to src/oshconnect/datamodels/datastreams.py index 11d3986..5158d4e 100644 --- a/oshconnect/datamodels/datastreams.py +++ b/src/oshconnect/datamodels/datastreams.py @@ -1,8 +1,8 @@ from pydantic import BaseModel, Field, field_validator, SerializeAsAny -from oshconnect.csapi4py.constants import ObservationFormat -from oshconnect.datamodels.encoding import Encoding -from oshconnect.datamodels.swe_components import AnyComponentSchema +from src.oshconnect.csapi4py.constants import ObservationFormat +from src.oshconnect.datamodels.encoding import Encoding +from src.oshconnect.datamodels.swe_components import AnyComponentSchema class DatastreamSchema(BaseModel): diff --git a/oshconnect/datamodels/encoding.py b/src/oshconnect/datamodels/encoding.py similarity index 100% rename from oshconnect/datamodels/encoding.py rename to src/oshconnect/datamodels/encoding.py diff --git a/oshconnect/datamodels/geometry.py b/src/oshconnect/datamodels/geometry.py similarity index 86% rename from oshconnect/datamodels/geometry.py rename to src/oshconnect/datamodels/geometry.py index ced5988..4573523 100644 --- a/oshconnect/datamodels/geometry.py +++ b/src/oshconnect/datamodels/geometry.py @@ -1,6 +1,6 @@ from pydantic import BaseModel, Field -from oshconnect.csapi4py.constants import GeometryTypes +from src.oshconnect.csapi4py.constants import GeometryTypes # TODO: Add specific validations for each type diff --git a/oshconnect/datamodels/network_properties.py b/src/oshconnect/datamodels/network_properties.py similarity index 100% rename from oshconnect/datamodels/network_properties.py rename to src/oshconnect/datamodels/network_properties.py diff --git a/oshconnect/datamodels/observations.py b/src/oshconnect/datamodels/observations.py similarity index 92% rename from oshconnect/datamodels/observations.py rename to src/oshconnect/datamodels/observations.py index 3de9dee..4788a85 100644 --- a/oshconnect/datamodels/observations.py +++ b/src/oshconnect/datamodels/observations.py @@ -3,7 +3,7 @@ from pydantic import BaseModel, Field -from oshconnect.datamodels.api_utils import Link +from src.oshconnect.datamodels.api_utils import Link class ObservationOMJSONInline(BaseModel): diff --git a/oshconnect/datamodels/swe_components.py b/src/oshconnect/datamodels/swe_components.py similarity index 97% rename from oshconnect/datamodels/swe_components.py rename to src/oshconnect/datamodels/swe_components.py index 911ac52..e78b74e 100644 --- a/oshconnect/datamodels/swe_components.py +++ b/src/oshconnect/datamodels/swe_components.py @@ -5,9 +5,9 @@ from pydantic import BaseModel, Field, field_validator, SerializeAsAny -from oshconnect.csapi4py.constants import GeometryTypes -from oshconnect.datamodels.api_utils import UCUMCode, URI -from oshconnect.datamodels.geometry import Geometry +from src.oshconnect.csapi4py.constants import GeometryTypes +from src.oshconnect.datamodels.api_utils import UCUMCode, URI +from src.oshconnect.datamodels.geometry import Geometry """ NOTE: The following classes are used to represent the Record Schemas that are required for use with Datastreams diff --git a/oshconnect/datamodels/system_events_and_history.py b/src/oshconnect/datamodels/system_events_and_history.py similarity index 91% rename from oshconnect/datamodels/system_events_and_history.py rename to src/oshconnect/datamodels/system_events_and_history.py index f0801ec..a6c581b 100644 --- a/oshconnect/datamodels/system_events_and_history.py +++ b/src/oshconnect/datamodels/system_events_and_history.py @@ -2,8 +2,8 @@ from pydantic import BaseModel, Field, HttpUrl -from oshconnect.datamodels.api_utils import Link, URI -from oshconnect.datamodels.geometry import Geometry +from src.oshconnect.datamodels.api_utils import Link, URI +from src.oshconnect.datamodels.geometry import Geometry class SystemEventOMJSON(BaseModel): diff --git a/oshconnect/datasource.py b/src/oshconnect/datasource.py similarity index 98% rename from oshconnect/datasource.py rename to src/oshconnect/datasource.py index ccd0f9b..41c4071 100644 --- a/oshconnect/datasource.py +++ b/src/oshconnect/datasource.py @@ -16,9 +16,9 @@ import requests import websockets -from oshconnect.csapi4py.constants import APIResourceTypes -from oshconnect.datamodels.observations import ObservationOMJSONInline -from oshconnect.datamodels.swe_components import DataRecordSchema +from src.oshconnect.csapi4py.constants import APIResourceTypes +from src.oshconnect.datamodels.observations import ObservationOMJSONInline +from src.oshconnect.datamodels.swe_components import DataRecordSchema from .core_datamodels import DatastreamResource, SystemResource, TimePeriod from .timemanagement import TemporalModes, Synchronizer diff --git a/oshconnect/datastore.py b/src/oshconnect/datastore.py similarity index 100% rename from oshconnect/datastore.py rename to src/oshconnect/datastore.py diff --git a/oshconnect/osh_connect_datamodels.py b/src/oshconnect/osh_connect_datamodels.py similarity index 97% rename from oshconnect/osh_connect_datamodels.py rename to src/oshconnect/osh_connect_datamodels.py index e9d4461..42508ee 100644 --- a/oshconnect/osh_connect_datamodels.py +++ b/src/oshconnect/osh_connect_datamodels.py @@ -10,11 +10,11 @@ import uuid from dataclasses import dataclass, field -from oshconnect.csapi4py.constants import APIResourceTypes -from oshconnect.csapi4py.core.default_api_helpers import APIHelper -from oshconnect.datamodels.datastreams import SWEDatastreamSchema -from oshconnect.datamodels.encoding import JSONEncoding -from oshconnect.datamodels.swe_components import DataRecordSchema +from src.oshconnect.csapi4py.constants import APIResourceTypes +from src.oshconnect.csapi4py.core.default_api_helpers import APIHelper +from src.oshconnect.datamodels.datastreams import SWEDatastreamSchema +from src.oshconnect.datamodels.encoding import JSONEncoding +from src.oshconnect.datamodels.swe_components import DataRecordSchema from .core_datamodels import DatastreamResource, ObservationResource, SystemResource from .timemanagement import TimeInstant, TimePeriod, TimeUtils diff --git a/oshconnect/oshconnectapi.py b/src/oshconnect/oshconnectapi.py similarity index 99% rename from oshconnect/oshconnectapi.py rename to src/oshconnect/oshconnectapi.py index 0a24efe..13dcea6 100644 --- a/oshconnect/oshconnectapi.py +++ b/src/oshconnect/oshconnectapi.py @@ -7,7 +7,7 @@ import logging import shelve -from oshconnect.csapi4py.core.default_api_helpers import APIHelper +from src.oshconnect.csapi4py.core.default_api_helpers import APIHelper from .core_datamodels import DatastreamResource, TimePeriod from .datasource import DataStream, DataStreamHandler, MessageWrapper from .datastore import DataStore diff --git a/oshconnect/styling.py b/src/oshconnect/styling.py similarity index 100% rename from oshconnect/styling.py rename to src/oshconnect/styling.py diff --git a/oshconnect/timemanagement.py b/src/oshconnect/timemanagement.py similarity index 100% rename from oshconnect/timemanagement.py rename to src/oshconnect/timemanagement.py diff --git a/tests/test_api_update.py b/tests/test_api_update.py index 3eff373..394d03b 100644 --- a/tests/test_api_update.py +++ b/tests/test_api_update.py @@ -1,6 +1,4 @@ -import pytest - -from oshconnect import OSHConnect, System, Node, Datastream +from src.oshconnect import OSHConnect, Node node = Node() app = OSHConnect() \ No newline at end of file diff --git a/tests/test_oshconnect.py b/tests/test_oshconnect.py index 7457fe7..2bbb65e 100644 --- a/tests/test_oshconnect.py +++ b/tests/test_oshconnect.py @@ -7,10 +7,10 @@ import websockets -from oshconnect.core_datamodels import TimePeriod -from oshconnect.osh_connect_datamodels import Node -from oshconnect.oshconnectapi import OSHConnect -from oshconnect.timemanagement import TimeInstant +from src.oshconnect import TimePeriod +from src.oshconnect import Node +from src.oshconnect import OSHConnect +from src.oshconnect import TimeInstant class TestOSHConnect: diff --git a/uv.lock b/uv.lock index c3056e9..6a84e03 100644 --- a/uv.lock +++ b/uv.lock @@ -231,7 +231,7 @@ wheels = [ [[package]] name = "oshconnect" -version = "0.3.0a3" +version = "0.3.0a3.post1" source = { virtual = "." } dependencies = [ { name = "paho-mqtt" }, From c49de382bb15203d5bb392251383282c008db052 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Fri, 19 Sep 2025 01:36:20 -0500 Subject: [PATCH 24/31] refactor: update import paths to remove 'src' prefix --- pyproject.toml | 2 +- src/oshconnect/control.py | 8 ++++---- src/oshconnect/core_datamodels.py | 8 ++++---- src/oshconnect/csapi4py/con_sys_api.py | 4 ++-- src/oshconnect/csapi4py/core/default_api_helpers.py | 4 ++-- src/oshconnect/csapi4py/endpoints.py | 2 +- src/oshconnect/csapi4py/part_1/capabilities.py | 4 ++-- src/oshconnect/csapi4py/part_1/collections_ep.py | 4 ++-- src/oshconnect/csapi4py/part_1/deployments.py | 4 ++-- src/oshconnect/csapi4py/part_1/procedures.py | 4 ++-- src/oshconnect/csapi4py/part_1/properties.py | 4 ++-- src/oshconnect/csapi4py/part_1/sampling_features.py | 4 ++-- src/oshconnect/csapi4py/part_1/systems.py | 6 +++--- src/oshconnect/csapi4py/part_2/commands.py | 4 ++-- src/oshconnect/csapi4py/part_2/control_channels.py | 4 ++-- src/oshconnect/csapi4py/part_2/datastreams.py | 4 ++-- src/oshconnect/csapi4py/part_2/observations.py | 4 ++-- src/oshconnect/csapi4py/part_2/system_events.py | 4 ++-- src/oshconnect/csapi4py/part_2/system_history.py | 4 ++-- src/oshconnect/csapi4py/request_bodies.py | 8 ++++---- src/oshconnect/datamodels/control_streams.py | 4 ++-- src/oshconnect/datamodels/datastreams.py | 6 +++--- src/oshconnect/datamodels/geometry.py | 2 +- src/oshconnect/datamodels/observations.py | 2 +- src/oshconnect/datamodels/swe_components.py | 6 +++--- src/oshconnect/datamodels/system_events_and_history.py | 4 ++-- src/oshconnect/datasource.py | 6 +++--- src/oshconnect/osh_connect_datamodels.py | 10 +++++----- src/oshconnect/oshconnectapi.py | 2 +- tests/test_api_update.py | 2 +- tests/test_oshconnect.py | 8 ++++---- uv.lock | 2 +- 32 files changed, 72 insertions(+), 72 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 44b3e04..dacf3d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oshconnect" -version = "0.3.0a3.post1" +version = "0.3.0a3.post2" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." readme = "README.md" authors = [ diff --git a/src/oshconnect/control.py b/src/oshconnect/control.py index 3686472..01f2c45 100644 --- a/src/oshconnect/control.py +++ b/src/oshconnect/control.py @@ -5,11 +5,11 @@ # Contact Email: ian@botts-inc.com # ============================================================================== import websockets -from src.oshconnect.csapi4py.comm.mqtt import MQTTCommClient -from src.oshconnect.datamodels.commands import CommandJSON -from src.oshconnect.datamodels.control_streams import ControlStreamJSONSchema +from oshconnect.csapi4py.comm.mqtt import MQTTCommClient +from oshconnect.datamodels.commands import CommandJSON +from oshconnect.datamodels.control_streams import ControlStreamJSONSchema -from src.oshconnect.osh_connect_datamodels import System +from oshconnect.osh_connect_datamodels import System class ControlSchema: diff --git a/src/oshconnect/core_datamodels.py b/src/oshconnect/core_datamodels.py index eebcee2..83fced5 100644 --- a/src/oshconnect/core_datamodels.py +++ b/src/oshconnect/core_datamodels.py @@ -8,13 +8,13 @@ from typing import List -from src.oshconnect.datamodels.geometry import Geometry -from src.oshconnect.datamodels.datastreams import DatastreamSchema -from src.oshconnect.datamodels.api_utils import Link +from oshconnect.datamodels.geometry import Geometry +from oshconnect.datamodels.datastreams import DatastreamSchema +from oshconnect.datamodels.api_utils import Link from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny from shapely import Point -from src.oshconnect.timemanagement import DateTimeSchema, TimePeriod +from oshconnect.timemanagement import DateTimeSchema, TimePeriod class BoundingBox(BaseModel): diff --git a/src/oshconnect/csapi4py/con_sys_api.py b/src/oshconnect/csapi4py/con_sys_api.py index 18237b5..120ba32 100644 --- a/src/oshconnect/csapi4py/con_sys_api.py +++ b/src/oshconnect/csapi4py/con_sys_api.py @@ -2,8 +2,8 @@ from pydantic import BaseModel, HttpUrl, Field -from src.oshconnect.csapi4py.endpoints import Endpoint -from src.oshconnect.csapi4py.request_wrappers import post_request, put_request, get_request, delete_request +from oshconnect.csapi4py.endpoints import Endpoint +from oshconnect.csapi4py.request_wrappers import post_request, put_request, get_request, delete_request class ConnectedSystemAPIRequest(BaseModel): diff --git a/src/oshconnect/csapi4py/core/default_api_helpers.py b/src/oshconnect/csapi4py/core/default_api_helpers.py index 73ccdb8..d034876 100644 --- a/src/oshconnect/csapi4py/core/default_api_helpers.py +++ b/src/oshconnect/csapi4py/core/default_api_helpers.py @@ -5,8 +5,8 @@ from pydantic import BaseModel, Field -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemAPIRequest -from src.oshconnect.csapi4py.constants import APIResourceTypes, EncodingSchema, APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemAPIRequest +from oshconnect.csapi4py.constants import APIResourceTypes, EncodingSchema, APITerms def determine_parent_type(res_type: APIResourceTypes): diff --git a/src/oshconnect/csapi4py/endpoints.py b/src/oshconnect/csapi4py/endpoints.py index bf26470..5943fe7 100644 --- a/src/oshconnect/csapi4py/endpoints.py +++ b/src/oshconnect/csapi4py/endpoints.py @@ -4,7 +4,7 @@ # import websockets from pydantic import BaseModel, Field -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.constants import APITerms class Endpoint(BaseModel): diff --git a/src/oshconnect/csapi4py/part_1/capabilities.py b/src/oshconnect/csapi4py/part_1/capabilities.py index 073c523..e094aa5 100644 --- a/src/oshconnect/csapi4py/part_1/capabilities.py +++ b/src/oshconnect/csapi4py/part_1/capabilities.py @@ -1,7 +1,7 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def get_landing_page(server_addr: HttpUrl, api_root: str = APITerms.API.value): diff --git a/src/oshconnect/csapi4py/part_1/collections_ep.py b/src/oshconnect/csapi4py/part_1/collections_ep.py index a391f2f..0c8dbfc 100644 --- a/src/oshconnect/csapi4py/part_1/collections_ep.py +++ b/src/oshconnect/csapi4py/part_1/collections_ep.py @@ -1,7 +1,7 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_all_collections(server_addr: HttpUrl, api_root: str = APITerms.API.value): diff --git a/src/oshconnect/csapi4py/part_1/deployments.py b/src/oshconnect/csapi4py/part_1/deployments.py index 0ed7802..0f8fe60 100644 --- a/src/oshconnect/csapi4py/part_1/deployments.py +++ b/src/oshconnect/csapi4py/part_1/deployments.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_all_deployments(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/src/oshconnect/csapi4py/part_1/procedures.py b/src/oshconnect/csapi4py/part_1/procedures.py index d5df4b2..41e58a5 100644 --- a/src/oshconnect/csapi4py/part_1/procedures.py +++ b/src/oshconnect/csapi4py/part_1/procedures.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_all_procedures(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/src/oshconnect/csapi4py/part_1/properties.py b/src/oshconnect/csapi4py/part_1/properties.py index df5f21f..8347424 100644 --- a/src/oshconnect/csapi4py/part_1/properties.py +++ b/src/oshconnect/csapi4py/part_1/properties.py @@ -1,7 +1,7 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_all_properties(server_addr: HttpUrl, api_root: str = APITerms.API.value): diff --git a/src/oshconnect/csapi4py/part_1/sampling_features.py b/src/oshconnect/csapi4py/part_1/sampling_features.py index 59dc523..5ae3d5c 100644 --- a/src/oshconnect/csapi4py/part_1/sampling_features.py +++ b/src/oshconnect/csapi4py/part_1/sampling_features.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_all_sampling_features(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers=None): diff --git a/src/oshconnect/csapi4py/part_1/systems.py b/src/oshconnect/csapi4py/part_1/systems.py index 876a093..d746b24 100644 --- a/src/oshconnect/csapi4py/part_1/systems.py +++ b/src/oshconnect/csapi4py/part_1/systems.py @@ -3,9 +3,9 @@ import requests from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms -from src.oshconnect.csapi4py.request_wrappers import post_request +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.request_wrappers import post_request def list_all_systems(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/src/oshconnect/csapi4py/part_2/commands.py b/src/oshconnect/csapi4py/part_2/commands.py index 4874b8d..ac26dbf 100644 --- a/src/oshconnect/csapi4py/part_2/commands.py +++ b/src/oshconnect/csapi4py/part_2/commands.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_all_commands(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/src/oshconnect/csapi4py/part_2/control_channels.py b/src/oshconnect/csapi4py/part_2/control_channels.py index 3c9e487..3b4d995 100644 --- a/src/oshconnect/csapi4py/part_2/control_channels.py +++ b/src/oshconnect/csapi4py/part_2/control_channels.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_all_control_streams(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/src/oshconnect/csapi4py/part_2/datastreams.py b/src/oshconnect/csapi4py/part_2/datastreams.py index 0917a39..3a21b3a 100644 --- a/src/oshconnect/csapi4py/part_2/datastreams.py +++ b/src/oshconnect/csapi4py/part_2/datastreams.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_all_datastreams(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/src/oshconnect/csapi4py/part_2/observations.py b/src/oshconnect/csapi4py/part_2/observations.py index 2c8e742..788407b 100644 --- a/src/oshconnect/csapi4py/part_2/observations.py +++ b/src/oshconnect/csapi4py/part_2/observations.py @@ -2,8 +2,8 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_all_observations(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers=None): diff --git a/src/oshconnect/csapi4py/part_2/system_events.py b/src/oshconnect/csapi4py/part_2/system_events.py index 3ee288e..afbbb47 100644 --- a/src/oshconnect/csapi4py/part_2/system_events.py +++ b/src/oshconnect/csapi4py/part_2/system_events.py @@ -1,7 +1,7 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_system_events(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/src/oshconnect/csapi4py/part_2/system_history.py b/src/oshconnect/csapi4py/part_2/system_history.py index 96fe909..dc73c5c 100644 --- a/src/oshconnect/csapi4py/part_2/system_history.py +++ b/src/oshconnect/csapi4py/part_2/system_history.py @@ -1,7 +1,7 @@ from pydantic import HttpUrl -from src.oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from src.oshconnect.csapi4py.constants import APITerms +from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from oshconnect.csapi4py.constants import APITerms def list_system_history(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, headers: dict = None): diff --git a/src/oshconnect/csapi4py/request_bodies.py b/src/oshconnect/csapi4py/request_bodies.py index c0f3daf..c9bbb93 100644 --- a/src/oshconnect/csapi4py/request_bodies.py +++ b/src/oshconnect/csapi4py/request_bodies.py @@ -2,10 +2,10 @@ from pydantic import BaseModel, HttpUrl, Field, model_serializer, RootModel, SerializeAsAny -from src.oshconnect.csapi4py.constants import DatastreamResultTypes -from src.oshconnect.datamodels.datastreams import DatastreamSchema -from src.oshconnect.datamodels.geometry import Geometry -from src.oshconnect.csapi4py.sensor_ml.sml import TypeOf +from oshconnect.csapi4py.constants import DatastreamResultTypes +from oshconnect.datamodels.datastreams import DatastreamSchema +from oshconnect.datamodels.geometry import Geometry +from oshconnect.csapi4py.sensor_ml.sml import TypeOf # TODO: Consider some sort of Abstract Base Class for all valid request bodies to inherit from to reduce the complexity diff --git a/src/oshconnect/datamodels/control_streams.py b/src/oshconnect/datamodels/control_streams.py index b11dac9..8ee8eb5 100644 --- a/src/oshconnect/datamodels/control_streams.py +++ b/src/oshconnect/datamodels/control_streams.py @@ -4,8 +4,8 @@ from pydantic import BaseModel, Field, SerializeAsAny -from src.oshconnect.datamodels.encoding import Encoding -from src.oshconnect.datamodels.swe_components import AnyComponentSchema +from oshconnect.datamodels.encoding import Encoding +from oshconnect.datamodels.swe_components import AnyComponentSchema class ControlStreamJSONSchema(BaseModel): diff --git a/src/oshconnect/datamodels/datastreams.py b/src/oshconnect/datamodels/datastreams.py index 5158d4e..11d3986 100644 --- a/src/oshconnect/datamodels/datastreams.py +++ b/src/oshconnect/datamodels/datastreams.py @@ -1,8 +1,8 @@ from pydantic import BaseModel, Field, field_validator, SerializeAsAny -from src.oshconnect.csapi4py.constants import ObservationFormat -from src.oshconnect.datamodels.encoding import Encoding -from src.oshconnect.datamodels.swe_components import AnyComponentSchema +from oshconnect.csapi4py.constants import ObservationFormat +from oshconnect.datamodels.encoding import Encoding +from oshconnect.datamodels.swe_components import AnyComponentSchema class DatastreamSchema(BaseModel): diff --git a/src/oshconnect/datamodels/geometry.py b/src/oshconnect/datamodels/geometry.py index 4573523..ced5988 100644 --- a/src/oshconnect/datamodels/geometry.py +++ b/src/oshconnect/datamodels/geometry.py @@ -1,6 +1,6 @@ from pydantic import BaseModel, Field -from src.oshconnect.csapi4py.constants import GeometryTypes +from oshconnect.csapi4py.constants import GeometryTypes # TODO: Add specific validations for each type diff --git a/src/oshconnect/datamodels/observations.py b/src/oshconnect/datamodels/observations.py index 4788a85..3de9dee 100644 --- a/src/oshconnect/datamodels/observations.py +++ b/src/oshconnect/datamodels/observations.py @@ -3,7 +3,7 @@ from pydantic import BaseModel, Field -from src.oshconnect.datamodels.api_utils import Link +from oshconnect.datamodels.api_utils import Link class ObservationOMJSONInline(BaseModel): diff --git a/src/oshconnect/datamodels/swe_components.py b/src/oshconnect/datamodels/swe_components.py index e78b74e..911ac52 100644 --- a/src/oshconnect/datamodels/swe_components.py +++ b/src/oshconnect/datamodels/swe_components.py @@ -5,9 +5,9 @@ from pydantic import BaseModel, Field, field_validator, SerializeAsAny -from src.oshconnect.csapi4py.constants import GeometryTypes -from src.oshconnect.datamodels.api_utils import UCUMCode, URI -from src.oshconnect.datamodels.geometry import Geometry +from oshconnect.csapi4py.constants import GeometryTypes +from oshconnect.datamodels.api_utils import UCUMCode, URI +from oshconnect.datamodels.geometry import Geometry """ NOTE: The following classes are used to represent the Record Schemas that are required for use with Datastreams diff --git a/src/oshconnect/datamodels/system_events_and_history.py b/src/oshconnect/datamodels/system_events_and_history.py index a6c581b..f0801ec 100644 --- a/src/oshconnect/datamodels/system_events_and_history.py +++ b/src/oshconnect/datamodels/system_events_and_history.py @@ -2,8 +2,8 @@ from pydantic import BaseModel, Field, HttpUrl -from src.oshconnect.datamodels.api_utils import Link, URI -from src.oshconnect.datamodels.geometry import Geometry +from oshconnect.datamodels.api_utils import Link, URI +from oshconnect.datamodels.geometry import Geometry class SystemEventOMJSON(BaseModel): diff --git a/src/oshconnect/datasource.py b/src/oshconnect/datasource.py index 41c4071..ccd0f9b 100644 --- a/src/oshconnect/datasource.py +++ b/src/oshconnect/datasource.py @@ -16,9 +16,9 @@ import requests import websockets -from src.oshconnect.csapi4py.constants import APIResourceTypes -from src.oshconnect.datamodels.observations import ObservationOMJSONInline -from src.oshconnect.datamodels.swe_components import DataRecordSchema +from oshconnect.csapi4py.constants import APIResourceTypes +from oshconnect.datamodels.observations import ObservationOMJSONInline +from oshconnect.datamodels.swe_components import DataRecordSchema from .core_datamodels import DatastreamResource, SystemResource, TimePeriod from .timemanagement import TemporalModes, Synchronizer diff --git a/src/oshconnect/osh_connect_datamodels.py b/src/oshconnect/osh_connect_datamodels.py index 42508ee..e9d4461 100644 --- a/src/oshconnect/osh_connect_datamodels.py +++ b/src/oshconnect/osh_connect_datamodels.py @@ -10,11 +10,11 @@ import uuid from dataclasses import dataclass, field -from src.oshconnect.csapi4py.constants import APIResourceTypes -from src.oshconnect.csapi4py.core.default_api_helpers import APIHelper -from src.oshconnect.datamodels.datastreams import SWEDatastreamSchema -from src.oshconnect.datamodels.encoding import JSONEncoding -from src.oshconnect.datamodels.swe_components import DataRecordSchema +from oshconnect.csapi4py.constants import APIResourceTypes +from oshconnect.csapi4py.core.default_api_helpers import APIHelper +from oshconnect.datamodels.datastreams import SWEDatastreamSchema +from oshconnect.datamodels.encoding import JSONEncoding +from oshconnect.datamodels.swe_components import DataRecordSchema from .core_datamodels import DatastreamResource, ObservationResource, SystemResource from .timemanagement import TimeInstant, TimePeriod, TimeUtils diff --git a/src/oshconnect/oshconnectapi.py b/src/oshconnect/oshconnectapi.py index 13dcea6..0a24efe 100644 --- a/src/oshconnect/oshconnectapi.py +++ b/src/oshconnect/oshconnectapi.py @@ -7,7 +7,7 @@ import logging import shelve -from src.oshconnect.csapi4py.core.default_api_helpers import APIHelper +from oshconnect.csapi4py.core.default_api_helpers import APIHelper from .core_datamodels import DatastreamResource, TimePeriod from .datasource import DataStream, DataStreamHandler, MessageWrapper from .datastore import DataStore diff --git a/tests/test_api_update.py b/tests/test_api_update.py index 394d03b..ed1df9d 100644 --- a/tests/test_api_update.py +++ b/tests/test_api_update.py @@ -1,4 +1,4 @@ -from src.oshconnect import OSHConnect, Node +from oshconnect import OSHConnect, Node node = Node() app = OSHConnect() \ No newline at end of file diff --git a/tests/test_oshconnect.py b/tests/test_oshconnect.py index 2bbb65e..c3f1440 100644 --- a/tests/test_oshconnect.py +++ b/tests/test_oshconnect.py @@ -7,10 +7,10 @@ import websockets -from src.oshconnect import TimePeriod -from src.oshconnect import Node -from src.oshconnect import OSHConnect -from src.oshconnect import TimeInstant +from oshconnect import TimePeriod +from oshconnect import Node +from oshconnect import OSHConnect +from oshconnect import TimeInstant class TestOSHConnect: diff --git a/uv.lock b/uv.lock index 6a84e03..02bbe6b 100644 --- a/uv.lock +++ b/uv.lock @@ -231,7 +231,7 @@ wheels = [ [[package]] name = "oshconnect" -version = "0.3.0a3.post1" +version = "0.3.0a3.post2" source = { virtual = "." } dependencies = [ { name = "paho-mqtt" }, From fcbecd5a99059ef0c684220442ceaba5619f2a3b Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Fri, 19 Sep 2025 12:49:12 -0500 Subject: [PATCH 25/31] update lock and add missing request dep --- pyproject.toml | 3 ++- uv.lock | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dacf3d1..32feb14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oshconnect" -version = "0.3.0a3.post2" +version = "0.3.0a3.post3" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." readme = "README.md" authors = [ @@ -12,6 +12,7 @@ dependencies = [ "pydantic>=2.7.4,<3.0.0", "shapely>=2.0.4,<3.0.0", "websockets>=12.0,<16.0", + "requests" ] [dependency-groups] diff --git a/uv.lock b/uv.lock index 02bbe6b..810841c 100644 --- a/uv.lock +++ b/uv.lock @@ -231,11 +231,12 @@ wheels = [ [[package]] name = "oshconnect" -version = "0.3.0a3.post2" +version = "0.3.0a3.post3" source = { virtual = "." } dependencies = [ { name = "paho-mqtt" }, { name = "pydantic" }, + { name = "requests" }, { name = "shapely" }, { name = "websockets" }, ] @@ -252,6 +253,7 @@ dev = [ requires-dist = [ { name = "paho-mqtt", specifier = ">=2.1.0" }, { name = "pydantic", specifier = ">=2.7.4,<3.0.0" }, + { name = "requests" }, { name = "shapely", specifier = ">=2.0.4,<3.0.0" }, { name = "websockets", specifier = ">=12.0,<16.0" }, ] From 298c77c2a7d00f8aacc0eef68074a547fdcd8df8 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Fri, 3 Oct 2025 14:12:06 -0500 Subject: [PATCH 26/31] - implement streamables and async data retrievals - reorganization of major files --- pyproject.toml | 6 +- src/oshconnect/__init__.py | 2 +- src/oshconnect/api_helpers.py | 1632 +++++++++++++++++ src/oshconnect/{datamodels => }/api_utils.py | 0 src/oshconnect/control.py | 7 +- src/oshconnect/csapi4py/comm/__init__.py | 0 src/oshconnect/csapi4py/con_sys_api.py | 4 +- src/oshconnect/csapi4py/constants.py | 1 + src/oshconnect/csapi4py/core/__init__.py | 0 .../{core => }/default_api_helpers.py | 38 +- src/oshconnect/csapi4py/endpoints.py | 2 +- .../csapi4py/{utilities => }/model_utils.py | 0 src/oshconnect/csapi4py/{comm => }/mqtt.py | 0 src/oshconnect/csapi4py/part_1/__init__.py | 7 - .../csapi4py/part_1/capabilities.py | 31 - .../csapi4py/part_1/collections_ep.py | 67 - src/oshconnect/csapi4py/part_1/deployments.py | 217 --- src/oshconnect/csapi4py/part_1/procedures.py | 97 - src/oshconnect/csapi4py/part_1/properties.py | 80 - .../csapi4py/part_1/sampling_features.py | 116 -- src/oshconnect/csapi4py/part_1/systems.py | 213 --- src/oshconnect/csapi4py/part_2/__init__.py | 6 - src/oshconnect/csapi4py/part_2/commands.py | 227 --- .../csapi4py/part_2/control_channels.py | 159 -- src/oshconnect/csapi4py/part_2/datastreams.py | 155 -- .../csapi4py/part_2/observations.py | 122 -- .../csapi4py/part_2/system_events.py | 122 -- .../csapi4py/part_2/system_history.py | 83 - src/oshconnect/csapi4py/request_bodies.py | 6 +- src/oshconnect/csapi4py/sensor_ml/__init__.py | 0 .../csapi4py/{sensor_ml => }/sml.py | 0 src/oshconnect/csapi4py/utilities/__init__.py | 0 src/oshconnect/datamodels/__init__.py | 10 - src/oshconnect/datamodels/commands.py | 14 - src/oshconnect/datamodels/control_streams.py | 39 - src/oshconnect/datamodels/datastreams.py | 25 - src/oshconnect/datamodels/observations.py | 19 - .../datamodels/system_events_and_history.py | 46 - src/oshconnect/datasource.py | 94 +- src/oshconnect/{datamodels => }/encoding.py | 0 src/oshconnect/{datamodels => }/geometry.py | 9 +- .../{datamodels => }/network_properties.py | 0 src/oshconnect/oshconnectapi.py | 60 +- ...e_datamodels.py => resource_datamodels.py} | 104 +- src/oshconnect/schema_datamodels.py | 137 ++ ...ct_datamodels.py => streamableresource.py} | 334 +++- .../{datamodels => }/swe_components.py | 13 +- tests/test_api_helper.py | 17 + tests/test_oshconnect.py | 18 +- tests/test_streamable_resources.py | 12 + uv.lock | 331 +++- 51 files changed, 2623 insertions(+), 2059 deletions(-) create mode 100644 src/oshconnect/api_helpers.py rename src/oshconnect/{datamodels => }/api_utils.py (100%) delete mode 100644 src/oshconnect/csapi4py/comm/__init__.py delete mode 100644 src/oshconnect/csapi4py/core/__init__.py rename src/oshconnect/csapi4py/{core => }/default_api_helpers.py (86%) rename src/oshconnect/csapi4py/{utilities => }/model_utils.py (100%) rename src/oshconnect/csapi4py/{comm => }/mqtt.py (100%) delete mode 100644 src/oshconnect/csapi4py/part_1/__init__.py delete mode 100644 src/oshconnect/csapi4py/part_1/capabilities.py delete mode 100644 src/oshconnect/csapi4py/part_1/collections_ep.py delete mode 100644 src/oshconnect/csapi4py/part_1/deployments.py delete mode 100644 src/oshconnect/csapi4py/part_1/procedures.py delete mode 100644 src/oshconnect/csapi4py/part_1/properties.py delete mode 100644 src/oshconnect/csapi4py/part_1/sampling_features.py delete mode 100644 src/oshconnect/csapi4py/part_1/systems.py delete mode 100644 src/oshconnect/csapi4py/part_2/__init__.py delete mode 100644 src/oshconnect/csapi4py/part_2/commands.py delete mode 100644 src/oshconnect/csapi4py/part_2/control_channels.py delete mode 100644 src/oshconnect/csapi4py/part_2/datastreams.py delete mode 100644 src/oshconnect/csapi4py/part_2/observations.py delete mode 100644 src/oshconnect/csapi4py/part_2/system_events.py delete mode 100644 src/oshconnect/csapi4py/part_2/system_history.py delete mode 100644 src/oshconnect/csapi4py/sensor_ml/__init__.py rename src/oshconnect/csapi4py/{sensor_ml => }/sml.py (100%) delete mode 100644 src/oshconnect/csapi4py/utilities/__init__.py delete mode 100644 src/oshconnect/datamodels/__init__.py delete mode 100644 src/oshconnect/datamodels/commands.py delete mode 100644 src/oshconnect/datamodels/control_streams.py delete mode 100644 src/oshconnect/datamodels/datastreams.py delete mode 100644 src/oshconnect/datamodels/observations.py delete mode 100644 src/oshconnect/datamodels/system_events_and_history.py rename src/oshconnect/{datamodels => }/encoding.py (100%) rename src/oshconnect/{datamodels => }/geometry.py (51%) rename src/oshconnect/{datamodels => }/network_properties.py (100%) rename src/oshconnect/{core_datamodels.py => resource_datamodels.py} (51%) create mode 100644 src/oshconnect/schema_datamodels.py rename src/oshconnect/{osh_connect_datamodels.py => streamableresource.py} (51%) rename src/oshconnect/{datamodels => }/swe_components.py (94%) create mode 100644 tests/test_api_helper.py create mode 100644 tests/test_streamable_resources.py diff --git a/pyproject.toml b/pyproject.toml index 32feb14..2901dcf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oshconnect" -version = "0.3.0a3.post3" +version = "0.3.0a4" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." readme = "README.md" authors = [ @@ -12,7 +12,8 @@ dependencies = [ "pydantic>=2.7.4,<3.0.0", "shapely>=2.0.4,<3.0.0", "websockets>=12.0,<16.0", - "requests" + "requests", + "aiohttp>=3.12.15", ] [dependency-groups] @@ -24,5 +25,4 @@ dev = [ ] [tool.setuptools] -#packages = ["oshconnect", "oshconnect.csapi4py", "oshconnect.datamodels", "oshconnect.csapi4py.comm"] packages = {find = { where = ["src/"]}} diff --git a/src/oshconnect/__init__.py b/src/oshconnect/__init__.py index b1e50e6..708396b 100644 --- a/src/oshconnect/__init__.py +++ b/src/oshconnect/__init__.py @@ -6,4 +6,4 @@ # ============================================================================== from .oshconnectapi import OSHConnect -from .osh_connect_datamodels import System, Node, Datastream, Observation, ControlChannel +from .streamableresource import System, Node, Datastream, Observation, ControlChannel diff --git a/src/oshconnect/api_helpers.py b/src/oshconnect/api_helpers.py new file mode 100644 index 0000000..87c7d66 --- /dev/null +++ b/src/oshconnect/api_helpers.py @@ -0,0 +1,1632 @@ +# ============================================================================= +# Copyright (c) 2025 Botts Innovative Research Inc. +# Date: 2025/9/30 +# Author: Ian Patterson +# Contact Email: ian@botts-inc.com +# ============================================================================= +from typing import Union + +import requests +from pydantic import HttpUrl + +from csapi4py.con_sys_api import ConnectedSystemsRequestBuilder +from csapi4py.constants import APITerms +from csapi4py.request_wrappers import post_request + + +def get_landing_page(server_addr: HttpUrl, api_root: str = APITerms.API.value): + """ + Returns the landing page of the API + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .build_url_from_base() + .build()) + return api_request + + +def get_conformance_info(server_addr: HttpUrl, api_root: str = APITerms.API.value): + """ + Returns the conformance information of the API + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONFORMANCE.value) + .build_url_from_base() + .build()) + return api_request + + +def list_all_collections(server_addr: HttpUrl, api_root: str = APITerms.API.value): + """ + List all collections + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .build_url_from_base() + .build()) + return api_request + + +def retrieve_collection_metadata(server_addr: HttpUrl, collection_id: str, api_root: str = APITerms.API.value): + """ + Retrieve a collection by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .with_resource_id(collection_id) + .build_url_from_base() + .build()) + return api_request + + +def list_all_items_in_collection(server_addr: HttpUrl, collection_id: str, api_root: str = APITerms.API.value): + """ + Lists all systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .with_resource_id(collection_id) + .for_sub_resource_type(APITerms.ITEMS.value) + .build_url_from_base() + .build()) + return api_request + + +def retrieve_collection_item_by_id(server_addr: HttpUrl, collection_id: str, item_id: str, + api_root: str = APITerms.API.value): + """ + Retrieves a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .with_resource_id(collection_id) + .for_sub_resource_type(APITerms.ITEMS.value) + .with_resource_id(item_id) + .build_url_from_base() + .build()) + return api_request + + +def list_all_commands(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all commands + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def list_commands_of_control_channel(server_addr: HttpUrl, control_channel_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all commands of a control channel + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_channel_id) + .for_sub_resource_type(APITerms.COMMANDS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def send_commands_to_specific_control_stream(server_addr: HttpUrl, control_stream_id: str, + request_body: Union[dict, str], + api_root: str = APITerms.API.value, headers=None): + """ + Sends a command to a control stream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + .for_sub_resource_type(APITerms.COMMANDS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + + return api_request.make_request() + + +def retrieve_command_by_id(server_addr: HttpUrl, command_id: str, api_root: str = APITerms.API.value, headers=None): + """ + Retrieves a command by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def update_command_description(server_addr: HttpUrl, command_id: str, request_body: Union[dict, str], + api_root: str = APITerms.API.value, headers=None): + """ + Updates a command's description by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + + return api_request.make_request() + + +def delete_command_by_id(server_addr: HttpUrl, command_id: str, api_root: str = APITerms.API.value, headers=None): + """ + Deletes a command by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + + return api_request.make_request() + + +def list_command_status_reports(server_addr: HttpUrl, command_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all status reports of a command by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .for_sub_resource_type(APITerms.STATUS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def add_command_status_reports(server_addr: HttpUrl, command_id: str, request_body: Union[dict, str], + api_root: str = APITerms.API.value, headers=None): + """ + Adds a status report to a command by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .for_sub_resource_type(APITerms.STATUS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + + return api_request.make_request() + + +def retrieve_command_status_report_by_id(server_addr: HttpUrl, command_id: str, status_report_id: str, + api_root: str = APITerms.API.value, headers=None): + """ + Retrieves a status report of a command by its id and status report id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .for_sub_resource_type(APITerms.STATUS.value) + .with_secondary_resource_id(status_report_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def update_command_status_report_by_id(server_addr: HttpUrl, command_id: str, status_report_id: str, + request_body: Union[dict, str], api_root: str = APITerms.API.value, + headers=None): + """ + Updates a status report of a command by its id and status report id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .for_sub_resource_type(APITerms.STATUS.value) + .with_secondary_resource_id(status_report_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + + return api_request.make_request() + + +def delete_command_status_report_by_id(server_addr: HttpUrl, command_id: str, status_report_id: str, + api_root: str = APITerms.API.value, headers=None): + """ + Deletes a status report of a command by its id and status report id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COMMANDS.value) + .with_resource_id(command_id) + .for_sub_resource_type(APITerms.STATUS.value) + .with_secondary_resource_id(status_report_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + + return api_request.make_request() + + +def list_all_control_streams(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all control streams + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def list_control_streams_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all control streams of a system + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.CONTROL_STREAMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def add_control_streams_to_system(server_addr: HttpUrl, system_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers=None): + """ + Adds a control stream to a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.CONTROL_STREAMS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_control_stream_description_by_id(server_addr: HttpUrl, control_stream_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Retrieves a control stream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def update_control_stream_description_by_id(server_addr: HttpUrl, control_stream_id: str, + request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers: dict = None): + """ + Updates a control stream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_control_stream_by_id(server_addr: HttpUrl, control_stream_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Deletes a control stream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + + return api_request.make_request() + + +def retrieve_control_stream_schema_by_id(server_addr: HttpUrl, control_stream_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Retrieves a control stream schema by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + .for_sub_resource_type(APITerms.SCHEMA.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def update_control_stream_schema_by_id(server_addr: HttpUrl, control_stream_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers: dict = None): + """ + Updates a control stream schema by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.CONTROL_STREAMS.value) + .with_resource_id(control_stream_id) + # .for_sub_resource_type(APITerms.SCHEMA.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def list_all_datastreams(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all datastreams + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def list_all_datastreams_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all datastreams of a system + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.DATASTREAMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def add_datastreams_to_system(server_addr: HttpUrl, system_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers=None): + """ + Adds a datastream to a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.DATASTREAMS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_datastream_by_id(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Retrieves a datastream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_datastream_by_id(server_addr: HttpUrl, datastream_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers=None): + """ + Updates a datastream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_datastream_by_id(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, headers=None): + """ + Deletes a datastream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() + + +def retrieve_datastream_schema(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Retrieves a datastream schema by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .for_sub_resource_type(APITerms.SCHEMA.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_datastream_schema(server_addr: HttpUrl, datastream_id: str, request_body: dict, + api_root: str = APITerms.API.value, headers=None): + """ + Updates a datastream schema by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .for_resource_type(APITerms.SCHEMA.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def list_all_deployments(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all deployments in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def create_new_deployments(server_addr: HttpUrl, request_body: Union[str, dict], api_root: str = APITerms.API.value, + headers: dict = None): + """ + Create a new deployment as defined by the request body + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_deployment_by_id(server_addr: HttpUrl, deployment_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Retrieve a deployment by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_deployment_by_id(server_addr: HttpUrl, deployment_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers: dict = None): + """ + Update a deployment by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_deployment_by_id(server_addr: HttpUrl, deployment_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Delete a deployment by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request + + +def list_deployed_systems(server_addr: HttpUrl, deployment_id, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Lists all deployed systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .for_sub_resource_type(APITerms.SYSTEMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def add_systems_to_deployment(server_addr: HttpUrl, deployment_id: str, uri_list: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .for_sub_resource_type(APITerms.SYSTEMS.value) + .with_request_body(uri_list) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_deployed_system_by_id(server_addr: HttpUrl, deployment_id: str, system_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Retrieves a system by its id + :return: + """ + + # TODO: Add a way to have a secondary resource ID for certain endpoints + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .for_sub_resource_type(APITerms.SYSTEMS.value) + .with_secondary_resource_id(system_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_deployed_system_by_id(server_addr: HttpUrl, deployment_id: str, system_id: str, request_body: dict, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Update a system by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .for_sub_resource_type(APITerms.SYSTEMS.value) + .with_secondary_resource_id(system_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + + return api_request + + +def delete_deployed_system_by_id(server_addr: HttpUrl, deployment_id: str, system_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Delete a system by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DEPLOYMENTS.value) + .with_resource_id(deployment_id) + .for_sub_resource_type(APITerms.SYSTEMS.value) + .with_secondary_resource_id(system_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() + + +def list_deployments_of_specific_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Lists all deployments of a specific system in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.DEPLOYMENTS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def list_all_observations(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers=None): + """ + Lists all observations + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.OBSERVATIONS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def list_observations_from_datastream(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all observations of a datastream + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .for_sub_resource_type(APITerms.OBSERVATIONS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def add_observations_to_datastream(server_addr: HttpUrl, datastream_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers=None): + """ + Adds an observation to a datastream by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.DATASTREAMS.value) + .with_resource_id(datastream_id) + .for_sub_resource_type(APITerms.OBSERVATIONS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + + return api_request.make_request() + + +def retrieve_observation_by_id(server_addr: HttpUrl, observation_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Retrieves an observation by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.OBSERVATIONS.value) + .with_resource_id(observation_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def update_observation_by_id(server_addr: HttpUrl, observation_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers=None): + """ + Updates an observation by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.OBSERVATIONS.value) + .with_resource_id(observation_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + + return api_request.make_request() + + +def delete_observation_by_id(server_addr: HttpUrl, observation_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Deletes an observation by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.OBSERVATIONS.value) + .with_resource_id(observation_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + + return api_request.make_request() + + +def list_all_procedures(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all procedures in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROCEDURES.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def create_new_procedures(server_addr: HttpUrl, request_body: Union[str, dict], api_root: str = APITerms.API.value, + headers: dict = None): + """ + Create a new procedure as defined by the request body + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROCEDURES.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + print(api_request) + return api_request.make_request() + + +def retrieve_procedure_by_id(server_addr: HttpUrl, procedure_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Retrieve a procedure by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROCEDURES.value) + .with_resource_id(procedure_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_procedure_by_id(server_addr: HttpUrl, procedure_id: str, request_body: Union[str, dict], + api_root: str = APITerms.API.value, headers: dict = None): + """ + Update a procedure by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROCEDURES.value) + .with_resource_id(procedure_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_procedure_by_id(server_addr: HttpUrl, procedure_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Delete a procedure by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROCEDURES.value) + .with_resource_id(procedure_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() + + +def list_all_properties(server_addr: HttpUrl, api_root: str = APITerms.API.value): + """ + List all properties + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROPERTIES.value) + .build_url_from_base() + .build()) + return api_request + + +def create_new_properties(server_addr: HttpUrl, request_body: dict, api_root: str = APITerms.API.value): + """ + Create a new property as defined by the request body + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROPERTIES.value) + .with_request_body(request_body) + .build_url_from_base() + .build()) + return api_request + + +def retrieve_property_by_id(server_addr: HttpUrl, property_id: str, api_root: str = APITerms.API.value): + """ + Retrieve a property by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROPERTIES.value) + .with_resource_id(property_id) + .build_url_from_base() + .build()) + return api_request + + +def update_property_by_id(server_addr: HttpUrl, property_id: str, request_body: dict, + api_root: str = APITerms.API.value): + """ + Update a property by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROPERTIES.value) + .with_resource_id(property_id) + .with_request_body(request_body) + .build_url_from_base() + .build()) + return api_request + + +def delete_property_by_id(server_addr: HttpUrl, property_id: str, api_root: str = APITerms.API.value): + """ + Delete a property by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.PROPERTIES.value) + .with_resource_id(property_id) + .build_url_from_base() + .build()) + return api_request + + +def list_all_sampling_features(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers=None): + """ + Lists all sampling features in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SAMPLING_FEATURES.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def list_sampling_features_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Lists all sampling features of a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.SAMPLING_FEATURES.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def create_new_sampling_features(server_addr: HttpUrl, system_id: str, request_body: Union[dict, str], + api_root: str = APITerms.API.value, headers=None): + """ + Create a new sampling feature as defined by the request body + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.SAMPLING_FEATURES.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_sampling_feature_by_id(server_addr: HttpUrl, sampling_feature_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Retrieve a sampling feature by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SAMPLING_FEATURES.value) + .with_resource_id(sampling_feature_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_sampling_feature_by_id(server_addr: HttpUrl, sampling_feature_id: str, request_body: Union[dict, str], + api_root: str = APITerms.API.value, headers=None): + """ + Update a sampling feature by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SAMPLING_FEATURES.value) + .with_resource_id(sampling_feature_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_sampling_feature_by_id(server_addr: HttpUrl, sampling_feature_id: str, api_root: str = APITerms.API.value, + headers=None): + """ + Delete a sampling feature by its ID + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SAMPLING_FEATURES.value) + .with_resource_id(sampling_feature_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() + + +def list_system_events(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all system events + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEM_EVENTS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def list_events_by_system_id(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Lists all events of a system + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.EVENTS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def add_new_system_events(server_addr: HttpUrl, system_id: str, request_body: dict, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Adds a new system event to a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.EVENTS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('POST') + .build()) + return api_request.make_request() + + +def retrieve_system_event_by_id(server_addr: HttpUrl, system_id: str, event_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Retrieves a system event by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.EVENTS.value) + .with_secondary_resource_id(event_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_system_event_by_id(server_addr: HttpUrl, system_id: str, event_id: str, request_body: dict, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Updates a system event by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.EVENTS.value) + + .with_secondary_resource_id(event_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_system_event_by_id(server_addr: HttpUrl, system_id: str, event_id: str, api_root: str = APITerms.API.value, + headers: dict = None): + """ + Deletes a system event by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.EVENTS.value) + .with_secondary_resource_id(event_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + + return api_request.make_request() + + +def list_system_history(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all history versions of a system + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_resource_type(APITerms.HISTORY.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def retrieve_system_historical_description_by_id(server_addr: HttpUrl, system_id: str, history_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Retrieves a historical system description by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_resource_type(APITerms.HISTORY.value) + .with_secondary_resource_id(history_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + return api_request.make_request() + + +def update_system_historical_description(server_addr: HttpUrl, system_id: str, history_rev_id: str, request_body: dict, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Updates a historical system description by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_resource_type(APITerms.HISTORY.value) + .with_secondary_resource_id(history_rev_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .with_request_method('PUT') + .build()) + return api_request.make_request() + + +def delete_system_historical_description_by_id(server_addr: HttpUrl, system_id: str, history_rev_id: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Deletes a historical system description by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_resource_type(APITerms.HISTORY.value) + .with_secondary_resource_id(history_rev_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() + + +def list_all_systems(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): + """ + Lists all systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .build_url_from_base() + .with_headers(headers) + .with_request_method('GET') + .build()) + + return api_request.make_request() + + +def create_new_systems(server_addr: HttpUrl, request_body: Union[str, dict], api_root: str = APITerms.API.value, + uname: str = None, + pword: str = None, headers: dict = None): + """ + Create a new system as defined by the request body + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_request_body(request_body) + .build_url_from_base() + .with_auth(uname, pword) + .with_headers(headers) + .with_request_method('POST') + .build()) + print(api_request.url) + # resp = requests.post(api_request.url, data=api_request.body, headers=api_request.headers, auth=(uname, pword)) + resp = post_request(api_request.url, api_request.body, api_request.headers, api_request.auth) + print(f'Create new system response: {resp}') + return resp + + +def list_all_systems_in_collection(server_addr: HttpUrl, collection_id: str, api_root: str = APITerms.API.value): + """ + NOTE: function may not be able to fully represent a request to the API at this time, as the test server lacks a few + elements. + Lists all systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .with_resource_id(collection_id) + # .for_sub_resource_type(APITerms.ITEMS.value) + .build_url_from_base() + .build()) + print(api_request.url) + resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() + + +def add_systems_to_collection(server_addr: HttpUrl, collection_id: str, uri_list: str, + api_root: str = APITerms.API.value): + """ + Lists all systems in the server at the default API endpoint + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.COLLECTIONS.value) + .with_resource_id(collection_id) + .for_sub_resource_type(APITerms.ITEMS.value) + .with_request_body(uri_list) + .build_url_from_base() + .build()) + resp = requests.post(api_request.url, json=api_request.body, headers=api_request.headers) + return resp.json() + + +def retrieve_system_by_id(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): + """ + Retrieves a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .build_url_from_base() + .build()) + resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() + + +def update_system_description(server_addr: HttpUrl, system_id: str, request_body: str, + api_root: str = APITerms.API.value, headers: dict = None): + """ + Updates a system's description by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .with_request_body(request_body) + .build_url_from_base() + .with_headers(headers) + .build()) + resp = requests.put(api_request.url, data=request_body, headers=api_request.headers) + return resp + + +def delete_system_by_id(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, headers: dict = None): + """ + Deletes a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .build_url_from_base() + .with_headers(headers) + .with_request_method('DELETE') + .build()) + return api_request.make_request() + + +def list_system_components(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): + """ + Lists all components of a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.COMPONENTS.value) + .build_url_from_base() + .build()) + print(api_request.url) + resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() + + +def add_system_components(server_addr: HttpUrl, system_id: str, request_body: dict, + api_root: str = APITerms.API.value): + """ + Adds components to a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.COMPONENTS.value) + .with_request_body(request_body) + .build_url_from_base() + .build()) + resp = requests.post(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() + + +def list_deployments_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): + """ + Lists all deployments of a system by its id + :return: + """ + builder = ConnectedSystemsRequestBuilder() + api_request = (builder.with_server_url(server_addr) + .with_api_root(api_root) + .for_resource_type(APITerms.SYSTEMS.value) + .with_resource_id(system_id) + .for_sub_resource_type(APITerms.DEPLOYMENTS.value) + .build_url_from_base() + + .build()) + resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) + return resp.json() + +# def list_sampling_features_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): +# """ +# Lists all sampling features of a system by its id +# :return: +# """ +# builder = ConnectedSystemsRequestBuilder() +# api_request = (builder.with_server_url(server_addr) +# .with_api_root(api_root) +# .for_resource_type(APITerms.SYSTEMS.value) +# .with_resource_id(system_id) +# .for_sub_resource_type(APITerms.SAMPLING_FEATURES.value) +# .build_url_from_base() +# .build()) +# print(api_request.url) +# resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) +# return resp.json() diff --git a/src/oshconnect/datamodels/api_utils.py b/src/oshconnect/api_utils.py similarity index 100% rename from src/oshconnect/datamodels/api_utils.py rename to src/oshconnect/api_utils.py diff --git a/src/oshconnect/control.py b/src/oshconnect/control.py index 01f2c45..c4b4ddb 100644 --- a/src/oshconnect/control.py +++ b/src/oshconnect/control.py @@ -5,11 +5,10 @@ # Contact Email: ian@botts-inc.com # ============================================================================== import websockets -from oshconnect.csapi4py.comm.mqtt import MQTTCommClient -from oshconnect.datamodels.commands import CommandJSON -from oshconnect.datamodels.control_streams import ControlStreamJSONSchema -from oshconnect.osh_connect_datamodels import System +from csapi4py.mqtt import MQTTCommClient +from schema_datamodels import ControlStreamJSONSchema, CommandJSON +from src.oshconnect import System class ControlSchema: diff --git a/src/oshconnect/csapi4py/comm/__init__.py b/src/oshconnect/csapi4py/comm/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/oshconnect/csapi4py/con_sys_api.py b/src/oshconnect/csapi4py/con_sys_api.py index 120ba32..5fbdfd9 100644 --- a/src/oshconnect/csapi4py/con_sys_api.py +++ b/src/oshconnect/csapi4py/con_sys_api.py @@ -2,8 +2,8 @@ from pydantic import BaseModel, HttpUrl, Field -from oshconnect.csapi4py.endpoints import Endpoint -from oshconnect.csapi4py.request_wrappers import post_request, put_request, get_request, delete_request +from .endpoints import Endpoint +from .request_wrappers import post_request, put_request, get_request, delete_request class ConnectedSystemAPIRequest(BaseModel): diff --git a/src/oshconnect/csapi4py/constants.py b/src/oshconnect/csapi4py/constants.py index aa91a86..94d4ceb 100644 --- a/src/oshconnect/csapi4py/constants.py +++ b/src/oshconnect/csapi4py/constants.py @@ -77,6 +77,7 @@ class APIResourceTypes(Enum): """ Defines the resource types """ + ROOT = "" COLLECTION = "Collection" COMMAND = "Command" COMPONENT = "Component" diff --git a/src/oshconnect/csapi4py/core/__init__.py b/src/oshconnect/csapi4py/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/oshconnect/csapi4py/core/default_api_helpers.py b/src/oshconnect/csapi4py/default_api_helpers.py similarity index 86% rename from src/oshconnect/csapi4py/core/default_api_helpers.py rename to src/oshconnect/csapi4py/default_api_helpers.py index d034876..e16e5df 100644 --- a/src/oshconnect/csapi4py/core/default_api_helpers.py +++ b/src/oshconnect/csapi4py/default_api_helpers.py @@ -1,3 +1,10 @@ +# ============================================================================= +# Copyright (c) 2025 Botts Innovative Research Inc. +# Date: 2025/9/30 +# Author: Ian Patterson +# Contact Email: ian@botts-inc.com +# ============================================================================= + from __future__ import annotations from abc import ABC @@ -5,8 +12,8 @@ from pydantic import BaseModel, Field -from oshconnect.csapi4py.con_sys_api import ConnectedSystemAPIRequest -from oshconnect.csapi4py.constants import APIResourceTypes, EncodingSchema, APITerms +from .con_sys_api import ConnectedSystemAPIRequest +from .constants import APIResourceTypes, EncodingSchema, APITerms def determine_parent_type(res_type: APIResourceTypes): @@ -75,6 +82,8 @@ def resource_type_to_endpoint(res_type: APIResourceTypes, parent_type: APIResour @dataclass class APIHelper(ABC): server_url: str = None + port: int = None + protocol: str = "https" api_root: str = "api" username: str = None password: str = None @@ -193,17 +202,19 @@ def resource_url_resolver(self, res_type: APIResourceTypes, res_id: str = None, return self.construct_url(parent_type, res_id, res_type, parent_res_id) - def construct_url(self, parent_type, res_id, res_type, parent_res_id): + def construct_url(self, parent_type, res_id, res_type, parent_res_id, for_socket: bool = False): """ Constructs an API endpoint url from the given parameters :param parent_type: :param res_id: :param res_type: :param parent_res_id: + :param for_socket: If true, will construct a WebSocket URL (ws:// or wss://) instead of HTTP/HTTPS. :return: """ # TODO: Test for less common cases to ensure that the URL is being constructed correctly - base_url = f'{self.server_url}/{self.api_root}' + base_url = self.get_api_root_url(socket=for_socket) + resource_endpoint = resource_type_to_endpoint(res_type, parent_type) url = f'{base_url}/{resource_endpoint}' @@ -221,6 +232,25 @@ def get_helper_auth(self): return self.username, self.password return None + def get_base_url(self, socket: bool = False): + if socket: + protocol = 'ws' if self.protocol == 'http' else 'wss' + return f'{protocol}://{self.server_url}{f":{self.port}" if self.port else ""}' + return f'{self.protocol}://{self.server_url}{f":{self.port}" if self.port else ""}' + + def get_api_root_url(self, socket: bool = False): + """ + Returns the full API root URL including protocol, server address, port (if applicable), and API root path. + :param socket: If true, will return a WebSocket URL (ws:// or wss://) instead of HTTP/HTTPS. + :return: + """ + return f'{self.get_base_url(socket=socket)}/{self.api_root}' + + def set_protocol(self, protocol: str): + if protocol not in ['http', 'https', 'ws', 'wss']: + raise ValueError('Protocol must be either "http" or "https"') + self.protocol = protocol + @dataclass(kw_only=True) class ResponseParserHelper: diff --git a/src/oshconnect/csapi4py/endpoints.py b/src/oshconnect/csapi4py/endpoints.py index 5943fe7..1d8a238 100644 --- a/src/oshconnect/csapi4py/endpoints.py +++ b/src/oshconnect/csapi4py/endpoints.py @@ -4,7 +4,7 @@ # import websockets from pydantic import BaseModel, Field -from oshconnect.csapi4py.constants import APITerms +from .constants import APITerms class Endpoint(BaseModel): diff --git a/src/oshconnect/csapi4py/utilities/model_utils.py b/src/oshconnect/csapi4py/model_utils.py similarity index 100% rename from src/oshconnect/csapi4py/utilities/model_utils.py rename to src/oshconnect/csapi4py/model_utils.py diff --git a/src/oshconnect/csapi4py/comm/mqtt.py b/src/oshconnect/csapi4py/mqtt.py similarity index 100% rename from src/oshconnect/csapi4py/comm/mqtt.py rename to src/oshconnect/csapi4py/mqtt.py diff --git a/src/oshconnect/csapi4py/part_1/__init__.py b/src/oshconnect/csapi4py/part_1/__init__.py deleted file mode 100644 index dd72804..0000000 --- a/src/oshconnect/csapi4py/part_1/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# import capabilities -# import collections_ep -# import deployments -# import procedures -# import properties -# import sampling_features -# import systems diff --git a/src/oshconnect/csapi4py/part_1/capabilities.py b/src/oshconnect/csapi4py/part_1/capabilities.py deleted file mode 100644 index e094aa5..0000000 --- a/src/oshconnect/csapi4py/part_1/capabilities.py +++ /dev/null @@ -1,31 +0,0 @@ -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def get_landing_page(server_addr: HttpUrl, api_root: str = APITerms.API.value): - """ - Returns the landing page of the API - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .build_url_from_base() - .build()) - return api_request - - -def get_conformance_info(server_addr: HttpUrl, api_root: str = APITerms.API.value): - """ - Returns the conformance information of the API - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.CONFORMANCE.value) - .build_url_from_base() - .build()) - return api_request diff --git a/src/oshconnect/csapi4py/part_1/collections_ep.py b/src/oshconnect/csapi4py/part_1/collections_ep.py deleted file mode 100644 index 0c8dbfc..0000000 --- a/src/oshconnect/csapi4py/part_1/collections_ep.py +++ /dev/null @@ -1,67 +0,0 @@ -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_all_collections(server_addr: HttpUrl, api_root: str = APITerms.API.value): - """ - List all collections - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COLLECTIONS.value) - .build_url_from_base() - .build()) - return api_request - - -def retrieve_collection_metadata(server_addr: HttpUrl, collection_id: str, api_root: str = APITerms.API.value): - """ - Retrieve a collection by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COLLECTIONS.value) - .with_resource_id(collection_id) - .build_url_from_base() - .build()) - return api_request - - -def list_all_items_in_collection(server_addr: HttpUrl, collection_id: str, api_root: str = APITerms.API.value): - """ - Lists all systems in the server at the default API endpoint - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COLLECTIONS.value) - .with_resource_id(collection_id) - .for_sub_resource_type(APITerms.ITEMS.value) - .build_url_from_base() - .build()) - return api_request - - -def retrieve_collection_item_by_id(server_addr: HttpUrl, collection_id: str, item_id: str, - api_root: str = APITerms.API.value): - """ - Retrieves a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COLLECTIONS.value) - .with_resource_id(collection_id) - .for_sub_resource_type(APITerms.ITEMS.value) - .with_resource_id(item_id) - .build_url_from_base() - .build()) - return api_request diff --git a/src/oshconnect/csapi4py/part_1/deployments.py b/src/oshconnect/csapi4py/part_1/deployments.py deleted file mode 100644 index 0f8fe60..0000000 --- a/src/oshconnect/csapi4py/part_1/deployments.py +++ /dev/null @@ -1,217 +0,0 @@ -from typing import Union - -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_all_deployments(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): - """ - Lists all deployments in the server at the default API endpoint - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DEPLOYMENTS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def create_new_deployments(server_addr: HttpUrl, request_body: Union[str, dict], api_root: str = APITerms.API.value, - headers: dict = None): - """ - Create a new deployment as defined by the request body - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DEPLOYMENTS.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('POST') - .build()) - return api_request.make_request() - - -def retrieve_deployment_by_id(server_addr: HttpUrl, deployment_id: str, api_root: str = APITerms.API.value, - headers: dict = None): - """ - Retrieve a deployment by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DEPLOYMENTS.value) - .with_resource_id(deployment_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def update_deployment_by_id(server_addr: HttpUrl, deployment_id: str, request_body: Union[str, dict], - api_root: str = APITerms.API.value, headers: dict = None): - """ - Update a deployment by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DEPLOYMENTS.value) - .with_resource_id(deployment_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - return api_request.make_request() - - -def delete_deployment_by_id(server_addr: HttpUrl, deployment_id: str, api_root: str = APITerms.API.value, - headers: dict = None): - """ - Delete a deployment by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DEPLOYMENTS.value) - .with_resource_id(deployment_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - return api_request - - -def list_deployed_systems(server_addr: HttpUrl, deployment_id, api_root: str = APITerms.API.value, - headers: dict = None): - """ - Lists all deployed systems in the server at the default API endpoint - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DEPLOYMENTS.value) - .with_resource_id(deployment_id) - .for_sub_resource_type(APITerms.SYSTEMS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def add_systems_to_deployment(server_addr: HttpUrl, deployment_id: str, uri_list: str, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Lists all systems in the server at the default API endpoint - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DEPLOYMENTS.value) - .with_resource_id(deployment_id) - .for_sub_resource_type(APITerms.SYSTEMS.value) - .with_request_body(uri_list) - .build_url_from_base() - .with_headers(headers) - .with_request_method('POST') - .build()) - return api_request.make_request() - - -def retrieve_deployed_system_by_id(server_addr: HttpUrl, deployment_id: str, system_id: str, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Retrieves a system by its id - :return: - """ - - # TODO: Add a way to have a secondary resource ID for certain endpoints - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DEPLOYMENTS.value) - .with_resource_id(deployment_id) - .for_sub_resource_type(APITerms.SYSTEMS.value) - .with_secondary_resource_id(system_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def update_deployed_system_by_id(server_addr: HttpUrl, deployment_id: str, system_id: str, request_body: dict, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Update a system by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DEPLOYMENTS.value) - .with_resource_id(deployment_id) - .for_sub_resource_type(APITerms.SYSTEMS.value) - .with_secondary_resource_id(system_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - - return api_request - - -def delete_deployed_system_by_id(server_addr: HttpUrl, deployment_id: str, system_id: str, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Delete a system by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DEPLOYMENTS.value) - .with_resource_id(deployment_id) - .for_sub_resource_type(APITerms.SYSTEMS.value) - .with_secondary_resource_id(system_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - return api_request.make_request() - - -def list_deployments_of_specific_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, - headers: dict = None): - """ - Lists all deployments of a specific system in the server at the default API endpoint - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.DEPLOYMENTS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() diff --git a/src/oshconnect/csapi4py/part_1/procedures.py b/src/oshconnect/csapi4py/part_1/procedures.py deleted file mode 100644 index 41e58a5..0000000 --- a/src/oshconnect/csapi4py/part_1/procedures.py +++ /dev/null @@ -1,97 +0,0 @@ -from typing import Union - -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_all_procedures(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): - """ - Lists all procedures in the server at the default API endpoint - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.PROCEDURES.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def create_new_procedures(server_addr: HttpUrl, request_body: Union[str, dict], api_root: str = APITerms.API.value, - headers: dict = None): - """ - Create a new procedure as defined by the request body - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.PROCEDURES.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('POST') - .build()) - print(api_request) - return api_request.make_request() - - -def retrieve_procedure_by_id(server_addr: HttpUrl, procedure_id: str, api_root: str = APITerms.API.value, - headers: dict = None): - """ - Retrieve a procedure by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.PROCEDURES.value) - .with_resource_id(procedure_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def update_procedure_by_id(server_addr: HttpUrl, procedure_id: str, request_body: Union[str, dict], - api_root: str = APITerms.API.value, headers: dict = None): - """ - Update a procedure by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.PROCEDURES.value) - .with_resource_id(procedure_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - return api_request.make_request() - - -def delete_procedure_by_id(server_addr: HttpUrl, procedure_id: str, api_root: str = APITerms.API.value, - headers: dict = None): - """ - Delete a procedure by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.PROCEDURES.value) - .with_resource_id(procedure_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - return api_request.make_request() diff --git a/src/oshconnect/csapi4py/part_1/properties.py b/src/oshconnect/csapi4py/part_1/properties.py deleted file mode 100644 index 8347424..0000000 --- a/src/oshconnect/csapi4py/part_1/properties.py +++ /dev/null @@ -1,80 +0,0 @@ -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_all_properties(server_addr: HttpUrl, api_root: str = APITerms.API.value): - """ - List all properties - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.PROPERTIES.value) - .build_url_from_base() - .build()) - return api_request - - -def create_new_properties(server_addr: HttpUrl, request_body: dict, api_root: str = APITerms.API.value): - """ - Create a new property as defined by the request body - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.PROPERTIES.value) - .with_request_body(request_body) - .build_url_from_base() - .build()) - return api_request - - -def retrieve_property_by_id(server_addr: HttpUrl, property_id: str, api_root: str = APITerms.API.value): - """ - Retrieve a property by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.PROPERTIES.value) - .with_resource_id(property_id) - .build_url_from_base() - .build()) - return api_request - - -def update_property_by_id(server_addr: HttpUrl, property_id: str, request_body: dict, - api_root: str = APITerms.API.value): - """ - Update a property by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.PROPERTIES.value) - .with_resource_id(property_id) - .with_request_body(request_body) - .build_url_from_base() - .build()) - return api_request - - -def delete_property_by_id(server_addr: HttpUrl, property_id: str, api_root: str = APITerms.API.value): - """ - Delete a property by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.PROPERTIES.value) - .with_resource_id(property_id) - .build_url_from_base() - .build()) - return api_request diff --git a/src/oshconnect/csapi4py/part_1/sampling_features.py b/src/oshconnect/csapi4py/part_1/sampling_features.py deleted file mode 100644 index 5ae3d5c..0000000 --- a/src/oshconnect/csapi4py/part_1/sampling_features.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import Union - -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_all_sampling_features(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers=None): - """ - Lists all sampling features in the server at the default API endpoint - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SAMPLING_FEATURES.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def list_sampling_features_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Lists all sampling features of a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.SAMPLING_FEATURES.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def create_new_sampling_features(server_addr: HttpUrl, system_id: str, request_body: Union[dict, str], - api_root: str = APITerms.API.value, headers=None): - """ - Create a new sampling feature as defined by the request body - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.SAMPLING_FEATURES.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('POST') - .build()) - return api_request.make_request() - - -def retrieve_sampling_feature_by_id(server_addr: HttpUrl, sampling_feature_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Retrieve a sampling feature by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SAMPLING_FEATURES.value) - .with_resource_id(sampling_feature_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def update_sampling_feature_by_id(server_addr: HttpUrl, sampling_feature_id: str, request_body: Union[dict, str], - api_root: str = APITerms.API.value, headers=None): - """ - Update a sampling feature by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SAMPLING_FEATURES.value) - .with_resource_id(sampling_feature_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - return api_request.make_request() - - -def delete_sampling_feature_by_id(server_addr: HttpUrl, sampling_feature_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Delete a sampling feature by its ID - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SAMPLING_FEATURES.value) - .with_resource_id(sampling_feature_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - return api_request.make_request() diff --git a/src/oshconnect/csapi4py/part_1/systems.py b/src/oshconnect/csapi4py/part_1/systems.py deleted file mode 100644 index d746b24..0000000 --- a/src/oshconnect/csapi4py/part_1/systems.py +++ /dev/null @@ -1,213 +0,0 @@ -from typing import Union - -import requests -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms -from oshconnect.csapi4py.request_wrappers import post_request - - -def list_all_systems(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): - """ - Lists all systems in the server at the default API endpoint - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def create_new_systems(server_addr: HttpUrl, request_body: Union[str, dict], api_root: str = APITerms.API.value, - uname: str = None, - pword: str = None, headers: dict = None): - """ - Create a new system as defined by the request body - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_request_body(request_body) - .build_url_from_base() - .with_auth(uname, pword) - .with_headers(headers) - .with_request_method('POST') - .build()) - print(api_request.url) - # resp = requests.post(api_request.url, data=api_request.body, headers=api_request.headers, auth=(uname, pword)) - resp = post_request(api_request.url, api_request.body, api_request.headers, api_request.auth) - print(f'Create new system response: {resp}') - return resp - - -def list_all_systems_in_collection(server_addr: HttpUrl, collection_id: str, api_root: str = APITerms.API.value): - """ - NOTE: function may not be able to fully represent a request to the API at this time, as the test server lacks a few - elements. - Lists all systems in the server at the default API endpoint - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COLLECTIONS.value) - .with_resource_id(collection_id) - # .for_sub_resource_type(APITerms.ITEMS.value) - .build_url_from_base() - .build()) - print(api_request.url) - resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) - return resp.json() - - -def add_systems_to_collection(server_addr: HttpUrl, collection_id: str, uri_list: str, - api_root: str = APITerms.API.value): - """ - Lists all systems in the server at the default API endpoint - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COLLECTIONS.value) - .with_resource_id(collection_id) - .for_sub_resource_type(APITerms.ITEMS.value) - .with_request_body(uri_list) - .build_url_from_base() - .build()) - resp = requests.post(api_request.url, json=api_request.body, headers=api_request.headers) - return resp.json() - - -def retrieve_system_by_id(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): - """ - Retrieves a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .build_url_from_base() - .build()) - resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) - return resp.json() - - -def update_system_description(server_addr: HttpUrl, system_id: str, request_body: str, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Updates a system's description by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .build()) - resp = requests.put(api_request.url, data=request_body, headers=api_request.headers) - return resp - - -def delete_system_by_id(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, headers: dict = None): - """ - Deletes a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - return api_request.make_request() - - -def list_system_components(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): - """ - Lists all components of a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.COMPONENTS.value) - .build_url_from_base() - .build()) - print(api_request.url) - resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) - return resp.json() - - -def add_system_components(server_addr: HttpUrl, system_id: str, request_body: dict, - api_root: str = APITerms.API.value): - """ - Adds components to a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.COMPONENTS.value) - .with_request_body(request_body) - .build_url_from_base() - .build()) - resp = requests.post(api_request.url, params=api_request.body, headers=api_request.headers) - return resp.json() - - -def list_deployments_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): - """ - Lists all deployments of a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.DEPLOYMENTS.value) - .build_url_from_base() - - .build()) - resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) - return resp.json() - - -def list_sampling_features_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value): - """ - Lists all sampling features of a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.SAMPLING_FEATURES.value) - .build_url_from_base() - .build()) - print(api_request.url) - resp = requests.get(api_request.url, params=api_request.body, headers=api_request.headers) - return resp.json() diff --git a/src/oshconnect/csapi4py/part_2/__init__.py b/src/oshconnect/csapi4py/part_2/__init__.py deleted file mode 100644 index 419373c..0000000 --- a/src/oshconnect/csapi4py/part_2/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# import commands -# import control_channels -# import datastreams -# import observations -# import system_events -# import system_history diff --git a/src/oshconnect/csapi4py/part_2/commands.py b/src/oshconnect/csapi4py/part_2/commands.py deleted file mode 100644 index ac26dbf..0000000 --- a/src/oshconnect/csapi4py/part_2/commands.py +++ /dev/null @@ -1,227 +0,0 @@ -from typing import Union - -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_all_commands(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): - """ - Lists all commands - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COMMANDS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def list_commands_of_control_channel(server_addr: HttpUrl, control_channel_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Lists all commands of a control channel - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.CONTROL_STREAMS.value) - .with_resource_id(control_channel_id) - .for_sub_resource_type(APITerms.COMMANDS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def send_commands_to_specific_control_stream(server_addr: HttpUrl, control_stream_id: str, - request_body: Union[dict, str], - api_root: str = APITerms.API.value, headers=None): - """ - Sends a command to a control stream by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.CONTROL_STREAMS.value) - .with_resource_id(control_stream_id) - .for_sub_resource_type(APITerms.COMMANDS.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('POST') - .build()) - - return api_request.make_request() - - -def retrieve_command_by_id(server_addr: HttpUrl, command_id: str, api_root: str = APITerms.API.value, headers=None): - """ - Retrieves a command by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COMMANDS.value) - .with_resource_id(command_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def update_command_description(server_addr: HttpUrl, command_id: str, request_body: Union[dict, str], - api_root: str = APITerms.API.value, headers=None): - """ - Updates a command's description by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COMMANDS.value) - .with_resource_id(command_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - - return api_request.make_request() - - -def delete_command_by_id(server_addr: HttpUrl, command_id: str, api_root: str = APITerms.API.value, headers=None): - """ - Deletes a command by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COMMANDS.value) - .with_resource_id(command_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - - return api_request.make_request() - - -def list_command_status_reports(server_addr: HttpUrl, command_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Lists all status reports of a command by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COMMANDS.value) - .with_resource_id(command_id) - .for_sub_resource_type(APITerms.STATUS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def add_command_status_reports(server_addr: HttpUrl, command_id: str, request_body: Union[dict, str], - api_root: str = APITerms.API.value, headers=None): - """ - Adds a status report to a command by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COMMANDS.value) - .with_resource_id(command_id) - .for_sub_resource_type(APITerms.STATUS.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('POST') - .build()) - - return api_request.make_request() - - -def retrieve_command_status_report_by_id(server_addr: HttpUrl, command_id: str, status_report_id: str, - api_root: str = APITerms.API.value, headers=None): - """ - Retrieves a status report of a command by its id and status report id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COMMANDS.value) - .with_resource_id(command_id) - .for_sub_resource_type(APITerms.STATUS.value) - .with_secondary_resource_id(status_report_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def update_command_status_report_by_id(server_addr: HttpUrl, command_id: str, status_report_id: str, - request_body: Union[dict, str], api_root: str = APITerms.API.value, - headers=None): - """ - Updates a status report of a command by its id and status report id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COMMANDS.value) - .with_resource_id(command_id) - .for_sub_resource_type(APITerms.STATUS.value) - .with_secondary_resource_id(status_report_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - - return api_request.make_request() - - -def delete_command_status_report_by_id(server_addr: HttpUrl, command_id: str, status_report_id: str, - api_root: str = APITerms.API.value, headers=None): - """ - Deletes a status report of a command by its id and status report id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.COMMANDS.value) - .with_resource_id(command_id) - .for_sub_resource_type(APITerms.STATUS.value) - .with_secondary_resource_id(status_report_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - - return api_request.make_request() diff --git a/src/oshconnect/csapi4py/part_2/control_channels.py b/src/oshconnect/csapi4py/part_2/control_channels.py deleted file mode 100644 index 3b4d995..0000000 --- a/src/oshconnect/csapi4py/part_2/control_channels.py +++ /dev/null @@ -1,159 +0,0 @@ -from typing import Union - -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_all_control_streams(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): - """ - Lists all control streams - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.CONTROL_STREAMS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def list_control_streams_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Lists all control streams of a system - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.CONTROL_STREAMS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def add_control_streams_to_system(server_addr: HttpUrl, system_id: str, request_body: Union[str, dict], - api_root: str = APITerms.API.value, headers=None): - """ - Adds a control stream to a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.CONTROL_STREAMS.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('POST') - .build()) - return api_request.make_request() - - -def retrieve_control_stream_description_by_id(server_addr: HttpUrl, control_stream_id: str, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Retrieves a control stream by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.CONTROL_STREAMS.value) - .with_resource_id(control_stream_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def update_control_stream_description_by_id(server_addr: HttpUrl, control_stream_id: str, - request_body: Union[str, dict], - api_root: str = APITerms.API.value, headers: dict = None): - """ - Updates a control stream by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.CONTROL_STREAMS.value) - .with_resource_id(control_stream_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - return api_request.make_request() - - -def delete_control_stream_by_id(server_addr: HttpUrl, control_stream_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Deletes a control stream by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.CONTROL_STREAMS.value) - .with_resource_id(control_stream_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - - return api_request.make_request() - - -def retrieve_control_stream_schema_by_id(server_addr: HttpUrl, control_stream_id: str, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Retrieves a control stream schema by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.CONTROL_STREAMS.value) - .with_resource_id(control_stream_id) - .for_sub_resource_type(APITerms.SCHEMA.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def update_control_stream_schema_by_id(server_addr: HttpUrl, control_stream_id: str, request_body: Union[str, dict], - api_root: str = APITerms.API.value, headers: dict = None): - """ - Updates a control stream schema by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.CONTROL_STREAMS.value) - .with_resource_id(control_stream_id) - # .for_sub_resource_type(APITerms.SCHEMA.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - return api_request.make_request() diff --git a/src/oshconnect/csapi4py/part_2/datastreams.py b/src/oshconnect/csapi4py/part_2/datastreams.py deleted file mode 100644 index 3a21b3a..0000000 --- a/src/oshconnect/csapi4py/part_2/datastreams.py +++ /dev/null @@ -1,155 +0,0 @@ -from typing import Union - -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_all_datastreams(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): - """ - Lists all datastreams - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DATASTREAMS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def list_all_datastreams_of_system(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Lists all datastreams of a system - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.DATASTREAMS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def add_datastreams_to_system(server_addr: HttpUrl, system_id: str, request_body: Union[str, dict], - api_root: str = APITerms.API.value, headers=None): - """ - Adds a datastream to a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.DATASTREAMS.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('POST') - .build()) - return api_request.make_request() - - -def retrieve_datastream_by_id(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Retrieves a datastream by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DATASTREAMS.value) - .with_resource_id(datastream_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def update_datastream_by_id(server_addr: HttpUrl, datastream_id: str, request_body: Union[str, dict], - api_root: str = APITerms.API.value, headers=None): - """ - Updates a datastream by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DATASTREAMS.value) - .with_resource_id(datastream_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - return api_request.make_request() - - -def delete_datastream_by_id(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, headers=None): - """ - Deletes a datastream by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DATASTREAMS.value) - .with_resource_id(datastream_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - return api_request.make_request() - - -def retrieve_datastream_schema(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Retrieves a datastream schema by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DATASTREAMS.value) - .with_resource_id(datastream_id) - .for_sub_resource_type(APITerms.SCHEMA.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def update_datastream_schema(server_addr: HttpUrl, datastream_id: str, request_body: dict, - api_root: str = APITerms.API.value, headers=None): - """ - Updates a datastream schema by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DATASTREAMS.value) - .with_resource_id(datastream_id) - .for_resource_type(APITerms.SCHEMA.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - return api_request.make_request() diff --git a/src/oshconnect/csapi4py/part_2/observations.py b/src/oshconnect/csapi4py/part_2/observations.py deleted file mode 100644 index 788407b..0000000 --- a/src/oshconnect/csapi4py/part_2/observations.py +++ /dev/null @@ -1,122 +0,0 @@ -from typing import Union - -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_all_observations(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers=None): - """ - Lists all observations - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.OBSERVATIONS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def list_observations_from_datastream(server_addr: HttpUrl, datastream_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Lists all observations of a datastream - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DATASTREAMS.value) - .with_resource_id(datastream_id) - .for_sub_resource_type(APITerms.OBSERVATIONS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def add_observations_to_datastream(server_addr: HttpUrl, datastream_id: str, request_body: Union[str, dict], - api_root: str = APITerms.API.value, headers=None): - """ - Adds an observation to a datastream by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.DATASTREAMS.value) - .with_resource_id(datastream_id) - .for_sub_resource_type(APITerms.OBSERVATIONS.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('POST') - .build()) - - return api_request.make_request() - - -def retrieve_observation_by_id(server_addr: HttpUrl, observation_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Retrieves an observation by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.OBSERVATIONS.value) - .with_resource_id(observation_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - - return api_request.make_request() - - -def update_observation_by_id(server_addr: HttpUrl, observation_id: str, request_body: Union[str, dict], - api_root: str = APITerms.API.value, headers=None): - """ - Updates an observation by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.OBSERVATIONS.value) - .with_resource_id(observation_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - - return api_request.make_request() - - -def delete_observation_by_id(server_addr: HttpUrl, observation_id: str, api_root: str = APITerms.API.value, - headers=None): - """ - Deletes an observation by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.OBSERVATIONS.value) - .with_resource_id(observation_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - - return api_request.make_request() diff --git a/src/oshconnect/csapi4py/part_2/system_events.py b/src/oshconnect/csapi4py/part_2/system_events.py deleted file mode 100644 index afbbb47..0000000 --- a/src/oshconnect/csapi4py/part_2/system_events.py +++ /dev/null @@ -1,122 +0,0 @@ -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_system_events(server_addr: HttpUrl, api_root: str = APITerms.API.value, headers: dict = None): - """ - Lists all system events - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEM_EVENTS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def list_events_by_system_id(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, - headers: dict = None): - """ - Lists all events of a system - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.EVENTS.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def add_new_system_events(server_addr: HttpUrl, system_id: str, request_body: dict, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Adds a new system event to a system by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.EVENTS.value) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('POST') - .build()) - return api_request.make_request() - - -def retrieve_system_event_by_id(server_addr: HttpUrl, system_id: str, event_id: str, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Retrieves a system event by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.EVENTS.value) - .with_secondary_resource_id(event_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def update_system_event_by_id(server_addr: HttpUrl, system_id: str, event_id: str, request_body: dict, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Updates a system event by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.EVENTS.value) - - .with_secondary_resource_id(event_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - return api_request.make_request() - - -def delete_system_event_by_id(server_addr: HttpUrl, system_id: str, event_id: str, api_root: str = APITerms.API.value, - headers: dict = None): - """ - Deletes a system event by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_sub_resource_type(APITerms.EVENTS.value) - .with_secondary_resource_id(event_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - - return api_request.make_request() diff --git a/src/oshconnect/csapi4py/part_2/system_history.py b/src/oshconnect/csapi4py/part_2/system_history.py deleted file mode 100644 index dc73c5c..0000000 --- a/src/oshconnect/csapi4py/part_2/system_history.py +++ /dev/null @@ -1,83 +0,0 @@ -from pydantic import HttpUrl - -from oshconnect.csapi4py.con_sys_api import ConnectedSystemsRequestBuilder -from oshconnect.csapi4py.constants import APITerms - - -def list_system_history(server_addr: HttpUrl, system_id: str, api_root: str = APITerms.API.value, headers: dict = None): - """ - Lists all history versions of a system - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_resource_type(APITerms.HISTORY.value) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def retrieve_system_historical_description_by_id(server_addr: HttpUrl, system_id: str, history_id: str, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Retrieves a historical system description by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_resource_type(APITerms.HISTORY.value) - .with_secondary_resource_id(history_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('GET') - .build()) - return api_request.make_request() - - -def update_system_historical_description(server_addr: HttpUrl, system_id: str, history_rev_id: str, request_body: dict, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Updates a historical system description by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_resource_type(APITerms.HISTORY.value) - .with_secondary_resource_id(history_rev_id) - .with_request_body(request_body) - .build_url_from_base() - .with_headers(headers) - .with_request_method('PUT') - .build()) - return api_request.make_request() - - -def delete_system_historical_description_by_id(server_addr: HttpUrl, system_id: str, history_rev_id: str, - api_root: str = APITerms.API.value, headers: dict = None): - """ - Deletes a historical system description by its id - :return: - """ - builder = ConnectedSystemsRequestBuilder() - api_request = (builder.with_server_url(server_addr) - .with_api_root(api_root) - .for_resource_type(APITerms.SYSTEMS.value) - .with_resource_id(system_id) - .for_resource_type(APITerms.HISTORY.value) - .with_secondary_resource_id(history_rev_id) - .build_url_from_base() - .with_headers(headers) - .with_request_method('DELETE') - .build()) - return api_request.make_request() diff --git a/src/oshconnect/csapi4py/request_bodies.py b/src/oshconnect/csapi4py/request_bodies.py index c9bbb93..8291f15 100644 --- a/src/oshconnect/csapi4py/request_bodies.py +++ b/src/oshconnect/csapi4py/request_bodies.py @@ -2,10 +2,10 @@ from pydantic import BaseModel, HttpUrl, Field, model_serializer, RootModel, SerializeAsAny -from oshconnect.csapi4py.constants import DatastreamResultTypes +from .constants import DatastreamResultTypes from oshconnect.datamodels.datastreams import DatastreamSchema from oshconnect.datamodels.geometry import Geometry -from oshconnect.csapi4py.sensor_ml.sml import TypeOf +from .sensor_ml.sml import TypeOf # TODO: Consider some sort of Abstract Base Class for all valid request bodies to inherit from to reduce the complexity @@ -70,7 +70,7 @@ class DatastreamBodyJSON(BaseModel): name: str = Field(...) description: str = Field(None) deployment: HttpUrl = Field(None, serialization_alias='deployment@link') - ultimate_feature_of_interest: HttpUrl = Field(None, serialization_alias='ultimateFeatureOfInterest@link') + ultimate_feature_of_interest: HttpUrl = Field(None, serialization_alias='featureOfInterest@link') sampling_feature: HttpUrl = Field(None, serialization_alias='samplingFeature@link') valid_time: list = Field(None, serialization_alias='validTime') output_name: str = Field(..., serialization_alias='outputName') diff --git a/src/oshconnect/csapi4py/sensor_ml/__init__.py b/src/oshconnect/csapi4py/sensor_ml/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/oshconnect/csapi4py/sensor_ml/sml.py b/src/oshconnect/csapi4py/sml.py similarity index 100% rename from src/oshconnect/csapi4py/sensor_ml/sml.py rename to src/oshconnect/csapi4py/sml.py diff --git a/src/oshconnect/csapi4py/utilities/__init__.py b/src/oshconnect/csapi4py/utilities/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/oshconnect/datamodels/__init__.py b/src/oshconnect/datamodels/__init__.py deleted file mode 100644 index 96c9d10..0000000 --- a/src/oshconnect/datamodels/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# import api_utils -# import commands -# import control_streams -# import datastreams -# import encoding -# import geometry -# import network_properties -# import observations -# import swe_components -# import system_events_and_history diff --git a/src/oshconnect/datamodels/commands.py b/src/oshconnect/datamodels/commands.py deleted file mode 100644 index 999b480..0000000 --- a/src/oshconnect/datamodels/commands.py +++ /dev/null @@ -1,14 +0,0 @@ -from datetime import datetime -from typing import Union - -from pydantic import BaseModel, Field - - -class CommandJSON(BaseModel): - """ - A class to represent a command in JSON format - """ - control_id: str = Field(None, serialization_alias="control@id") - issue_time: Union[str, float] = Field(datetime.now().isoformat(), serialization_alias="issueTime") - sender: str = Field(None) - params: Union[dict, list, int, float, str] = Field(None) diff --git a/src/oshconnect/datamodels/control_streams.py b/src/oshconnect/datamodels/control_streams.py deleted file mode 100644 index 8ee8eb5..0000000 --- a/src/oshconnect/datamodels/control_streams.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import annotations - -from typing import Union - -from pydantic import BaseModel, Field, SerializeAsAny - -from oshconnect.datamodels.encoding import Encoding -from oshconnect.datamodels.swe_components import AnyComponentSchema - - -class ControlStreamJSONSchema(BaseModel): - """ - A class to represent the schema of a control stream - """ - id: str = Field(None) - name: str = Field(...) - description: str = Field(None) - deployment_link: str = Field(None, serialization_alias='deployment@link') - ultimate_feature_of_interest_link: str = Field(None, serialization_alias='ultimateFeatureOfInterest@link') - sampling_feature_link: str = Field(None, alias='samplingFeature@link') - valid_time: list = Field(None, serialization_alias='validTime') - input_name: str = Field(None, serialization_alias='inputName') - links: list = Field(None) - control_stream_schema: SerializeAsAny[Union[SWEControlChannelSchema, JSONControlChannelSchema]] = Field(..., - serialization_alias='schema') - - -class SWEControlChannelSchema(BaseModel): - """ - A class to represent the schema of a control channel - """ - command_format: str = Field("application/swe+json", serialization_alias='commandFormat') - encoding: SerializeAsAny[Encoding] = Field(...) - record_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='recordSchema') - - -class JSONControlChannelSchema(BaseModel): - command_format: str = Field("application/cmd+json", serialization_alias='commandFormat') - params_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='paramsSchema') diff --git a/src/oshconnect/datamodels/datastreams.py b/src/oshconnect/datamodels/datastreams.py deleted file mode 100644 index 11d3986..0000000 --- a/src/oshconnect/datamodels/datastreams.py +++ /dev/null @@ -1,25 +0,0 @@ -from pydantic import BaseModel, Field, field_validator, SerializeAsAny - -from oshconnect.csapi4py.constants import ObservationFormat -from oshconnect.datamodels.encoding import Encoding -from oshconnect.datamodels.swe_components import AnyComponentSchema - - -class DatastreamSchema(BaseModel): - """ - A class to represent the schema of a datastream - """ - obs_format: str = Field(..., serialization_alias='obsFormat') - - -class SWEDatastreamSchema(DatastreamSchema): - encoding: SerializeAsAny[Encoding] = Field(...) - record_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='recordSchema') - - @field_validator('obs_format') - @classmethod - def check_check_obs_format(cls, v): - if v not in [ObservationFormat.SWE_JSON.value, ObservationFormat.SWE_CSV.value, - ObservationFormat.SWE_TEXT.value, ObservationFormat.SWE_BINARY.value]: - raise ValueError('obsFormat must be on of the SWE formats') - return v diff --git a/src/oshconnect/datamodels/observations.py b/src/oshconnect/datamodels/observations.py deleted file mode 100644 index 3de9dee..0000000 --- a/src/oshconnect/datamodels/observations.py +++ /dev/null @@ -1,19 +0,0 @@ -from datetime import datetime -from typing import Union, List - -from pydantic import BaseModel, Field - -from oshconnect.datamodels.api_utils import Link - - -class ObservationOMJSONInline(BaseModel): - """ - A class to represent an observation in OM-JSON format - """ - datastream_id: str = Field(None, serialization_alias="datastream@id") - foi_id: str = Field(None, serialization_alias="foi@id") - phenomenon_time: str = Field(None, serialization_alias="phenomenonTime") - result_time: str = Field(datetime.now().isoformat(), serialization_alias="resultTime") - parameters: dict = Field(None) - result: Union[int, float, str, dict, list] = Field(...) - result_links: List[Link] = Field(None, serialization_alias="result@links") diff --git a/src/oshconnect/datamodels/system_events_and_history.py b/src/oshconnect/datamodels/system_events_and_history.py deleted file mode 100644 index f0801ec..0000000 --- a/src/oshconnect/datamodels/system_events_and_history.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import annotations - -from pydantic import BaseModel, Field, HttpUrl - -from oshconnect.datamodels.api_utils import Link, URI -from oshconnect.datamodels.geometry import Geometry - - -class SystemEventOMJSON(BaseModel): - """ - A class to represent the schema of a system event - """ - label: str = Field(...) - description: str = Field(None) - definition: HttpUrl = Field(...) - identifiers: list = Field(None) - classifiers: list = Field(None) - contacts: list = Field(None) - documentation: list = Field(None) - time: str = Field(...) - properties: list = Field(None) - configuration: dict = Field(None) - links: list[Link] = Field(None) - - -class SystemHistoryGeoJSON(BaseModel): - """ - A class to represent the schema of a system history - """ - type: str = Field(...) - id: str = Field(None) - properties: SystemHistoryProperties = Field(...) - geometry: Geometry = Field(None) - bbox: list = Field(None) - links: list[Link] = Field(None) - - -class SystemHistoryProperties(BaseModel): - feature_type: str = Field(...) - uid: URI = Field(...) - name: str = Field(...) - description: str = Field(None) - asset_type: str = Field(None) - valid_time: list = Field(None) - parent_system_link: str = Field(None, serialization_alias='parentSystem@link') - procedure_link: str = Field(None, serialization_alias='procedure@link') diff --git a/src/oshconnect/datasource.py b/src/oshconnect/datasource.py index ccd0f9b..b3cc637 100644 --- a/src/oshconnect/datasource.py +++ b/src/oshconnect/datasource.py @@ -16,11 +16,11 @@ import requests import websockets -from oshconnect.csapi4py.constants import APIResourceTypes -from oshconnect.datamodels.observations import ObservationOMJSONInline -from oshconnect.datamodels.swe_components import DataRecordSchema +from .csapi4py.constants import APIResourceTypes +from .schema_datamodels import ObservationOMJSONInline +from .swe_components import DataRecordSchema -from .core_datamodels import DatastreamResource, SystemResource, TimePeriod +from .resource_datamodels import DatastreamResource, TimePeriod from .timemanagement import TemporalModes, Synchronizer @@ -29,7 +29,8 @@ class DataStream: """ - DataSource: represents the active connection of a datastream object. + To Be Deprecated with next minor prerelease version of OSHConnect + DataStream: represents the active connection of a datastream object. This class may later be used to connect to a control channel as well. It will almost certainly be used for Control Stream status monitoring. @@ -41,7 +42,7 @@ class DataStream: name: str = None _id: str = None _datastream: DatastreamResource = None - _parent_system: SystemResource = None + # _parent_system: SystemResource = None _playback_mode: TemporalModes = None _url: str = None _auth: str = None @@ -50,8 +51,7 @@ class DataStream: _result_schema: DataRecordSchema = None _synchronizer: Synchronizer = None - def __init__(self, name: str, datastream: DatastreamResource, - parent_system: SystemResource): + def __init__(self, name: str, datastream: DatastreamResource): """ :param name: Human-readable name of the DataSource :param datastream: DatastreamResource object @@ -62,23 +62,14 @@ def __init__(self, name: str, datastream: DatastreamResource, self.name = name self._datastream = datastream self._playback_websocket = None - self._parent_system = parent_system + # self._parent_system = parent_system self._playback_mode = None self._url = None self._auth = None self._extra_headers = None - if self._parent_system.get_parent_node().is_secure: - self._auth = self._parent_system.get_parent_node().get_decoded_auth() - self._extra_headers = {'Authorization': f'Basic {self._auth}'} - # get result schema - - # t_url = f'http://{self._parent_system.get_parent_node().get_address()}:{self._parent_system.get_parent_node().get_port()}' - # - # res = consys4py.part_2.datastreams.retrieve_datastream_schema(t_url, - # datastream_id=self._datastream.ds_id, - # api_root=self._parent_system.get_parent_node()._api_helper.api_root, - # headers=self._extra_headers) - # print(res.json()) + # if self._parent_system._parent_node().is_secure: + # self._auth = self._parent_system._parent_node().get_decoded_auth() + # self._extra_headers = {'Authorization': f'Basic {self._auth}'} def get_id(self) -> str: """ @@ -96,41 +87,16 @@ def get_name(self): """ return self.name - def create_process(self): - """ - **Unimplemented** - - Create a process for the DataSource - - :return: - """ - pass - - def terminate_process(self): - """ - **Unimplemented** - """ - pass - - # Might not be necessary - def subscribe(self): - """ - **Unimplemented** - - :return: - """ - pass - - def set_playback_mode(self, mode: TemporalModes): - """ - Sets the playback mode of the DataSource and regenerates the URL accordingly - - :param mode: TemporalModes - - :return: - """ - self._playback_mode = mode - self.generate_retrieval_url() + # def set_playback_mode(self, mode: TemporalModes): + # """ + # Sets the playback mode of the DataSource and regenerates the URL accordingly + # + # :param mode: TemporalModes + # + # :return: + # """ + # self._playback_mode = mode + # self.generate_retrieval_url() def initialize(self): """ @@ -193,13 +159,13 @@ def get_status(self): """ return self._status - def get_parent_system(self) -> SystemResource: - """ - Retrieve the DataSource's parent System - - :return: The parent System object of the DataSource - """ - return self._parent_system + # def get_parent_system(self) -> SystemResource: + # """ + # Retrieve the DataSource's parent System + # + # :return: The parent System object of the DataSource + # """ + # return self._parent_system def get_ws_client(self): """ @@ -309,7 +275,7 @@ def add_datasource(self, datasource: DataStream): :return: """ - datasource.set_playback_mode(self._playback_mode) + # datasource.set_playback_mode(self._playback_mode) self.datasource_map[datasource.get_id()] = datasource def remove_datasource(self, datasource_id: str) -> DataStream: diff --git a/src/oshconnect/datamodels/encoding.py b/src/oshconnect/encoding.py similarity index 100% rename from src/oshconnect/datamodels/encoding.py rename to src/oshconnect/encoding.py diff --git a/src/oshconnect/datamodels/geometry.py b/src/oshconnect/geometry.py similarity index 51% rename from src/oshconnect/datamodels/geometry.py rename to src/oshconnect/geometry.py index ced5988..5123e1c 100644 --- a/src/oshconnect/datamodels/geometry.py +++ b/src/oshconnect/geometry.py @@ -1,6 +1,13 @@ +# ============================================================================= +# Copyright (c) 2025 Botts Innovative Research Inc. +# Date: 2025/10/1 +# Author: Ian Patterson +# Contact Email: ian@botts-inc.com +# ============================================================================= + from pydantic import BaseModel, Field -from oshconnect.csapi4py.constants import GeometryTypes +from .csapi4py.constants import GeometryTypes # TODO: Add specific validations for each type diff --git a/src/oshconnect/datamodels/network_properties.py b/src/oshconnect/network_properties.py similarity index 100% rename from src/oshconnect/datamodels/network_properties.py rename to src/oshconnect/network_properties.py diff --git a/src/oshconnect/oshconnectapi.py b/src/oshconnect/oshconnectapi.py index 0a24efe..46506af 100644 --- a/src/oshconnect/oshconnectapi.py +++ b/src/oshconnect/oshconnectapi.py @@ -7,13 +7,13 @@ import logging import shelve -from oshconnect.csapi4py.core.default_api_helpers import APIHelper -from .core_datamodels import DatastreamResource, TimePeriod -from .datasource import DataStream, DataStreamHandler, MessageWrapper +from .csapi4py.default_api_helpers import APIHelper +from .resource_datamodels import DatastreamResource +from .datasource import DataStream, MessageWrapper from .datastore import DataStore -from .osh_connect_datamodels import Node, System +from .streamableresource import Node, System, SessionManager, Datastream from .styling import Styling -from .timemanagement import TemporalModes, TimeManagement +from .timemanagement import TemporalModes, TimeManagement, TimePeriod class OSHConnect: @@ -24,30 +24,31 @@ class OSHConnect: _nodes: list[Node] = [] _systems: list[System] = [] _cs_api_builder: APIHelper = None - _datasource_handler: DataStreamHandler = None + # _datasource_handler: DataStreamHandler = None _datastreams: list[DataStream] = [] _datataskers: list[DataStore] = [] _datagroups: list = [] _tasks: list = [] _playback_mode: TemporalModes = TemporalModes.REAL_TIME + _session_manager: SessionManager = None def __init__(self, name: str, **kwargs): """ :param name: name of the OSHConnect instance, in the event that :param kwargs: - - 'playback_mode': TemporalModes """ self._name = name - if 'nodes' in kwargs: - self._nodes = kwargs['nodes'] - self._playback_mode = kwargs['playback_mode'] - self._datasource_handler.set_playback_mode(self._playback_mode) - self._datasource_handler = DataStreamHandler() - if 'playback_mode' in kwargs: - self._playback_mode = kwargs['playback_mode'] - self._datasource_handler.set_playback_mode(self._playback_mode) + # if 'nodes' in kwargs: + # self._nodes = kwargs['nodes'] + # self._playback_mode = kwargs['playback_mode'] + # self._datasource_handler.set_playback_mode(self._playback_mode) + # self._datasource_handler = DataStreamHandler() + # if 'playback_mode' in kwargs: + # self._playback_mode = kwargs['playback_mode'] + # self._datasource_handler.set_playback_mode(self._playback_mode) logging.info(f"OSHConnect instance {name} created") + self._session_manager = SessionManager() def get_name(self): """ @@ -62,6 +63,7 @@ def add_node(self, node: Node): :param node: Node object :return: """ + node.register_with_session_manager(self._session_manager) self._nodes.append(node) def remove_node(self, node_id: str): @@ -135,7 +137,7 @@ def visualize_streams(self, streams: list): def get_visualization_recommendations(self, streams: list): pass - def discover_datastreams(self): + def dep_discover_datastreams(self): """ Discover datastreams of the current systems of the OSHConnect instance and create objects for them that are stored in the DataSourceHandler. @@ -146,12 +148,18 @@ def discover_datastreams(self): res_datastreams = system.discover_datastreams() # create DataSource(s) new_datasource = [ - DataStream(name=ds.name, datastream=ds, parent_system=system) + DataStream(name=ds.name, datastream=ds, parent_system=system.get_system_resource()) for ds in res_datastreams] self._datastreams.extend(new_datasource) list(map(self._datasource_handler.add_datasource, new_datasource)) + def discover_datastreams(self): + for system in self._systems: + res_datastreams = system.discover_datastreams() + datastreams = list(map(lambda ds: Datastream(parent_node=system.get_parent_node(), id=ds.ds_id, datastream_resource=ds), res_datastreams)) + self._datastreams.extend(datastreams) + def discover_systems(self, nodes: list[str] = None): """ Discover systems from the nodes that have been added to the OSHConnect instance. They are associated with the @@ -206,12 +214,12 @@ def _insert_system(self, system: System, target_node: Node): :return: the created system """ if target_node in self._nodes: - self.add_system(system, target_node, insert_resource=True) + self.add_system_to_node(system, target_node, insert_resource=True) return system - def insert_datastream(self, datastream: DatastreamResource, system: str | System) -> str: + def add_datastream(self, datastream: DatastreamResource, system: str | System) -> str: """ - Insert a datastream into the OSHConnect instance. + Adds a datastream into the OSHConnect instance. :param datastream: DataSource object :param system: System object or system id :return: @@ -240,7 +248,7 @@ def find_system(self, system_id: str) -> System | None: return None # System Management - def add_system(self, system: System, target_node: Node, insert_resource: bool = False): + def add_system_to_node(self, system: System, target_node: Node, insert_resource: bool = False): """ Add a system to the target node. :param system: System object @@ -264,7 +272,7 @@ def create_and_insert_system(self, system_opts: dict, target_node: Node): """ if target_node in self._nodes: new_system = System(**system_opts) - self.add_system(new_system, target_node, insert_resource=True) + self.add_system_to_node(new_system, target_node, insert_resource=True) return new_system def remove_system(self, system_id: str): @@ -273,3 +281,11 @@ def remove_system(self, system_id: str): # DataStream Helpers def get_datastreams(self) -> list[DataStream]: return self._datastreams + + def connect_session_streams(self, session_id: str): + """ + Connects all datastreams that are associated with the given session ID. + :param session_id: + :return: + """ + self._session_manager.start_session_streams(session_id) diff --git a/src/oshconnect/core_datamodels.py b/src/oshconnect/resource_datamodels.py similarity index 51% rename from src/oshconnect/core_datamodels.py rename to src/oshconnect/resource_datamodels.py index 83fced5..7217fde 100644 --- a/src/oshconnect/core_datamodels.py +++ b/src/oshconnect/resource_datamodels.py @@ -7,14 +7,13 @@ from __future__ import annotations from typing import List - -from oshconnect.datamodels.geometry import Geometry -from oshconnect.datamodels.datastreams import DatastreamSchema -from oshconnect.datamodels.api_utils import Link -from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny +from .geometry import Geometry +from .api_utils import Link +from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny, model_validator from shapely import Point -from oshconnect.timemanagement import DateTimeSchema, TimePeriod +from .schema_datamodels import DatastreamRecordSchema +from .timemanagement import DateTimeSchema, TimePeriod class BoundingBox(BaseModel): @@ -88,34 +87,44 @@ class ProcessMethod: method: list +class BaseResource(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + + id: str = Field(..., alias="id") + name: str = Field(...) + description: str = Field(None) + type: str = Field(None) + links: List[Link] = Field(None) + + class SystemResource(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) - feature_type: str = Field(None, serialization_alias="type") - system_id: str = Field(None, serialization_alias="id") + feature_type: str = Field(None, alias="type") + system_id: str = Field(None, alias="id") properties: dict = Field(None) geometry: Geometry | None = Field(None) bbox: BoundingBox = Field(None) links: List[Link] = Field(None) description: str = Field(None) - uid: str = Field(None, serialization_alias="uniqueId") + uid: str = Field(None, alias="uniqueId") label: str = Field(None) lang: str = Field(None) keywords: List[str] = Field(None) identifiers: List[str] = Field(None) classifiers: List[str] = Field(None) - valid_time: DateTimeSchema = Field(None, serialization_alias="validTime") - security_constraints: List[SecurityConstraints] = Field(None, serialization_alias="securityConstraints") - legal_constraints: List[LegalConstraints] = Field(None, serialization_alias="legalConstraints") + valid_time: DateTimeSchema = Field(None, alias="validTime") + security_constraints: List[SecurityConstraints] = Field(None, alias="securityConstraints") + legal_constraints: List[LegalConstraints] = Field(None, alias="legalConstraints") characteristics: List[Characteristics] = Field(None) capabilities: List[Capabilities] = Field(None) contacts: List[Contact] = Field(None) documentation: List[Documentation] = Field(None) history: List[HistoryEvent] = Field(None) definition: str = Field(None) - type_of: str = Field(None, serialization_alias="typeOf") + type_of: str = Field(None, alias="typeOf") configuration: ConfigurationSettings = Field(None) - features_of_interest: List[FeatureOfInterest] = Field(None, serialization_alias="featuresOfInterest") + features_of_interest: List[FeatureOfInterest] = Field(None, alias="featuresOfInterest") inputs: List[Input] = Field(None) outputs: List[Output] = Field(None) parameters: List[Parameter] = Field(None) @@ -132,29 +141,62 @@ class DatastreamResource(BaseModel): """ # model_config = ConfigDict(populate_by_name=True) - ds_id: str = Field(..., serialization_alias="id") + ds_id: str = Field(..., alias="id") name: str = Field(...) description: str = Field(None) - valid_time: TimePeriod = Field(..., serialization_alias="validTime") - output_name: str = Field(None, serialization_alias="outputName") - procedure_link: Link = Field(None, serialization_alias="procedureLink@link") - deployment_link: Link = Field(None, serialization_alias="deploymentLink@link") - ultimate_feature_of_interest_link: Link = Field(None, serialization_alias="ultimateFeatureOfInterest@link") - sampling_feature_link: Link = Field(None, serialization_alias="samplingFeature@link") + valid_time: TimePeriod = Field(..., alias="validTime") + output_name: str = Field(None, alias="outputName") + procedure_link: Link = Field(None, alias="procedureLink@link") + deployment_link: Link = Field(None, alias="deploymentLink@link") + feature_of_interest_link: Link = Field(None, alias="featureOfInterest@link") + sampling_feature_link: Link = Field(None, alias="samplingFeature@link") parameters: dict = Field(None) - phenomenon_time: TimePeriod = Field(None, serialization_alias="phenomenonTimeInterval") - result_time: TimePeriod = Field(None, serialization_alias="resultTimeInterval") - ds_type: str = Field(None, serialization_alias="type") - result_type: str = Field(None, serialization_alias="resultType") + phenomenon_time: TimePeriod = Field(None, alias="phenomenonTimeInterval") + result_time: TimePeriod = Field(None, alias="resultTimeInterval") + ds_type: str = Field(None, alias="type") + result_type: str = Field(None, alias="resultType") links: List[Link] = Field(None) - record_schema: SerializeAsAny[DatastreamSchema] = Field(None, serialization_alias="schema") + record_schema: SerializeAsAny[DatastreamRecordSchema] = Field(None, alias="schema") + + @classmethod + @model_validator(mode="before") + def handle_aliases(cls, values): + if isinstance(values, dict): + if 'ds_id' not in values: + for alias in ('id', 'datastream_id'): + if alias in values: + values['ds_id'] = values[alias] + break + if 'valid_time' not in values: + for alias in ('validTime', 'time_interval'): + if alias in values: + values['valid_time'] = values[alias] + break + return values class ObservationResource(BaseModel): - sampling_feature_id: str = Field(None, serialization_alias="samplingFeature@Id") - procedure_link: Link = Field(None, serialization_alias="procedure@link") - phenomenon_time: DateTimeSchema = Field(None, serialization_alias="phenomenonTime") - result_time: DateTimeSchema = Field(..., serialization_alias="resultTime") + sampling_feature_id: str = Field(None, alias="samplingFeature@Id") + procedure_link: Link = Field(None, alias="procedure@link") + phenomenon_time: DateTimeSchema = Field(None, alias="phenomenonTime") + result_time: DateTimeSchema = Field(..., alias="resultTime") parameters: dict = Field(None) result: dict = Field(...) - result_link: Link = Field(None, serialization_alias="result@link") + result_link: Link = Field(None, alias="result@link") + + +class ControlStreamResource(BaseModel): + name: str = Field(...) + description: str = Field(None) + valid_time: TimePeriod = Field(..., alias="validTime") + input_name: str = Field(None, alias="inputName") + procedure_link: Link = Field(None, alias="procedureLink@link") + deployment_link: Link = Field(None, alias="deploymentLink@link") + feature_of_interest_link: Link = Field(None, alias="featureOfInterest@link") + sampling_feature_link: Link = Field(None, alias="samplingFeature@link") + issue_time: DateTimeSchema = Field(None, alias="issueTime") + execution_time: DateTimeSchema = Field(None, alias="executionTime") + live: bool = Field(None) + asynchronous: bool = Field(..., alias="asynchronous") + record_schema: SerializeAsAny[DatastreamRecordSchema] = Field(None, alias="schema") + links: List[Link] = Field(None) diff --git a/src/oshconnect/schema_datamodels.py b/src/oshconnect/schema_datamodels.py new file mode 100644 index 0000000..e8cfc5b --- /dev/null +++ b/src/oshconnect/schema_datamodels.py @@ -0,0 +1,137 @@ +# ============================================================================= +# Copyright (c) 2025 Botts Innovative Research Inc. +# Date: 2025/9/30 +# Author: Ian Patterson +# Contact Email: ian@botts-inc.com +# ============================================================================= +from __future__ import annotations + +from datetime import datetime +from typing import Union, List + +from pydantic import BaseModel, Field, SerializeAsAny, field_validator, HttpUrl + +from .api_utils import Link, URI +from .csapi4py.constants import ObservationFormat +from .encoding import Encoding +from .geometry import Geometry +from .swe_components import AnyComponentSchema + +""" +In many of the top level resource models there is a "schema" field of some description. These models are meant to ease +the burden on the end user to create those. +""" + + +class CommandJSON(BaseModel): + """ + A class to represent a command in JSON format + """ + control_id: str = Field(None, serialization_alias="control@id") + issue_time: Union[str, float] = Field(datetime.now().isoformat(), serialization_alias="issueTime") + sender: str = Field(None) + params: Union[dict, list, int, float, str] = Field(None) + + +class ControlStreamJSONSchema(BaseModel): + """ + A class to represent the schema of a control stream + """ + id: str = Field(None) + name: str = Field(...) + description: str = Field(None) + deployment_link: str = Field(None, serialization_alias='deployment@link') + feature_of_interest_link: str = Field(None, serialization_alias='featureOfInterest@link') + sampling_feature_link: str = Field(None, alias='samplingFeature@link') + valid_time: list = Field(None, serialization_alias='validTime') + input_name: str = Field(None, serialization_alias='inputName') + links: list = Field(None) + control_stream_schema: SerializeAsAny[Union[SWEControlChannelSchema, JSONControlChannelSchema]] = Field(..., + serialization_alias='schema') + + +class SWEControlChannelSchema(BaseModel): + """ + A class to represent the schema of a control channel + """ + command_format: str = Field("application/swe+json", serialization_alias='commandFormat') + encoding: SerializeAsAny[Encoding] = Field(...) + record_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='recordSchema') + + +class JSONControlChannelSchema(BaseModel): + command_format: str = Field("application/cmd+json", serialization_alias='commandFormat') + params_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='paramsSchema') + + +class DatastreamRecordSchema(BaseModel): + """ + A class to represent the schema of a datastream + """ + obs_format: str = Field(..., serialization_alias='obsFormat') + + +class SWEDatastreamRecordSchema(DatastreamRecordSchema): + encoding: SerializeAsAny[Encoding] = Field(...) + record_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='recordSchema') + + @field_validator('obs_format') + @classmethod + def check_check_obs_format(cls, v): + if v not in [ObservationFormat.SWE_JSON.value, ObservationFormat.SWE_CSV.value, + ObservationFormat.SWE_TEXT.value, ObservationFormat.SWE_BINARY.value]: + raise ValueError('obsFormat must be on of the SWE formats') + return v + + +class ObservationOMJSONInline(BaseModel): + """ + A class to represent an observation in OM-JSON format + """ + datastream_id: str = Field(None, serialization_alias="datastream@id") + foi_id: str = Field(None, serialization_alias="foi@id") + phenomenon_time: str = Field(None, serialization_alias="phenomenonTime") + result_time: str = Field(datetime.now().isoformat(), serialization_alias="resultTime") + parameters: dict = Field(None) + result: Union[int, float, str, dict, list] = Field(...) + result_links: List[Link] = Field(None, serialization_alias="result@links") + + +class SystemEventOMJSON(BaseModel): + """ + A class to represent the schema of a system event + """ + label: str = Field(...) + description: str = Field(None) + definition: HttpUrl = Field(...) + identifiers: list = Field(None) + classifiers: list = Field(None) + contacts: list = Field(None) + documentation: list = Field(None) + time: str = Field(...) + properties: list = Field(None) + configuration: dict = Field(None) + links: list[Link] = Field(None) + + +class SystemHistoryGeoJSON(BaseModel): + """ + A class to represent the schema of a system history + """ + type: str = Field(...) + id: str = Field(None) + properties: SystemHistoryProperties = Field(...) + geometry: Geometry = Field(None) + bbox: list = Field(None) + links: list[Link] = Field(None) + + +class SystemHistoryProperties(BaseModel): + feature_type: str = Field(...) + uid: URI = Field(...) + name: str = Field(...) + description: str = Field(None) + asset_type: str = Field(None) + valid_time: list = Field(None) + parent_system_link: str = Field(None, serialization_alias='parentSystem@link') + procedure_link: str = Field(None, serialization_alias='procedure@link') diff --git a/src/oshconnect/osh_connect_datamodels.py b/src/oshconnect/streamableresource.py similarity index 51% rename from src/oshconnect/osh_connect_datamodels.py rename to src/oshconnect/streamableresource.py index e9d4461..07715d5 100644 --- a/src/oshconnect/osh_connect_datamodels.py +++ b/src/oshconnect/streamableresource.py @@ -1,22 +1,34 @@ -# ============================================================================== -# Copyright (c) 2024 Botts Innovative Research, Inc. -# Date: 2024/6/26 -# Author: Ian Patterson -# Contact Email: ian@botts-inc.com -# ============================================================================== +# ============================================================================= +# Copyright (c) 2025 Botts Innovative Research Inc. +# Date: 2025/9/29 +# Author: Ian Patterson +# Contact Email: ian@botts-inc.com +# ============================================================================= + from __future__ import annotations +import asyncio import base64 +import logging import uuid from dataclasses import dataclass, field - -from oshconnect.csapi4py.constants import APIResourceTypes -from oshconnect.csapi4py.core.default_api_helpers import APIHelper -from oshconnect.datamodels.datastreams import SWEDatastreamSchema -from oshconnect.datamodels.encoding import JSONEncoding -from oshconnect.datamodels.swe_components import DataRecordSchema - -from .core_datamodels import DatastreamResource, ObservationResource, SystemResource +from enum import Enum +from multiprocessing import Process +from multiprocessing.queues import Queue +from typing import TypeVar, Generic, Union +from uuid import UUID, uuid4 + +from aiohttp import ClientSession, BasicAuth +from aiohttp import WSMsgType, ClientWebSocketResponse + +from .csapi4py.constants import APIResourceTypes +from .csapi4py.default_api_helpers import APIHelper +from .encoding import JSONEncoding +from .resource_datamodels import ControlStreamResource +from .resource_datamodels import DatastreamResource, ObservationResource +from .resource_datamodels import SystemResource +from .schema_datamodels import SWEDatastreamRecordSchema +from .swe_components import DataRecordSchema from .timemanagement import TimeInstant, TimePeriod, TimeUtils @@ -34,6 +46,59 @@ def convert_auth_to_base64(username: str, password: str) -> str: return base64.b64encode(f"{username}:{password}".encode()).decode() +class OSHClientSession(ClientSession): + verify_ssl = True + _streamables: dict[str, 'StreamableResource'] = None + + def __init__(self, base_url, *args, verify_ssl=True, **kwargs): + super().__init__(base_url, *args, **kwargs) + self.verify_ssl = verify_ssl + self._streamables = {} + + def connect_streamables(self): + for streamable in self._streamables.values(): + streamable.start() + + def close_streamables(self): + for streamable in self._streamables.values(): + streamable.stop() + + def register_streamable(self, streamable: StreamableResource): + if self._streamables is None: + self._streamables = {} + self._streamables[streamable.get_streamable_id_str()] = streamable + + +class SessionManager: + _session_tokens = None + sessions: dict[str, OSHClientSession] = None + + def __init__(self, session_tokens: dict[str, str] = None): + self._session_tokens = session_tokens + self.sessions = {} + + def register_session(self, session_id, session: OSHClientSession) -> OSHClientSession: + self.sessions[session_id] = session + return session + + def unregister_session(self, session_id): + session = self.sessions.pop(session_id) + session.close() + + def get_session(self, session_id): + return self.sessions.get(session_id, None) + + def start_session_streams(self, session_id): + session = self.get_session(session_id) + if session is None: + raise ValueError(f"No session found for ID {session_id}") + session.connect_streamables() + + def start_all_streams(self): + for session in self.sessions.values(): + session.connect_streamables() + + @dataclass(kw_only=True) class Node: _id: str @@ -44,11 +109,11 @@ class Node: is_secure: bool _basic_auth: bytes = None _api_helper: APIHelper - # _system_ids: list[uuid] = field(default_factory=list) _systems: list[System] = field(default_factory=list) + _client_session: OSHClientSession = None def __init__(self, protocol: str, address: str, port: int, - username: str = None, password: str = None, + username: str = None, password: str = None, session_manager: SessionManager = None, **kwargs: dict): self._id = f'node-{uuid.uuid4()}' self.protocol = protocol @@ -59,12 +124,16 @@ def __init__(self, protocol: str, address: str, port: int, self.add_basicauth(username, password) self.endpoints = Endpoints() self._api_helper = APIHelper( - server_url=f'{self.protocol}://{self.address}:{self.port}', + server_url=self.address, + protocol=self.protocol, + port=self.port, api_root=self.endpoints.connected_systems, username=username, password=password) if self.is_secure: self._api_helper.user_auth = True self._systems = [] + if session_manager is not None: + self.register_with_session_manager(session_manager) def get_id(self): return self._id @@ -76,7 +145,8 @@ def get_port(self): return self.port def get_api_endpoint(self): - return f"http{'s' if self.is_secure else ''}://{self.address}:{self.port}/{self.endpoints.connected_systems}" + # return f"http{'s' if self.is_secure else ''}://{self.address}:{self.port}/{self.endpoints.connected_systems}" + return self._api_helper.get_api_root_url() def add_basicauth(self, username: str, password: str): if not self.is_secure: @@ -97,8 +167,8 @@ def discover_systems(self): for system_json in system_objs: print(system_json) system = SystemResource.model_validate(system_json) - sys_obj = System.from_system_resource(system) - sys_obj.update_parent_node(self) + sys_obj = System.from_system_resource(system, self) + sys_obj.set_parent_node(self) self._systems.append(sys_obj) new_systems.append(sys_obj) return new_systems @@ -106,7 +176,7 @@ def discover_systems(self): return None def add_new_system(self, system: System): - system.update_parent_node(self) + system.set_parent_node(self) self._systems.append(system) def get_api_helper(self) -> APIHelper: @@ -131,21 +201,153 @@ def add_system(self, system: System, target_node: Node, insert_resource: bool = def systems(self) -> list[System]: return self._systems + def register_with_session_manager(self, session_manager: SessionManager): + """ + Registers this node with the provided session manager, creating a new client session. + :param session_manager: SessionManager instance + """ + self._client_session = session_manager.register_session(self._id, OSHClientSession( + base_url=self._api_helper.get_base_url())) + + def register_streamable(self, streamable: StreamableResource): + if self._client_session is None: + raise ValueError("Node is not registered with a SessionManager.") + self._client_session.register_streamable(streamable) + + def get_session(self) -> OSHClientSession: + return self._client_session + + +class Status(Enum): + INITIALIZING = "initializing" + INITIALIZED = "initialized" + STARTING = "starting" + STARTED = "started" + STOPPING = "stopping" + STOPPED = "stopped" + + +T = TypeVar('T', SystemResource, DatastreamResource) + + +class StreamableResource(Generic[T]): + _id: UUID = None + _resource_id: str = None + _canonical_link: str = None + _topic: str = None + _status: str = Status.STOPPED.value + ws_url: str = None + _client_websocket: ClientWebSocketResponse = None + _message_handler = None + _parent_node: Node = None + _underlying_resource: T = None + _process: Process = None + _msg_queue: asyncio.Queue[Union[str, bytes, float, int]] = None + + def __init__(self, node: Node): + self._id = uuid4() + self._message_handler = self._default_message_handler_fn + self._parent_node = node + self._parent_node.register_streamable(self) -class System: - uid: uuid.UUID - resource_id: str + def get_streamable_id(self) -> UUID: + return self._id + + def get_streamable_id_str(self) -> str: + return self._id.hex + + def initialize(self): + # self._process = Process(target=self.stream, args=()) + resource_type = None + if isinstance(self._underlying_resource, SystemResource): + resource_type = APIResourceTypes.SYSTEM + elif isinstance(self._underlying_resource, DatastreamResource): + resource_type = APIResourceTypes.DATASTREAM + if resource_type is None: + raise ValueError("Underlying resource must be set to either SystemResource or DatastreamResource before initialization.") + # This needs to be implemented separately for each subclass + self.ws_url = self._parent_node.get_api_helper().construct_url(parent_type=resource_type, res_type=APIResourceTypes.OBSERVATION, parent_res_id=self._underlying_resource.ds_id, res_id=None) + self._msg_queue = asyncio.Queue() + self._status = Status.INITIALIZED.value + + def start(self): + if self._status != Status.INITIALIZED.value: + logging.warning(f"Streamable resource {self._id} not initialized. Call initialize() first.") + return + self._status = Status.STARTING.value + # self._process.start() + self._status = Status.STARTED.value + + asyncio.gather(self.stream()) + + async def stream(self): + if self._msg_queue is None: + self._msg_queue = asyncio.Queue() + + session = self._parent_node.get_session() + + # TODO: handle auth properly and not right here... + auth = BasicAuth("admin", "admin") + + async with session.ws_connect(self.ws_url, auth=auth) as ws: + logging.info(f"Streamable resource {self._id} started.") + async for msg in ws: + self._message_handler(ws, msg) + + def stop(self): + # It would be nicer to join() here once we have cleaner shutdown logic in place to avoid corrupting processes + # that are writing to streams or that need to manage authentication state + self._status = "stopping" + self._process.terminate() + self._status = "stopped" + + def _default_message_handler_fn(self, ws, msg): + if msg.type == WSMsgType.TEXT: + print(f"Received text message: {msg.data}") + self._msg_queue.put(msg.data) + elif msg.type == WSMsgType.BINARY: + print(f"Received binary message: {msg.data}") + self._msg_queue.put(msg.data) + elif msg.type == WSMsgType.CLOSE: + print("WebSocket closed") + elif msg.type == WSMsgType.ERROR: + print(f"WebSocket error: {ws.exception()}") + + def set_parent_node(self, node: Node): + self._parent_node = node + + def get_parent_node(self) -> Node: + return self._parent_node + + def poll(self): + pass + + def fetch(self, time_period: TimePeriod): + pass + + def get_msg_queue(self) -> Queue: + """ + Returns the message queue for this streamable resource. In cases where a custom message handler is used this is + not guaranteed to return anything or provided a queue with data. + :return: Queue object + """ + return self._msg_queue + + def get_underlying_resource(self) -> T: + return self._underlying_resource + + +class System(StreamableResource[SystemResource]): + resources_id: str name: str label: str - # datastreams: list[Datastream] - datastreams: list[DatastreamResource] + datastreams: list[Datastream] control_channels: list[ControlChannel] description: str urn: str _parent_node: Node - _sys_resource: SystemResource - def __init__(self, name: str, label: str, urn: str, **kwargs): + def __init__(self, name: str, label: str, urn: str, parent_node: Node, **kwargs): """ :param name: The machine-accessible name of the system :param label: The human-readable label of the system @@ -153,7 +355,7 @@ def __init__(self, name: str, label: str, urn: str, **kwargs): :param kwargs: - 'description': A description of the system """ - self.uid = uuid.uuid4() + super().__init__(node=parent_node) self.name = name self.label = label self.datastreams = [] @@ -164,17 +366,15 @@ def __init__(self, name: str, label: str, urn: str, **kwargs): if kwargs.get('description'): self.description = kwargs['description'] - def update_parent_node(self, node: Node): - self._parent_node = node - - def get_parent_node(self) -> Node: - return self._parent_node + self._underlying_resource = self.to_system_resource() + # self.underlying_resource = self._sys_resource def discover_datastreams(self) -> list[DatastreamResource]: res = self._parent_node.get_api_helper().retrieve_resource( APIResourceTypes.DATASTREAM, req_headers={}) datastream_json = res.json()['items'] ds_resources = [] + for ds in datastream_json: datastream_objs = DatastreamResource.model_validate(ds) ds_resources.append(datastream_objs) @@ -182,7 +382,7 @@ def discover_datastreams(self) -> list[DatastreamResource]: return ds_resources @staticmethod - def from_system_resource(system_resource: SystemResource): + def from_system_resource(system_resource: SystemResource, parent_node: Node) -> System: other_props = system_resource.model_dump() print(f'Props of SystemResource: {other_props}') @@ -191,23 +391,31 @@ def from_system_resource(system_resource: SystemResource): new_system = System(name=other_props['properties']['name'], label=other_props['properties']['name'], urn=other_props['properties']['uid'], - resource_id=system_resource.system_id) + resource_id=system_resource.system_id, parent_node=parent_node) else: new_system = System(name=system_resource.name, label=system_resource.label, urn=system_resource.urn, - resource_id=system_resource.system_id) + resource_id=system_resource.system_id, parent_node=parent_node) + + new_system.set_system_resource(system_resource) return new_system def to_system_resource(self) -> SystemResource: resource = SystemResource(uid=self.urn, label=self.name, feature_type='PhysicalSystem') if len(self.datastreams) > 0: - resource.outputs = [ds.to_resource() for ds in self.datastreams] + resource.outputs = [ds.get_underlying_resource() for ds in self.datastreams] # if len(self.control_channels) > 0: # resource.inputs = [cc.to_resource() for cc in self.control_channels] return resource + def set_system_resource(self, sys_resource: SystemResource): + self._underlying_resource = sys_resource + + def get_system_resource(self) -> SystemResource: + return self._underlying_resource + def add_insert_datastream(self, datastream: DataRecordSchema): """ Adds a datastream to the system while also inserting it into the system's parent node via HTTP POST. @@ -217,8 +425,8 @@ def add_insert_datastream(self, datastream: DataRecordSchema): print(f'Adding datastream: {datastream.model_dump_json(exclude_none=True, by_alias=True)}') # Make the request to add the datastream # if successful, add the datastream to the system - datastream_schema = SWEDatastreamSchema(record_schema=datastream, obs_format='application/swe+json', - encoding=JSONEncoding()) + datastream_schema = SWEDatastreamRecordSchema(record_schema=datastream, obs_format='application/swe+json', + encoding=JSONEncoding()) datastream_resource = DatastreamResource(ds_id="default", name=datastream.label, output_name=datastream.label, record_schema=datastream_schema, valid_time=TimePeriod(start=TimeInstant.now_as_time_instant(), @@ -269,32 +477,39 @@ def retrieve_resource(self): print(system_json) system_resource = SystemResource.model_validate(system_json) print(f'System Resource: {system_resource}') - self._sys_resource = system_resource + self._underlying_resource = system_resource + return None -class Datastream: +class Datastream(StreamableResource[DatastreamResource]): should_poll: bool - _id: str - _datastream_resource: DatastreamResource + resource_id: str + # _datastream_resource: DatastreamResource _parent_node: Node def __init__(self, id: str = None, parent_node: Node = None, datastream_resource: DatastreamResource = None): - self._id = id + super().__init__(node=parent_node) + self.resource_id = id self._parent_node = parent_node - self._datastream_resource = datastream_resource + # self._datastream_resource = datastream_resource + self._underlying_resource = datastream_resource def get_id(self): - return self._datastream_resource.ds_id + return self._underlying_resource.ds_id def insert_observation(self, observation: Observation): pass - def to_resource(self) -> DatastreamResource: - # if self._datastream_resource is None: - # self._datastream_resource = DatastreamResource( - # ds_id=uuid.uuid4(), name=self.name, - # valid_time=self.validTimeRange) - return self._datastream_resource + @staticmethod + def from_resource(ds_resource: DatastreamResource, parent_node: Node): + new_ds = Datastream(id=ds_resource.ds_id, parent_node=parent_node, datastream_resource=ds_resource) + return new_ds + + def set_resource(self, resource: DatastreamResource): + self._underlying_resource = resource + + def get_resource(self) -> DatastreamResource: + return self._underlying_resource def observation_template(self) -> Observation: pass @@ -302,13 +517,13 @@ def observation_template(self) -> Observation: def create_observation(self, obs_data: dict): obs = ObservationResource(result=obs_data, result_time=TimeInstant.now_as_time_instant()) # Validate against the schema - if self._datastream_resource.record_schema is not None: - obs.validate_against_schema(self._datastream_resource.record_schema) + if self._underlying_resource.record_schema is not None: + obs.validate_against_schema(self._underlying_resource.record_schema) return obs def insert_observation_dict(self, obs_data: dict): res = self._parent_node.get_api_helper().create_resource(APIResourceTypes.OBSERVATION, obs_data, - parent_res_id=self._id, + parent_res_id=self.resource_id, req_headers={'Content-Type': 'application/json'}) if res.ok: obs_id = res.headers['Location'].split('/')[-1] @@ -317,6 +532,9 @@ def insert_observation_dict(self, obs_data: dict): else: raise Exception(f'Failed to insert observation: {res.text}') + # def initialize(self): + + # def create_from_record_schema(record_schema: DataRecordSchema, parent_system: System): # new_ds = Datastream(name=record_schema.label, record_schema=record_schema) # new_ds._datastream_resource = DatastreamResource(ds_id=uuid.uuid4(), name=new_ds.name) @@ -325,7 +543,9 @@ def insert_observation_dict(self, obs_data: dict): class ControlChannel: - # _cc_resource: ControlStream + _cc_resource: ControlStreamResource + resource_id: str + _parent_node: Node def __init__(self): pass diff --git a/src/oshconnect/datamodels/swe_components.py b/src/oshconnect/swe_components.py similarity index 94% rename from src/oshconnect/datamodels/swe_components.py rename to src/oshconnect/swe_components.py index 911ac52..f7220f3 100644 --- a/src/oshconnect/datamodels/swe_components.py +++ b/src/oshconnect/swe_components.py @@ -1,3 +1,10 @@ +# ============================================================================= +# Copyright (c) 2025 Botts Innovative Research Inc. +# Date: 2025/9/30 +# Author: Ian Patterson +# Contact Email: ian@botts-inc.com +# ============================================================================= + from __future__ import annotations from numbers import Real @@ -5,9 +12,9 @@ from pydantic import BaseModel, Field, field_validator, SerializeAsAny -from oshconnect.csapi4py.constants import GeometryTypes -from oshconnect.datamodels.api_utils import UCUMCode, URI -from oshconnect.datamodels.geometry import Geometry +from .csapi4py.constants import GeometryTypes +from .api_utils import UCUMCode, URI +from .geometry import Geometry """ NOTE: The following classes are used to represent the Record Schemas that are required for use with Datastreams diff --git a/tests/test_api_helper.py b/tests/test_api_helper.py new file mode 100644 index 0000000..d4128b3 --- /dev/null +++ b/tests/test_api_helper.py @@ -0,0 +1,17 @@ +from csapi4py.default_api_helpers import APIHelper + +def test_url_generation(): + helper = APIHelper(server_url='localhost', port=8282, protocol='http', username='admin', password='admin', api_root='sensorhub/api') + expected_url = "http://localhost:8282/sensorhub/api" + url = helper.get_api_root_url() + assert url == expected_url + expected_url = "ws://localhost:8282/sensorhub/api" + url = helper.get_api_root_url(socket=True) + assert url == expected_url + helper.set_protocol('https') + expected_url = "https://localhost:8282/sensorhub/api" + url = helper.get_api_root_url() + assert url == expected_url + expected_url = "wss://localhost:8282/sensorhub/api" + url = helper.get_api_root_url(socket=True) + assert url == expected_url diff --git a/tests/test_oshconnect.py b/tests/test_oshconnect.py index c3f1440..e958d33 100644 --- a/tests/test_oshconnect.py +++ b/tests/test_oshconnect.py @@ -5,12 +5,14 @@ # Contact Email: ian@botts-inc.com # ============================================================================== +import sys +import os import websockets -from oshconnect import TimePeriod -from oshconnect import Node -from oshconnect import OSHConnect -from oshconnect import TimeInstant +from src.oshconnect import OSHConnect, Node +from timemanagement import TimePeriod, TimeInstant + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) class TestOSHConnect: @@ -45,7 +47,8 @@ def test_oshconnect_create(self): def test_oshconnect_add_node(self): app = OSHConnect(name="Test OSH Connect") - node = Node(address="http://localhost", port=self.TEST_PORT, protocol="http", username="admin", password="admin") + node = Node(address="http://localhost", port=self.TEST_PORT, protocol="http", username="admin", + password="admin") # node.add_basicauth("admin", "admin") app.add_node(node) assert len(app._nodes) == 1 @@ -71,8 +74,9 @@ def test_oshconnect_find_datastreams(self): assert len(app._datastreams) > 0 async def test_obs_ws_stream(self): - ds_url = ("ws://localhost:8585/sensorhub/api/datastreams/e07n5sbjqvalm/observations?f=application%2Fjson" - "&resultTime=latest/2025-06-18T15:46:32Z") + ds_url = ( + "ws://localhost:8282/sensorhub/api/datastreams/038q16egp1t0/observations?resultTime=latest" + "/2026-01-01T12:00:00Z&f=application%2Fjson") # stream = requests.get(ds_url, stream=True, auth=('admin', 'admin')) async with websockets.connect(ds_url, extra_headers={'Authorization': 'Basic YWRtaW46YWRtaW4='}) as stream: diff --git a/tests/test_streamable_resources.py b/tests/test_streamable_resources.py new file mode 100644 index 0000000..2588e04 --- /dev/null +++ b/tests/test_streamable_resources.py @@ -0,0 +1,12 @@ +from src.oshconnect import OSHConnect, Node + + +def test_streamble_observations(): + app = OSHConnect("Test App") + node = Node(address="localhost", port=8282, username="admin", password="admin", protocol="http") + app.add_node(node) + app.discover_systems() + app.discover_datastreams() + + datastreams = app.get_datastreams() + print(datastreams) \ No newline at end of file diff --git a/uv.lock b/uv.lock index 810841c..790e496 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,79 @@ version = 1 revision = 3 requires-python = ">=3.12, <4.0" +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, + { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, + { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, + { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, + { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, + { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, + { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, + { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, + { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + [[package]] name = "alabaster" version = "0.7.16" @@ -20,6 +93,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + [[package]] name = "babel" version = "2.17.0" @@ -105,6 +187,66 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/5c/0627be4c9976d56b1217cb5187b7504e7fd7d3503f8bfd312a04077bd4f7/flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343", size = 57786, upload-time = "2025-03-29T20:08:37.902Z" }, ] +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, + { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, + { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, + { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, + { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, + { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, + { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, + { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, + { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, + { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, + { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, + { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, + { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, + { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, + { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +] + [[package]] name = "idna" version = "3.10" @@ -191,6 +333,69 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, ] +[[package]] +name = "multidict" +version = "6.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/f6/512ffd8fd8b37fb2680e5ac35d788f1d71bbaf37789d21a820bdc441e565/multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8", size = 76516, upload-time = "2025-08-11T12:06:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/99/58/45c3e75deb8855c36bd66cc1658007589662ba584dbf423d01df478dd1c5/multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3", size = 45394, upload-time = "2025-08-11T12:06:54.555Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/e8c4472a93a26e4507c0b8e1f0762c0d8a32de1328ef72fd704ef9cc5447/multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b", size = 43591, upload-time = "2025-08-11T12:06:55.672Z" }, + { url = "https://files.pythonhosted.org/packages/05/51/edf414f4df058574a7265034d04c935aa84a89e79ce90fcf4df211f47b16/multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287", size = 237215, upload-time = "2025-08-11T12:06:57.213Z" }, + { url = "https://files.pythonhosted.org/packages/c8/45/8b3d6dbad8cf3252553cc41abea09ad527b33ce47a5e199072620b296902/multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138", size = 258299, upload-time = "2025-08-11T12:06:58.946Z" }, + { url = "https://files.pythonhosted.org/packages/3c/e8/8ca2e9a9f5a435fc6db40438a55730a4bf4956b554e487fa1b9ae920f825/multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6", size = 242357, upload-time = "2025-08-11T12:07:00.301Z" }, + { url = "https://files.pythonhosted.org/packages/0f/84/80c77c99df05a75c28490b2af8f7cba2a12621186e0a8b0865d8e745c104/multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9", size = 268369, upload-time = "2025-08-11T12:07:01.638Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e9/920bfa46c27b05fb3e1ad85121fd49f441492dca2449c5bcfe42e4565d8a/multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c", size = 269341, upload-time = "2025-08-11T12:07:02.943Z" }, + { url = "https://files.pythonhosted.org/packages/af/65/753a2d8b05daf496f4a9c367fe844e90a1b2cac78e2be2c844200d10cc4c/multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402", size = 256100, upload-time = "2025-08-11T12:07:04.564Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/655be13ae324212bf0bc15d665a4e34844f34c206f78801be42f7a0a8aaa/multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7", size = 253584, upload-time = "2025-08-11T12:07:05.914Z" }, + { url = "https://files.pythonhosted.org/packages/5c/74/ab2039ecc05264b5cec73eb018ce417af3ebb384ae9c0e9ed42cb33f8151/multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f", size = 251018, upload-time = "2025-08-11T12:07:08.301Z" }, + { url = "https://files.pythonhosted.org/packages/af/0a/ccbb244ac848e56c6427f2392741c06302bbfba49c0042f1eb3c5b606497/multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d", size = 251477, upload-time = "2025-08-11T12:07:10.248Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b0/0ed49bba775b135937f52fe13922bc64a7eaf0a3ead84a36e8e4e446e096/multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7", size = 263575, upload-time = "2025-08-11T12:07:11.928Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/7fb85a85e14de2e44dfb6a24f03c41e2af8697a6df83daddb0e9b7569f73/multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802", size = 259649, upload-time = "2025-08-11T12:07:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/03/9e/b3a459bcf9b6e74fa461a5222a10ff9b544cb1cd52fd482fb1b75ecda2a2/multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24", size = 251505, upload-time = "2025-08-11T12:07:14.57Z" }, + { url = "https://files.pythonhosted.org/packages/86/a2/8022f78f041dfe6d71e364001a5cf987c30edfc83c8a5fb7a3f0974cff39/multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793", size = 41888, upload-time = "2025-08-11T12:07:15.904Z" }, + { url = "https://files.pythonhosted.org/packages/c7/eb/d88b1780d43a56db2cba24289fa744a9d216c1a8546a0dc3956563fd53ea/multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e", size = 46072, upload-time = "2025-08-11T12:07:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/9f/16/b929320bf5750e2d9d4931835a4c638a19d2494a5b519caaaa7492ebe105/multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364", size = 43222, upload-time = "2025-08-11T12:07:18.328Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" }, + { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" }, + { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" }, + { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" }, + { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" }, + { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" }, + { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" }, + { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" }, + { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" }, + { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" }, + { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" }, + { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" }, + { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, +] + [[package]] name = "numpy" version = "2.2.4" @@ -231,9 +436,10 @@ wheels = [ [[package]] name = "oshconnect" -version = "0.3.0a3.post3" +version = "0.3.0a4" source = { virtual = "." } dependencies = [ + { name = "aiohttp" }, { name = "paho-mqtt" }, { name = "pydantic" }, { name = "requests" }, @@ -251,6 +457,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "aiohttp", specifier = ">=3.12.15" }, { name = "paho-mqtt", specifier = ">=2.1.0" }, { name = "pydantic", specifier = ">=2.7.4,<3.0.0" }, { name = "requests" }, @@ -293,6 +500,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, + { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, + { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, + { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, + { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, + { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +] + [[package]] name = "pycodestyle" version = "2.13.0" @@ -607,3 +871,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/52/8915f51f9aaef4e4361c89dd6cf69f72a0159f14e0d25026c81b6ad22525/websockets-12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae0a5da8f35a5be197f328d4727dbcfafa53d1824fac3d96cdd3a642fe09394f", size = 124985, upload-time = "2023-10-21T14:20:15.817Z" }, { url = "https://files.pythonhosted.org/packages/79/4d/9cc401e7b07e80532ebc8c8e993f42541534da9e9249c59ee0139dcb0352/websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e", size = 118370, upload-time = "2023-10-21T14:21:10.075Z" }, ] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, + { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, + { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, + { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, + { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, + { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, + { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, + { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +] From 6c910c97650231f10ec7948ffbbcad1b0d843a1f Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Tue, 7 Oct 2025 16:28:27 -0500 Subject: [PATCH 27/31] - use MQTT as default communication protocol - update some URl construction methods --- .../csapi4py/default_api_helpers.py | 63 ++-- src/oshconnect/csapi4py/mqtt.py | 2 +- src/oshconnect/encoding.py | 3 +- src/oshconnect/eventbus.py | 15 + src/oshconnect/oshconnectapi.py | 95 +++--- src/oshconnect/resource_datamodels.py | 10 +- src/oshconnect/schema_datamodels.py | 12 +- src/oshconnect/streamableresource.py | 308 +++++++++++++++--- 8 files changed, 383 insertions(+), 125 deletions(-) create mode 100644 src/oshconnect/eventbus.py diff --git a/src/oshconnect/csapi4py/default_api_helpers.py b/src/oshconnect/csapi4py/default_api_helpers.py index e16e5df..08a2468 100644 --- a/src/oshconnect/csapi4py/default_api_helpers.py +++ b/src/oshconnect/csapi4py/default_api_helpers.py @@ -16,6 +16,8 @@ from .constants import APIResourceTypes, EncodingSchema, APITerms +# TODO: rework to make the first resource in the endpoint the primary key for URL construction, currently, the implementation is a bit on the confusing side with what is being generated and why. + def determine_parent_type(res_type: APIResourceTypes): match res_type: case APIResourceTypes.SYSTEM: @@ -84,6 +86,7 @@ class APIHelper(ABC): server_url: str = None port: int = None protocol: str = "https" + server_root: str = "sensorhub" api_root: str = "api" username: str = None password: str = None @@ -178,52 +181,54 @@ def delete_resource(self, res_type: APIResourceTypes, res_id: str, parent_res_id return api_request.make_request() # Helpers - def resource_url_resolver(self, res_type: APIResourceTypes, res_id: str = None, parent_res_id: str = None, + def resource_url_resolver(self, subresource_type: APIResourceTypes, subresource_id: str = None, + resource_id: str = None, from_collection: bool = False): """ Helper to generate a URL endpoint for a given resource type and id by matching the resource type to an appropriate parent endpoint and inserting the resource ids as necessary. - :param res_type: - :param res_id: - :param parent_res_id: + :param subresource_type: + :param subresource_id: + :param resource_id: :param from_collection: :return: """ - if res_type is None: + if subresource_type is None: raise ValueError('Resource type must contain a valid APIResourceType') - if res_type is APIResourceTypes.COLLECTION and from_collection: + if subresource_type is APIResourceTypes.COLLECTION and from_collection: raise ValueError('Collections are not sub-resources of other collections') parent_type = None - if parent_res_id and not from_collection: - parent_type = determine_parent_type(res_type) - elif parent_res_id and from_collection: + if resource_id and not from_collection: + parent_type = determine_parent_type(subresource_type) + elif resource_id and from_collection: parent_type = APIResourceTypes.COLLECTION - return self.construct_url(parent_type, res_id, res_type, parent_res_id) + return self.construct_url(parent_type, subresource_id, subresource_type, resource_id) - def construct_url(self, parent_type, res_id, res_type, parent_res_id, for_socket: bool = False): + def construct_url(self, resource_type: APIResourceTypes, subresource_id, subresource_type, resource_id, + for_socket: bool = False): """ Constructs an API endpoint url from the given parameters - :param parent_type: - :param res_id: - :param res_type: - :param parent_res_id: + :param resource_type: + :param subresource_id: + :param subresource_type: + :param resource_id: :param for_socket: If true, will construct a WebSocket URL (ws:// or wss://) instead of HTTP/HTTPS. :return: """ # TODO: Test for less common cases to ensure that the URL is being constructed correctly base_url = self.get_api_root_url(socket=for_socket) - resource_endpoint = resource_type_to_endpoint(res_type, parent_type) + resource_endpoint = resource_type_to_endpoint(subresource_type, resource_type) url = f'{base_url}/{resource_endpoint}' - if parent_type: - parent_endpoint = resource_type_to_endpoint(parent_type) - url = f'{base_url}/{parent_endpoint}/{parent_res_id}/{resource_endpoint}' + if resource_type: + parent_endpoint = resource_type_to_endpoint(resource_type) + url = f'{base_url}/{parent_endpoint}/{resource_id}/{resource_endpoint}' - if res_id: - url = f'{url}/{res_id}' + if subresource_id: + url = f'{url}/{subresource_id}' return url @@ -244,13 +249,27 @@ def get_api_root_url(self, socket: bool = False): :param socket: If true, will return a WebSocket URL (ws:// or wss://) instead of HTTP/HTTPS. :return: """ - return f'{self.get_base_url(socket=socket)}/{self.api_root}' + return f'{self.get_base_url(socket=socket)}/{self.server_root}/{self.api_root}' def set_protocol(self, protocol: str): if protocol not in ['http', 'https', 'ws', 'wss']: raise ValueError('Protocol must be either "http" or "https"') self.protocol = protocol + def get_mqtt_topic(self, resource_type, subresource_type, resource_id: str, + for_socket: bool = False): + """ + Returns the MQTT topic for the resource type, if applicable. + :return: + """ + resource_endpoint = f'/{resource_type_to_endpoint(subresource_type, resource_type)}' + parent_endpoint = "" if resource_type is None else f'/{resource_type_to_endpoint(resource_type)}' + parent_id = "" if resource_id is None else f'/{resource_id}' + topic_locator = f'/{self.api_root}{parent_endpoint}{parent_id}{resource_endpoint}' + print(f'MQTT Topic: {topic_locator}') + + return topic_locator + @dataclass(kw_only=True) class ResponseParserHelper: diff --git a/src/oshconnect/csapi4py/mqtt.py b/src/oshconnect/csapi4py/mqtt.py index 9295545..d593b7e 100644 --- a/src/oshconnect/csapi4py/mqtt.py +++ b/src/oshconnect/csapi4py/mqtt.py @@ -173,7 +173,7 @@ def start(self): def stop(self): """ - Stop the MQTT client.\ + Stop the MQTT client. :return: """ diff --git a/src/oshconnect/encoding.py b/src/oshconnect/encoding.py index e2c04d7..c5ac19e 100644 --- a/src/oshconnect/encoding.py +++ b/src/oshconnect/encoding.py @@ -1,7 +1,8 @@ -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict class Encoding(BaseModel): + model_config = ConfigDict(populate_by_name=True) id: str = Field(None) type: str = Field(...) vector_as_arrays: bool = Field(False, alias='vectorAsArrays') diff --git a/src/oshconnect/eventbus.py b/src/oshconnect/eventbus.py new file mode 100644 index 0000000..b3a7f9e --- /dev/null +++ b/src/oshconnect/eventbus.py @@ -0,0 +1,15 @@ +# ============================================================================= +# Copyright (c) 2025 Botts Innovative Research Inc. +# Date: 2025/10/6 +# Author: Ian Patterson +# Contact Email: ian@botts-inc.com +# ============================================================================= +import collections +from abc import ABC + + +class EventBus(ABC): + """ + A base class for an event bus system. + """ + _deque: collections.deque \ No newline at end of file diff --git a/src/oshconnect/oshconnectapi.py b/src/oshconnect/oshconnectapi.py index 46506af..741d319 100644 --- a/src/oshconnect/oshconnectapi.py +++ b/src/oshconnect/oshconnectapi.py @@ -6,11 +6,12 @@ # ============================================================================== import logging import shelve +from uuid import UUID from .csapi4py.default_api_helpers import APIHelper -from .resource_datamodels import DatastreamResource -from .datasource import DataStream, MessageWrapper +from .datasource import MessageWrapper from .datastore import DataStore +from .resource_datamodels import DatastreamResource from .streamableresource import Node, System, SessionManager, Datastream from .styling import Styling from .timemanagement import TemporalModes, TimeManagement, TimePeriod @@ -25,7 +26,7 @@ class OSHConnect: _systems: list[System] = [] _cs_api_builder: APIHelper = None # _datasource_handler: DataStreamHandler = None - _datastreams: list[DataStream] = [] + _datastreams: list[Datastream] = [] _datataskers: list[DataStore] = [] _datagroups: list = [] _tasks: list = [] @@ -38,15 +39,6 @@ def __init__(self, name: str, **kwargs): :param kwargs: """ self._name = name - # if 'nodes' in kwargs: - # self._nodes = kwargs['nodes'] - # self._playback_mode = kwargs['playback_mode'] - # self._datasource_handler.set_playback_mode(self._playback_mode) - # self._datasource_handler = DataStreamHandler() - # if 'playback_mode' in kwargs: - # self._playback_mode = kwargs['playback_mode'] - # self._datasource_handler.set_playback_mode(self._playback_mode) - logging.info(f"OSHConnect instance {name} created") self._session_manager = SessionManager() @@ -116,20 +108,6 @@ def select_temporal_mode(self, mode: str): """ pass - async def playback_streams(self, stream_ids: list = None): - """ - Begins playback of the datastreams that have been connected to the app. The method of playback is determined - by the temporal mode that has been set. - :param stream_ids: - :return: - """ - if stream_ids is None: - await self._datasource_handler.connect_all( - self.timestream.get_time_range()) - else: - for stream_id in stream_ids: - await self._datasource_handler.connect_ds(stream_id) - def visualize_streams(self, streams: list): pass @@ -137,27 +115,13 @@ def visualize_streams(self, streams: list): def get_visualization_recommendations(self, streams: list): pass - def dep_discover_datastreams(self): - """ - Discover datastreams of the current systems of the OSHConnect instance and create objects for them that are - stored in the DataSourceHandler. - :return: - """ - # NOTE: This will need to check to prevent dupes in the future - for system in self._systems: - res_datastreams = system.discover_datastreams() - # create DataSource(s) - new_datasource = [ - DataStream(name=ds.name, datastream=ds, parent_system=system.get_system_resource()) - for ds in - res_datastreams] - self._datastreams.extend(new_datasource) - list(map(self._datasource_handler.add_datasource, new_datasource)) - def discover_datastreams(self): for system in self._systems: res_datastreams = system.discover_datastreams() - datastreams = list(map(lambda ds: Datastream(parent_node=system.get_parent_node(), id=ds.ds_id, datastream_resource=ds), res_datastreams)) + datastreams = list( + map(lambda ds: Datastream(parent_node=system.get_parent_node(), id=ds.ds_id, datastream_resource=ds), + res_datastreams)) + datastreams = [ds.set_parent_resource_id(system.get_underlying_resource().system_id) for ds in datastreams] self._datastreams.extend(datastreams) def discover_systems(self, nodes: list[str] = None): @@ -279,9 +243,12 @@ def remove_system(self, system_id: str): pass # DataStream Helpers - def get_datastreams(self) -> list[DataStream]: + def get_datastreams(self) -> list[Datastream]: return self._datastreams + def get_datastream_ids(self) -> list[UUID]: + return [ds.get_internal_id() for ds in self._datastreams] + def connect_session_streams(self, session_id: str): """ Connects all datastreams that are associated with the given session ID. @@ -289,3 +256,41 @@ def connect_session_streams(self, session_id: str): :return: """ self._session_manager.start_session_streams(session_id) + + def get_resource_group(self, resource_ids: list[UUID]) -> tuple[list[System], list[Datastream]]: + """ + Get a group of resources by their IDs. Can be any mix of systems, datastreams, and controlstreams. + :param resource_ids: list of resource IDs (internal UUID) + """ + systems = [system for system in self._systems if system.get_internal_id() in resource_ids] + datastreams = [ds for ds in self._datastreams if ds.get_internal_id() in resource_ids] + return systems, datastreams + + def initialize_resource_groups(self, resource_ids: list = None): + """ + Initializes the datastreams that are specified. + """ + systems, datastreams = self.get_resource_group(resource_ids) + + if systems: + for system in systems: + system.initialize() + if datastreams: + for ds in datastreams: + ds.initialize() + + def start_datastreams(self, dsid_list: list = None): + """ + Starts the datastreams that are specified. + """ + datastreams = self.get_resource_group(dsid_list)[1] + for ds in datastreams: + ds.start() + + def start_systems(self, sysid_list: list = None): + """ + Starts the systems that are specified. + """ + systems = self.get_resource_group(sysid_list)[0] + for system in systems: + system.start() diff --git a/src/oshconnect/resource_datamodels.py b/src/oshconnect/resource_datamodels.py index 7217fde..33954fc 100644 --- a/src/oshconnect/resource_datamodels.py +++ b/src/oshconnect/resource_datamodels.py @@ -17,7 +17,7 @@ class BoundingBox(BaseModel): - model_config = ConfigDict(arbitrary_types_allowed=True) + model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True) lower_left_corner: Point = Field(..., description="The lower left corner of the bounding box.") upper_right_corner: Point = Field(..., description="The upper right corner of the bounding box.") @@ -88,7 +88,7 @@ class ProcessMethod: class BaseResource(BaseModel): - model_config = ConfigDict(arbitrary_types_allowed=True) + model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True) id: str = Field(..., alias="id") name: str = Field(...) @@ -98,7 +98,7 @@ class BaseResource(BaseModel): class SystemResource(BaseModel): - model_config = ConfigDict(arbitrary_types_allowed=True) + model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True) feature_type: str = Field(None, alias="type") system_id: str = Field(None, alias="id") @@ -139,7 +139,7 @@ class DatastreamResource(BaseModel): that, depending on the format of the request, the fields needed may differ. There may be derived models in a later release that will have different sets of required fields to ease the validation process for users. """ - # model_config = ConfigDict(populate_by_name=True) + model_config = ConfigDict(populate_by_name=True) ds_id: str = Field(..., alias="id") name: str = Field(...) @@ -176,6 +176,7 @@ def handle_aliases(cls, values): class ObservationResource(BaseModel): + model_config = ConfigDict(populate_by_name=True) sampling_feature_id: str = Field(None, alias="samplingFeature@Id") procedure_link: Link = Field(None, alias="procedure@link") phenomenon_time: DateTimeSchema = Field(None, alias="phenomenonTime") @@ -186,6 +187,7 @@ class ObservationResource(BaseModel): class ControlStreamResource(BaseModel): + model_config = ConfigDict(populate_by_name=True) name: str = Field(...) description: str = Field(None) valid_time: TimePeriod = Field(..., alias="validTime") diff --git a/src/oshconnect/schema_datamodels.py b/src/oshconnect/schema_datamodels.py index e8cfc5b..be27e6a 100644 --- a/src/oshconnect/schema_datamodels.py +++ b/src/oshconnect/schema_datamodels.py @@ -9,7 +9,7 @@ from datetime import datetime from typing import Union, List -from pydantic import BaseModel, Field, SerializeAsAny, field_validator, HttpUrl +from pydantic import BaseModel, Field, SerializeAsAny, field_validator, HttpUrl, ConfigDict from .api_utils import Link, URI from .csapi4py.constants import ObservationFormat @@ -27,6 +27,7 @@ class CommandJSON(BaseModel): """ A class to represent a command in JSON format """ + model_config = ConfigDict(populate_by_name=True) control_id: str = Field(None, serialization_alias="control@id") issue_time: Union[str, float] = Field(datetime.now().isoformat(), serialization_alias="issueTime") sender: str = Field(None) @@ -37,6 +38,7 @@ class ControlStreamJSONSchema(BaseModel): """ A class to represent the schema of a control stream """ + model_config = ConfigDict(populate_by_name=True) id: str = Field(None) name: str = Field(...) description: str = Field(None) @@ -54,12 +56,14 @@ class SWEControlChannelSchema(BaseModel): """ A class to represent the schema of a control channel """ + model_config = ConfigDict(populate_by_name=True) command_format: str = Field("application/swe+json", serialization_alias='commandFormat') encoding: SerializeAsAny[Encoding] = Field(...) record_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='recordSchema') class JSONControlChannelSchema(BaseModel): + model_config = ConfigDict(populate_by_name=True) command_format: str = Field("application/cmd+json", serialization_alias='commandFormat') params_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='paramsSchema') @@ -68,10 +72,12 @@ class DatastreamRecordSchema(BaseModel): """ A class to represent the schema of a datastream """ + model_config = ConfigDict(populate_by_name=True) obs_format: str = Field(..., serialization_alias='obsFormat') class SWEDatastreamRecordSchema(DatastreamRecordSchema): + model_config = ConfigDict(populate_by_name=True) encoding: SerializeAsAny[Encoding] = Field(...) record_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='recordSchema') @@ -88,6 +94,7 @@ class ObservationOMJSONInline(BaseModel): """ A class to represent an observation in OM-JSON format """ + model_config = ConfigDict(populate_by_name=True) datastream_id: str = Field(None, serialization_alias="datastream@id") foi_id: str = Field(None, serialization_alias="foi@id") phenomenon_time: str = Field(None, serialization_alias="phenomenonTime") @@ -101,6 +108,7 @@ class SystemEventOMJSON(BaseModel): """ A class to represent the schema of a system event """ + model_config = ConfigDict(populate_by_name=True) label: str = Field(...) description: str = Field(None) definition: HttpUrl = Field(...) @@ -118,6 +126,7 @@ class SystemHistoryGeoJSON(BaseModel): """ A class to represent the schema of a system history """ + model_config = ConfigDict(populate_by_name=True) type: str = Field(...) id: str = Field(None) properties: SystemHistoryProperties = Field(...) @@ -127,6 +136,7 @@ class SystemHistoryGeoJSON(BaseModel): class SystemHistoryProperties(BaseModel): + model_config = ConfigDict(populate_by_name=True) feature_type: str = Field(...) uid: URI = Field(...) name: str = Field(...) diff --git a/src/oshconnect/streamableresource.py b/src/oshconnect/streamableresource.py index 07715d5..728c009 100644 --- a/src/oshconnect/streamableresource.py +++ b/src/oshconnect/streamableresource.py @@ -9,8 +9,11 @@ import asyncio import base64 +import json import logging +import traceback import uuid +from abc import ABC, abstractmethod from dataclasses import dataclass, field from enum import Enum from multiprocessing import Process @@ -21,6 +24,7 @@ from aiohttp import ClientSession, BasicAuth from aiohttp import WSMsgType, ClientWebSocketResponse +from .csapi4py.mqtt import MQTTCommClient from .csapi4py.constants import APIResourceTypes from .csapi4py.default_api_helpers import APIHelper from .encoding import JSONEncoding @@ -105,19 +109,24 @@ class Node: protocol: str address: str port: int + server_root: str = 'sensorhub' endpoints: Endpoints is_secure: bool _basic_auth: bytes = None _api_helper: APIHelper _systems: list[System] = field(default_factory=list) _client_session: OSHClientSession = None + _mqtt_client: MQTTCommClient = None + _mqtt_port: int = 1883 def __init__(self, protocol: str, address: str, port: int, - username: str = None, password: str = None, session_manager: SessionManager = None, - **kwargs: dict): + username: str = None, password: str = None, server_root: str = 'sensorhub', + session_manager: SessionManager = None, + **kwargs): self._id = f'node-{uuid.uuid4()}' self.protocol = protocol self.address = address + self.server_root = server_root self.port = port self.is_secure = username is not None and password is not None if self.is_secure: @@ -127,13 +136,22 @@ def __init__(self, protocol: str, address: str, port: int, server_url=self.address, protocol=self.protocol, port=self.port, - api_root=self.endpoints.connected_systems, username=username, + server_root=self.server_root, + api_root='api', username=username, password=password) if self.is_secure: self._api_helper.user_auth = True self._systems = [] if session_manager is not None: - self.register_with_session_manager(session_manager) + session_task = self.register_with_session_manager(session_manager) + asyncio.gather(session_task) + + if kwargs.get('enable_mqtt'): + if kwargs.get('mqtt_port') is not None: + self._mqtt_port = kwargs.get('mqtt_port') + self._mqtt_client = MQTTCommClient(url=self.address, port=self._mqtt_port) + # self._mqtt_client = MQTTCommClient(url=self.address + self.server_root, port=self._mqtt_port, + # username=username, password=password, ) def get_id(self): return self._id @@ -157,6 +175,12 @@ def add_basicauth(self, username: str, password: str): def get_decoded_auth(self): return self._basic_auth.decode('utf-8') + def get_basicauth(self): + return BasicAuth(self._api_helper.username, self._api_helper.password) + + def get_mqtt_client(self) -> MQTTCommClient: + return self._mqtt_client + def discover_systems(self): result = self._api_helper.retrieve_resource(APIResourceTypes.SYSTEM, req_headers={}) @@ -168,7 +192,6 @@ def discover_systems(self): print(system_json) system = SystemResource.model_validate(system_json) sys_obj = System.from_system_resource(system, self) - sys_obj.set_parent_node(self) self._systems.append(sys_obj) new_systems.append(sys_obj) return new_systems @@ -226,11 +249,16 @@ class Status(Enum): STOPPING = "stopping" STOPPED = "stopped" +class StreamableModes(Enum): + PUSH = "push" + PULL = "pull" + BIDIRECTIONAL = "bidirectional" -T = TypeVar('T', SystemResource, DatastreamResource) +T = TypeVar('T', SystemResource, DatastreamResource, ControlStreamResource) -class StreamableResource(Generic[T]): + +class StreamableResource(Generic[T], ABC): _id: UUID = None _resource_id: str = None _canonical_link: str = None @@ -242,13 +270,18 @@ class StreamableResource(Generic[T]): _parent_node: Node = None _underlying_resource: T = None _process: Process = None - _msg_queue: asyncio.Queue[Union[str, bytes, float, int]] = None + _msg_reader_queue: asyncio.Queue[Union[str, bytes, float, int]] = None + _msg_writer_queue: asyncio.Queue[Union[str, bytes, float, int]] = None + _mqtt_client: MQTTCommClient = None + _parent_resource_id: str = None + _connection_mode: StreamableModes = StreamableModes.PUSH.value - def __init__(self, node: Node): + def __init__(self, node: Node, connection_mode: StreamableModes = StreamableModes.PUSH.value): self._id = uuid4() self._message_handler = self._default_message_handler_fn self._parent_node = node self._parent_node.register_streamable(self) + self._mqtt_client = self._parent_node.get_mqtt_client() def get_streamable_id(self) -> UUID: return self._id @@ -264,10 +297,16 @@ def initialize(self): elif isinstance(self._underlying_resource, DatastreamResource): resource_type = APIResourceTypes.DATASTREAM if resource_type is None: - raise ValueError("Underlying resource must be set to either SystemResource or DatastreamResource before initialization.") + raise ValueError( + "Underlying resource must be set to either SystemResource or DatastreamResource before initialization.") # This needs to be implemented separately for each subclass - self.ws_url = self._parent_node.get_api_helper().construct_url(parent_type=resource_type, res_type=APIResourceTypes.OBSERVATION, parent_res_id=self._underlying_resource.ds_id, res_id=None) - self._msg_queue = asyncio.Queue() + self.ws_url = self._parent_node.get_api_helper().construct_url(resource_type=resource_type, + subresource_type=APIResourceTypes.OBSERVATION, + resource_id=self._underlying_resource.ds_id, + subresource_id=None) + self._msg_reader_queue = asyncio.Queue() + self._msg_writer_queue = asyncio.Queue() + self.init_mqtt() self._status = Status.INITIALIZED.value def start(self): @@ -275,24 +314,88 @@ def start(self): logging.warning(f"Streamable resource {self._id} not initialized. Call initialize() first.") return self._status = Status.STARTING.value - # self._process.start() self._status = Status.STARTED.value - asyncio.gather(self.stream()) + # if asyncio.get_running_loop().is_running(): + # asyncio.create_task(self.stream()) + # else: + # loop = asyncio.get_event_loop() + # loop.create_task(self.stream()) async def stream(self): - if self._msg_queue is None: - self._msg_queue = asyncio.Queue() - session = self._parent_node.get_session() - # TODO: handle auth properly and not right here... - auth = BasicAuth("admin", "admin") + try: + async with session.ws_connect(self.ws_url, auth=self._parent_node.get_basicauth()) as ws: + logging.info(f"Streamable resource {self._id} started.") + read_task = asyncio.create_task(self._read_from_ws(ws)) + write_task = asyncio.create_task(self._write_to_ws(ws)) + await asyncio.gather(read_task, write_task) + except Exception as e: + logging.error(f"Error in streamable resource {self._id}: {e}") + logging.error(traceback.format_exc()) + + def init_mqtt(self): + if self._mqtt_client is None: + logging.warning(f"No MQTT client configured for streamable resource {self._id}.") + return + + self.get_mqtt_topic() + + def get_mqtt_topic(self, subresource: APIResourceTypes | None = None): + """ + Retrieves the MQTT topic for this streamable resource based on its underlying resource type. By default, the topic + is actually for listening to subresources of a default type + :param subresource : Optional subresource type to get the topic for, defaults to None + """ + resource_type = None + parent_res_type = None + res_id = None + parent_id = None + + if isinstance(self._underlying_resource, ControlStreamResource): + parent_res_type = APIResourceTypes.CONTROL_CHANNEL + resource_type = APIResourceTypes.COMMAND + parent_id = self._resource_id - async with session.ws_connect(self.ws_url, auth=auth) as ws: - logging.info(f"Streamable resource {self._id} started.") - async for msg in ws: - self._message_handler(ws, msg) + elif isinstance(self._underlying_resource, DatastreamResource): + parent_res_type = APIResourceTypes.DATASTREAM + resource_type = APIResourceTypes.OBSERVATION + parent_id = self._resource_id + + elif isinstance(self._underlying_resource, SystemResource): + match subresource: + case APIResourceTypes.DATASTREAM: + resource_type = APIResourceTypes.DATASTREAM + parent_res_type = APIResourceTypes.SYSTEM + parent_id = self._resource_id + case APIResourceTypes.CONTROL_CHANNEL: + resource_type = APIResourceTypes.CONTROL_CHANNEL + parent_res_type = APIResourceTypes.SYSTEM + parent_id = self._resource_id + case None: + resource_type = APIResourceTypes.SYSTEM + parent_res_type = None + parent_id = None + case _: + raise ValueError(f"Unsupported subresource type {subresource} for SystemResource.") + + topic = self._parent_node.get_api_helper().get_mqtt_topic(subresource_type=resource_type, + resource_id=parent_id, + resource_type=parent_res_type) + return topic + + async def _read_from_ws(self, ws): + async for msg in ws: + self._message_handler(ws, msg) + + async def _write_to_ws(self, ws): + while self._status is Status.STARTED.value: + try: + msg = self._msg_writer_queue.get_nowait() + await ws.send_bytes(msg) + except asyncio.QueueEmpty: + await asyncio.sleep(0.05) def stop(self): # It would be nicer to join() here once we have cleaner shutdown logic in place to avoid corrupting processes @@ -304,10 +407,10 @@ def stop(self): def _default_message_handler_fn(self, ws, msg): if msg.type == WSMsgType.TEXT: print(f"Received text message: {msg.data}") - self._msg_queue.put(msg.data) + self._msg_reader_queue.put(msg.data) elif msg.type == WSMsgType.BINARY: print(f"Received binary message: {msg.data}") - self._msg_queue.put(msg.data) + self._msg_reader_queue.put(msg.data) elif msg.type == WSMsgType.CLOSE: print("WebSocket closed") elif msg.type == WSMsgType.ERROR: @@ -319,26 +422,87 @@ def set_parent_node(self, node: Node): def get_parent_node(self) -> Node: return self._parent_node + def set_parent_resource_id(self, res_id: str): + self._parent_resource_id = res_id + + def get_parent_resource_id(self) -> str: + return self._parent_resource_id + def poll(self): pass def fetch(self, time_period: TimePeriod): pass - def get_msg_queue(self) -> Queue: + def get_msg_reader_queue(self) -> Queue: """ Returns the message queue for this streamable resource. In cases where a custom message handler is used this is not guaranteed to return anything or provided a queue with data. :return: Queue object """ - return self._msg_queue + return self._msg_reader_queue + + def get_msg_writer_queue(self) -> Queue: + """ + Returns the message queue for writing messages to this streamable resource. + :return: Queue object + """ + return self._msg_writer_queue def get_underlying_resource(self) -> T: return self._underlying_resource + def get_internal_id(self) -> UUID: + return self._id + + def insert_data(self, data: dict): + """ Naively inserts data into the message writer queue to be sent over the WebSocket connection. + No Checks are performed to ensure the data is valid for the underlying resource. + :param data: Data to be sent, typically bytes or str + """ + print(f"Inserting data into message writer queue: {data}") + data_bytes = json.dumps(data).encode("utf-8") if isinstance(data, dict) else data + self._msg_writer_queue.put_nowait(data_bytes) + + def subscribe_mqtt(self, topic: str, qos: int = 0): + if self._mqtt_client is None: + logging.warning(f"No MQTT client configured for streamable resource {self._id}.") + return + self._mqtt_client.subscribe(topic, qos=qos, msg_callback=self._message_handler) + + def _publish_mqtt(self, topic, payload): + if self._mqtt_client is None: + logging.warning(f"No MQTT client configured for streamable resource {self._id}.") + return + print(f'Publishing to MQTT topic {topic}: {payload}') + self._mqtt_client.publish(topic, payload, qos=0) + + async def _write_to_mqtt(self): + while self._status is Status.STARTED.value: + try: + msg = self._msg_writer_queue.get_nowait() + print(f"Popped message: {msg}, attempting to publish...") + self._publish_mqtt(self._topic, msg) + except asyncio.QueueEmpty: + await asyncio.sleep(0.05) + except Exception as e: + print(f"Error in Write To MQTT {self._id}: {e}") + print(traceback.format_exc()) + if self._status is Status.STOPPED.value: + print("MQTT write task stopping as streamable resource is stopped.") + + def set_connection_mode(self, mode: StreamableModes): + self._connection_mode = mode + + @abstractmethod + def publish(self, payload): + """ + Publishes data to the MQTT topic associated with this streamable resource. + """ + pass + class System(StreamableResource[SystemResource]): - resources_id: str name: str label: str datastreams: list[Datastream] @@ -362,7 +526,7 @@ def __init__(self, name: str, label: str, urn: str, parent_node: Node, **kwargs) self.control_channels = [] self.urn = urn if kwargs.get('resource_id'): - self.resource_id = kwargs['resource_id'] + self._resource_id = kwargs['resource_id'] if kwargs.get('description'): self.description = kwargs['description'] @@ -442,7 +606,7 @@ def add_insert_datastream(self, datastream: DataRecordSchema): datastream_resource.model_dump_json(by_alias=True, exclude_none=True), req_headers={ 'Content-Type': 'application/json' - }, parent_res_id=self.resource_id) + }, parent_res_id=self._resource_id) if res.ok: datastream_id = res.headers['Location'].split('/')[-1] @@ -452,7 +616,9 @@ def add_insert_datastream(self, datastream: DataRecordSchema): raise Exception(f'Failed to create datastream: {datastream_resource.name}') self.datastreams.append(datastream_resource) - return Datastream(datastream_id, self._parent_node, datastream_resource) + new_ds = Datastream(datastream_id, self._parent_node, datastream_resource) + new_ds.set_parent_resource_id(self._underlying_resource.system_id) + return new_ds def insert_self(self): res = self._parent_node.get_api_helper().create_resource( @@ -464,14 +630,14 @@ def insert_self(self): if res.ok: location = res.headers['Location'] sys_id = location.split('/')[-1] - self.resource_id = sys_id - print(f'Created system: {self.resource_id}') + self._resource_id = sys_id + print(f'Created system: {self._resource_id}') def retrieve_resource(self): - if self.resource_id is None: + if self._resource_id is None: return None res = self._parent_node.get_api_helper().retrieve_resource(res_type=APIResourceTypes.SYSTEM, - res_id=self.resource_id) + res_id=self._resource_id) if res.ok: system_json = res.json() print(system_json) @@ -480,19 +646,20 @@ def retrieve_resource(self): self._underlying_resource = system_resource return None + def publish(self, payload): + self._publish_mqtt(self.get_mqtt_topic(), payload) + class Datastream(StreamableResource[DatastreamResource]): should_poll: bool - resource_id: str # _datastream_resource: DatastreamResource _parent_node: Node def __init__(self, id: str = None, parent_node: Node = None, datastream_resource: DatastreamResource = None): super().__init__(node=parent_node) - self.resource_id = id self._parent_node = parent_node - # self._datastream_resource = datastream_resource self._underlying_resource = datastream_resource + self._resource_id = datastream_resource.ds_id def get_id(self): return self._underlying_resource.ds_id @@ -523,7 +690,7 @@ def create_observation(self, obs_data: dict): def insert_observation_dict(self, obs_data: dict): res = self._parent_node.get_api_helper().create_resource(APIResourceTypes.OBSERVATION, obs_data, - parent_res_id=self.resource_id, + parent_res_id=self._resource_id, req_headers={'Content-Type': 'application/json'}) if res.ok: obs_id = res.headers['Location'].split('/')[-1] @@ -532,23 +699,62 @@ def insert_observation_dict(self, obs_data: dict): else: raise Exception(f'Failed to insert observation: {res.text}') - # def initialize(self): + def start(self): + super().start() + if self._mqtt_client is not None: + self._mqtt_client.connect() + self._mqtt_client.start() + self.subscribe_mqtt(self._topic) + if self._connection_mode is StreamableModes.PULL or self._connection_mode is StreamableModes.BIDIRECTIONAL: + self._mqtt_client.subscribe(self._topic, msg_callback=self._mqtt_sub_callback) + + try: + loop = asyncio.get_event_loop() + loop.create_task(self._write_to_mqtt()) + except Exception as e: + # TODO: Use logging instead of print + print(traceback.format_exc()) + print(f"Error starting MQTT write task: {e}") + def init_mqtt(self): + super().init_mqtt() + self._topic = self.get_mqtt_topic(subresource=APIResourceTypes.OBSERVATION) - # def create_from_record_schema(record_schema: DataRecordSchema, parent_system: System): - # new_ds = Datastream(name=record_schema.label, record_schema=record_schema) - # new_ds._datastream_resource = DatastreamResource(ds_id=uuid.uuid4(), name=new_ds.name) - # parent_system.datastreams.append(new_ds) - # return new_ds + def publish(self, payload): + self._publish_mqtt(self._topic, payload) + def _queue_push(self, msg): + print(f'Pushing message to reader queue: {msg}') + self._msg_writer_queue.put_nowait(msg) + print(f'Queue size is now: {self._msg_writer_queue.qsize()}') -class ControlChannel: - _cc_resource: ControlStreamResource - resource_id: str - _parent_node: Node + def _queue_pop(self): + return self._msg_reader_queue.get_nowait() - def __init__(self): - pass + def _mqtt_sub_callback(self, client, userdata, msg): + print(f"MQTT Message received on topic {msg.topic}: {msg.payload}") + self._queue_push(msg.payload) + + def insert(self, data: dict): + # self._queue_push(data) + encoded = json.dumps(data).encode('utf-8') + self._publish_mqtt(self._topic, encoded) + + +class ControlChannel(StreamableResource[ControlStreamResource]): + + def __init__(self, node: Node = None): + super().__init__(node=node) + + def add_underlying_resource(self, resource: ControlStreamResource): + self._underlying_resource = resource + + def init_mqtt(self): + super().init_mqtt() + self._topic = self.get_mqtt_topic(subresource=APIResourceTypes.COMMAND) + + def publish(self, payload): + self._publish_mqtt(self._topic, payload) class Observation: From 145971db15684b0e3482779656c6d4c609aa4af8 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 8 Oct 2025 00:35:57 -0500 Subject: [PATCH 28/31] fix common connection issues for MQTT to support pub/sub --- .../csapi4py/default_api_helpers.py | 23 ++++++++ src/oshconnect/csapi4py/mqtt.py | 6 +-- src/oshconnect/eventbus.py | 2 +- src/oshconnect/oshconnectapi.py | 4 +- src/oshconnect/streamableresource.py | 52 ++++++++++++------- 5 files changed, 64 insertions(+), 23 deletions(-) diff --git a/src/oshconnect/csapi4py/default_api_helpers.py b/src/oshconnect/csapi4py/default_api_helpers.py index 08a2468..25c70da 100644 --- a/src/oshconnect/csapi4py/default_api_helpers.py +++ b/src/oshconnect/csapi4py/default_api_helpers.py @@ -137,6 +137,29 @@ def retrieve_resource(self, res_type: APIResourceTypes, res_id: str = None, pare headers=req_headers) return api_request.make_request() + def get_resource(self, resource_type: APIResourceTypes, resource_id: str = None, + subresource_type: APIResourceTypes = None, + req_headers: dict = None): + + """ + Helper to get resources by type, specifically by id, and optionally a sub-resource collection of a specified resource. + :param resource_type: + :param resource_id: + :param subresource_type: + :param req_headers: + :return: + """ + if req_headers is None: + req_headers = {} + base_api_url = self.get_api_root_url() + resource_type_str = resource_type_to_endpoint(resource_type) + res_id_str = f'/{resource_id}' if resource_id else "" + sub_res_type_str = f'/{resource_type_to_endpoint(subresource_type)}' if subresource_type else "" + complete_url = f'{base_api_url}/{resource_type_str}{res_id_str}{sub_res_type_str}' + api_request = ConnectedSystemAPIRequest(url=complete_url, request_method='GET', auth=self.get_helper_auth(), + headers=req_headers) + return api_request.make_request() + def update_resource(self, res_type: APIResourceTypes, res_id: str, json_data: any, parent_res_id: str = None, from_collection: bool = False, url_endpoint: str = None, req_headers: dict = None): """ diff --git a/src/oshconnect/csapi4py/mqtt.py b/src/oshconnect/csapi4py/mqtt.py index d593b7e..5fb95d0 100644 --- a/src/oshconnect/csapi4py/mqtt.py +++ b/src/oshconnect/csapi4py/mqtt.py @@ -2,7 +2,7 @@ class MQTTCommClient: - def __init__(self, url, port=1883, username=None, password=None, path='mqtt', client_id="", transport='tcp'): + def __init__(self, url, port=1883, username=None, password=None, path='mqtt', client_id_suffix="", transport='tcp'): """ Wraps a paho mqtt client to provide a simple interface for interacting with the mqtt server that is customized for this library. @@ -17,10 +17,10 @@ def __init__(self, url, port=1883, username=None, password=None, path='mqtt', cl self.__url = url self.__port = port self.__path = path - self.__client_id = client_id + self.__client_id = f'oscapy_mqtt-{client_id_suffix}' self.__transport = transport - self.__client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=client_id) + self.__client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=self.__client_id) if self.__transport == 'websockets': self.__client.ws_set_options(path=self.__path) diff --git a/src/oshconnect/eventbus.py b/src/oshconnect/eventbus.py index b3a7f9e..7040d2f 100644 --- a/src/oshconnect/eventbus.py +++ b/src/oshconnect/eventbus.py @@ -12,4 +12,4 @@ class EventBus(ABC): """ A base class for an event bus system. """ - _deque: collections.deque \ No newline at end of file + _deque: collections.deque diff --git a/src/oshconnect/oshconnectapi.py b/src/oshconnect/oshconnectapi.py index 741d319..0883d64 100644 --- a/src/oshconnect/oshconnectapi.py +++ b/src/oshconnect/oshconnectapi.py @@ -121,7 +121,9 @@ def discover_datastreams(self): datastreams = list( map(lambda ds: Datastream(parent_node=system.get_parent_node(), id=ds.ds_id, datastream_resource=ds), res_datastreams)) - datastreams = [ds.set_parent_resource_id(system.get_underlying_resource().system_id) for ds in datastreams] + for ds in datastreams: + ds.set_parent_resource_id(system.get_underlying_resource().system_id) + # datastreams = [ds.set_parent_resource_id(system.get_underlying_resource().system_id) for ds in datastreams] self._datastreams.extend(datastreams) def discover_systems(self, nodes: list[str] = None): diff --git a/src/oshconnect/streamableresource.py b/src/oshconnect/streamableresource.py index 728c009..e5d2618 100644 --- a/src/oshconnect/streamableresource.py +++ b/src/oshconnect/streamableresource.py @@ -149,7 +149,9 @@ def __init__(self, protocol: str, address: str, port: int, if kwargs.get('enable_mqtt'): if kwargs.get('mqtt_port') is not None: self._mqtt_port = kwargs.get('mqtt_port') - self._mqtt_client = MQTTCommClient(url=self.address, port=self._mqtt_port) + self._mqtt_client = MQTTCommClient(url=self.address, port=self._mqtt_port, client_id_suffix=uuid.uuid4().hex,) + self._mqtt_client.connect() + self._mqtt_client.start() # self._mqtt_client = MQTTCommClient(url=self.address + self.server_root, port=self._mqtt_port, # username=username, password=password, ) @@ -249,6 +251,7 @@ class Status(Enum): STOPPING = "stopping" STOPPED = "stopped" + class StreamableModes(Enum): PUSH = "push" PULL = "pull" @@ -340,7 +343,12 @@ def init_mqtt(self): logging.warning(f"No MQTT client configured for streamable resource {self._id}.") return - self.get_mqtt_topic() + self._mqtt_client.set_on_subscribe(self._default_on_subscribe) + + # self.get_mqtt_topic() + + def _default_on_subscribe(self, client, userdata, mid, granted_qos, properties): + print("OSH Subscribed: "+str(mid)+" "+str(granted_qos)) def get_mqtt_topic(self, subresource: APIResourceTypes | None = None): """ @@ -468,7 +476,7 @@ def subscribe_mqtt(self, topic: str, qos: int = 0): if self._mqtt_client is None: logging.warning(f"No MQTT client configured for streamable resource {self._id}.") return - self._mqtt_client.subscribe(topic, qos=qos, msg_callback=self._message_handler) + self._mqtt_client.subscribe(topic, qos=qos, msg_callback=self._mqtt_sub_callback) def _publish_mqtt(self, topic, payload): if self._mqtt_client is None: @@ -502,6 +510,11 @@ def publish(self, payload): pass + def _mqtt_sub_callback(self, client, userdata, msg): + print(f"Received MQTT message on topic {msg.topic}: {msg.payload}") + self._msg_reader_queue.put_nowait(msg.payload) + + class System(StreamableResource[SystemResource]): name: str label: str @@ -534,8 +547,10 @@ def __init__(self, name: str, label: str, urn: str, parent_node: Node, **kwargs) # self.underlying_resource = self._sys_resource def discover_datastreams(self) -> list[DatastreamResource]: - res = self._parent_node.get_api_helper().retrieve_resource( - APIResourceTypes.DATASTREAM, req_headers={}) + # res = self._parent_node.get_api_helper().retrieve_resource( + # APIResourceTypes.DATASTREAM, req_headers={}) + res = self._parent_node.get_api_helper().get_resource(APIResourceTypes.SYSTEM, self._resource_id, + APIResourceTypes.DATASTREAM) datastream_json = res.json()['items'] ds_resources = [] @@ -702,19 +717,20 @@ def insert_observation_dict(self, obs_data: dict): def start(self): super().start() if self._mqtt_client is not None: - self._mqtt_client.connect() - self._mqtt_client.start() - self.subscribe_mqtt(self._topic) + # self._mqtt_client.connect() + if self._connection_mode is StreamableModes.PULL or self._connection_mode is StreamableModes.BIDIRECTIONAL: self._mqtt_client.subscribe(self._topic, msg_callback=self._mqtt_sub_callback) + else: + try: + loop = asyncio.get_event_loop() + loop.create_task(self._write_to_mqtt()) + except Exception as e: + # TODO: Use logging instead of print + print(traceback.format_exc()) + print(f"Error starting MQTT write task: {e}") - try: - loop = asyncio.get_event_loop() - loop.create_task(self._write_to_mqtt()) - except Exception as e: - # TODO: Use logging instead of print - print(traceback.format_exc()) - print(f"Error starting MQTT write task: {e}") + # self._mqtt_client.start() def init_mqtt(self): super().init_mqtt() @@ -731,9 +747,9 @@ def _queue_push(self, msg): def _queue_pop(self): return self._msg_reader_queue.get_nowait() - def _mqtt_sub_callback(self, client, userdata, msg): - print(f"MQTT Message received on topic {msg.topic}: {msg.payload}") - self._queue_push(msg.payload) + # def _mqtt_sub_callback(self, client, userdata, msg): + # print(f"MQTT Message received on topic {msg.topic}: {msg.payload}") + # self._queue_push(msg.payload) def insert(self, data: dict): # self._queue_push(data) From 2e834c748675cbeed226ab16f4921a70322ccef1 Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 8 Oct 2025 00:41:29 -0500 Subject: [PATCH 29/31] fix small flake8 failure --- src/oshconnect/streamableresource.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/oshconnect/streamableresource.py b/src/oshconnect/streamableresource.py index e5d2618..449e4a2 100644 --- a/src/oshconnect/streamableresource.py +++ b/src/oshconnect/streamableresource.py @@ -149,7 +149,8 @@ def __init__(self, protocol: str, address: str, port: int, if kwargs.get('enable_mqtt'): if kwargs.get('mqtt_port') is not None: self._mqtt_port = kwargs.get('mqtt_port') - self._mqtt_client = MQTTCommClient(url=self.address, port=self._mqtt_port, client_id_suffix=uuid.uuid4().hex,) + self._mqtt_client = MQTTCommClient(url=self.address, port=self._mqtt_port, + client_id_suffix=uuid.uuid4().hex, ) self._mqtt_client.connect() self._mqtt_client.start() # self._mqtt_client = MQTTCommClient(url=self.address + self.server_root, port=self._mqtt_port, @@ -348,7 +349,7 @@ def init_mqtt(self): # self.get_mqtt_topic() def _default_on_subscribe(self, client, userdata, mid, granted_qos, properties): - print("OSH Subscribed: "+str(mid)+" "+str(granted_qos)) + print("OSH Subscribed: " + str(mid) + " " + str(granted_qos)) def get_mqtt_topic(self, subresource: APIResourceTypes | None = None): """ @@ -358,7 +359,7 @@ def get_mqtt_topic(self, subresource: APIResourceTypes | None = None): """ resource_type = None parent_res_type = None - res_id = None + # res_id = None parent_id = None if isinstance(self._underlying_resource, ControlStreamResource): @@ -509,7 +510,6 @@ def publish(self, payload): """ pass - def _mqtt_sub_callback(self, client, userdata, msg): print(f"Received MQTT message on topic {msg.topic}: {msg.payload}") self._msg_reader_queue.put_nowait(msg.payload) From 72e6cc3d33d40445d609b07530f1296d410a7a0a Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Wed, 8 Oct 2025 00:44:40 -0500 Subject: [PATCH 30/31] change flake8 source directory --- .github/workflows/linting.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linting.yaml b/.github/workflows/linting.yaml index 9a13d9c..6d8d3d8 100644 --- a/.github/workflows/linting.yaml +++ b/.github/workflows/linting.yaml @@ -18,4 +18,4 @@ jobs: - name: Lint run: | - uv run flake8 oshconnect \ No newline at end of file + uv run flake8 src/oshconnect \ No newline at end of file From c269550b1e84f86ef980feea6a1d9f7cb952860f Mon Sep 17 00:00:00 2001 From: Ian Patterson Date: Thu, 16 Oct 2025 13:29:55 -0500 Subject: [PATCH 31/31] - update MQTT functions - refine ControlStream methods - remove some unneeded code --- pyproject.toml | 2 +- src/oshconnect/__init__.py | 2 +- src/oshconnect/control.py | 64 --- src/oshconnect/csapi4py/constants.py | 4 +- .../csapi4py/default_api_helpers.py | 52 +- src/oshconnect/datasource.py | 536 ------------------ src/oshconnect/oshconnectapi.py | 13 +- src/oshconnect/resource_datamodels.py | 10 +- src/oshconnect/schema_datamodels.py | 39 +- src/oshconnect/streamableresource.py | 327 +++++++---- uv.lock | 2 +- 11 files changed, 289 insertions(+), 762 deletions(-) delete mode 100644 src/oshconnect/control.py delete mode 100644 src/oshconnect/datasource.py diff --git a/pyproject.toml b/pyproject.toml index 2901dcf..a45aada 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oshconnect" -version = "0.3.0a4" +version = "0.3.0a5" description = "Library for interfacing with OSH, helping guide visualization efforts, and providing a place to store configurations." readme = "README.md" authors = [ diff --git a/src/oshconnect/__init__.py b/src/oshconnect/__init__.py index 708396b..c4d111c 100644 --- a/src/oshconnect/__init__.py +++ b/src/oshconnect/__init__.py @@ -6,4 +6,4 @@ # ============================================================================== from .oshconnectapi import OSHConnect -from .streamableresource import System, Node, Datastream, Observation, ControlChannel +from .streamableresource import System, Node, Datastream, ControlStream diff --git a/src/oshconnect/control.py b/src/oshconnect/control.py deleted file mode 100644 index c4b4ddb..0000000 --- a/src/oshconnect/control.py +++ /dev/null @@ -1,64 +0,0 @@ -# ============================================================================== -# Copyright (c) 2024 Botts Innovative Research, Inc. -# Date: 2024/7/1 -# Author: Ian Patterson -# Contact Email: ian@botts-inc.com -# ============================================================================== -import websockets - -from csapi4py.mqtt import MQTTCommClient -from schema_datamodels import ControlStreamJSONSchema, CommandJSON -from src.oshconnect import System - - -class ControlSchema: - schema: dict = None - - -class ControlStream: - name: str = None - _parent_systems: System = None - _strategy: str = "mqtt" - _resource_endpoint = None - # _auth: str = None - _websocket: websockets.WebSocketServerProtocol = None - _schema: ControlStreamJSONSchema = None - _mqtt_client: MQTTCommClient = None - - def __init__(self, parent_system: System, resource_endpoint: str, name=None, strategy="mqtt"): - self._parent_systems = parent_system - self.name = name - self._strategy = strategy - self._resource_endpoint = resource_endpoint - - def set_schema(self, schema: ControlStreamJSONSchema): - self._schema = schema - - def connect(self): - pass - - def subscribe(self): - if self._strategy == "mqtt" and self._mqtt_client is not None: - self._mqtt_client.subscribe(f'{self._resource_endpoint}/commands') - elif self._strategy == "mqtt" and self._mqtt_client is None: - raise ValueError("No MQTT Client found.") - elif self._strategy == "websocket": - pass - - def publish(self, payload: CommandJSON): - if self._strategy == "mqtt" and self._mqtt_client is not None: - self._mqtt_client.publish(f'{self._resource_endpoint}/status', payload=payload, qos=1) - elif self._strategy == "mqtt" and self._mqtt_client is None: - raise ValueError("No MQTT Client found.") - elif self._strategy == "websocket": - pass - - def disconnect(self): - pass - - def unsubscribe(self): - self._mqtt_client.unsubscribe(f'{self._resource_endpoint}/commands') - - -class Command: - pass diff --git a/src/oshconnect/csapi4py/constants.py b/src/oshconnect/csapi4py/constants.py index 94d4ceb..a304f7f 100644 --- a/src/oshconnect/csapi4py/constants.py +++ b/src/oshconnect/csapi4py/constants.py @@ -91,9 +91,11 @@ class APIResourceTypes(Enum): SYSTEM = "System" SYSTEM_EVENT = "SystemEvent" SYSTEM_HISTORY = "SystemHistory" + STATUS = "Status" + SCHEMA = "Schema" -class EncodingSchema(Enum): +class ContentTypes(Enum): """ Defines the encoding formats """ diff --git a/src/oshconnect/csapi4py/default_api_helpers.py b/src/oshconnect/csapi4py/default_api_helpers.py index 25c70da..4ecce4b 100644 --- a/src/oshconnect/csapi4py/default_api_helpers.py +++ b/src/oshconnect/csapi4py/default_api_helpers.py @@ -13,7 +13,7 @@ from pydantic import BaseModel, Field from .con_sys_api import ConnectedSystemAPIRequest -from .constants import APIResourceTypes, EncodingSchema, APITerms +from .constants import APIResourceTypes, ContentTypes, APITerms # TODO: rework to make the first resource in the endpoint the primary key for URL construction, currently, the implementation is a bit on the confusing side with what is being generated and why. @@ -77,6 +77,10 @@ def resource_type_to_endpoint(res_type: APIResourceTypes, parent_type: APIResour return APITerms.HISTORY.value case APIResourceTypes.DEPLOYMENT: return APITerms.DEPLOYMENTS.value + case APIResourceTypes.STATUS: + return APITerms.STATUS.value + case APIResourceTypes.SCHEMA: + return APITerms.SCHEMA.value case _: raise ValueError('Invalid resource type') @@ -279,16 +283,24 @@ def set_protocol(self, protocol: str): raise ValueError('Protocol must be either "http" or "https"') self.protocol = protocol - def get_mqtt_topic(self, resource_type, subresource_type, resource_id: str, - for_socket: bool = False): + # TODO: add validity checking for resource type combinations + def get_mqtt_topic(self, resource_type, subresource_type, resource_id: str, subresource_id: str = None): """ - Returns the MQTT topic for the resource type, if applicable. + Returns the MQTT topic for the resource type, does not check for validity of the resource type combination + :param resource_type : The API resource type of the resource that comes first in the URL, cannot be None + :param subresource_type: The API resource type of the sub-resource that comes second in the URL, optional if there + is no sub-resource. + :param resource_id: The ID of the primary resource, can be none if the request is being made for all resources of + the given type. + :param subresource_id: The ID of the sub-resource, can be none if the request is being made for all sub-resources of + the given type. :return: """ - resource_endpoint = f'/{resource_type_to_endpoint(subresource_type, resource_type)}' - parent_endpoint = "" if resource_type is None else f'/{resource_type_to_endpoint(resource_type)}' - parent_id = "" if resource_id is None else f'/{resource_id}' - topic_locator = f'/{self.api_root}{parent_endpoint}{parent_id}{resource_endpoint}' + subresource_endpoint = f'/{resource_type_to_endpoint(subresource_type)}' + resource_endpoint = "" if resource_type is None else f'/{resource_type_to_endpoint(resource_type)}' + resource_ident = "" if resource_id is None else f'/{resource_id}' + subresource_ident = "" if subresource_id is None else f'/{subresource_id}' + topic_locator = f'/{self.api_root}{resource_endpoint}{resource_ident}{subresource_endpoint}{subresource_ident}' print(f'MQTT Topic: {topic_locator}') return topic_locator @@ -305,17 +317,17 @@ class DefaultObjectRepresentations(BaseModel): Should work in tandem with planned Serializer/Deserializer classes. """ # Part 1 - collections: str = Field(EncodingSchema.JSON.value) - deployments: str = Field(EncodingSchema.GEO_JSON.value) - procedures: str = Field(EncodingSchema.GEO_JSON.value) - properties: str = Field(EncodingSchema.SML_JSON.value) - sampling_features: str = Field(EncodingSchema.GEO_JSON.value) - systems: str = Field(EncodingSchema.GEO_JSON.value) + collections: str = Field(ContentTypes.JSON.value) + deployments: str = Field(ContentTypes.GEO_JSON.value) + procedures: str = Field(ContentTypes.GEO_JSON.value) + properties: str = Field(ContentTypes.SML_JSON.value) + sampling_features: str = Field(ContentTypes.GEO_JSON.value) + systems: str = Field(ContentTypes.GEO_JSON.value) # Part 2 - datastreams: str = Field(EncodingSchema.JSON.value) - observations: str = Field(EncodingSchema.JSON.value) - control_channels: str = Field(EncodingSchema.JSON.value) - commands: str = Field(EncodingSchema.JSON.value) - system_events: str = Field(EncodingSchema.OM_JSON.value) - system_history: str = Field(EncodingSchema.GEO_JSON.value) + datastreams: str = Field(ContentTypes.JSON.value) + observations: str = Field(ContentTypes.JSON.value) + control_channels: str = Field(ContentTypes.JSON.value) + commands: str = Field(ContentTypes.JSON.value) + system_events: str = Field(ContentTypes.OM_JSON.value) + system_history: str = Field(ContentTypes.GEO_JSON.value) # TODO: validate schemas for each resource to amke sure they are allowed per the spec diff --git a/src/oshconnect/datasource.py b/src/oshconnect/datasource.py deleted file mode 100644 index b3cc637..0000000 --- a/src/oshconnect/datasource.py +++ /dev/null @@ -1,536 +0,0 @@ -# ============================================================================== -# Copyright (c) 2024 Botts Innovative Research, Inc. -# Date: 2024/6/26 -# Author: Ian Patterson -# Contact Email: ian@botts-inc.com -# ============================================================================== -# -# Author: Ian Patterson -# -# Contact Email: ian@botts-inc.com -from __future__ import annotations - -import asyncio -import json -from uuid import uuid4 - -import requests -import websockets -from .csapi4py.constants import APIResourceTypes -from .schema_datamodels import ObservationOMJSONInline -from .swe_components import DataRecordSchema - -from .resource_datamodels import DatastreamResource, TimePeriod -from .timemanagement import TemporalModes, Synchronizer - - -# from swecommondm.component_implementations import DataRecord - - -class DataStream: - """ - To Be Deprecated with next minor prerelease version of OSHConnect - DataStream: represents the active connection of a datastream object. - This class may later be used to connect to a control channel as well. It will almost certainly be used - for Control Stream status monitoring. - - Attributes: - name: Human readable name of the DataSource - _datastream: DatastreamResource object - _parent_system: System object that the DataSource is associated with. - """ - name: str = None - _id: str = None - _datastream: DatastreamResource = None - # _parent_system: SystemResource = None - _playback_mode: TemporalModes = None - _url: str = None - _auth: str = None - _playback_websocket: websockets.WebSocketClientProtocol = None - _extra_headers: dict = None - _result_schema: DataRecordSchema = None - _synchronizer: Synchronizer = None - - def __init__(self, name: str, datastream: DatastreamResource): - """ - :param name: Human-readable name of the DataSource - :param datastream: DatastreamResource object - :param parent_system: System object that the DataSource is associated with. - """ - self._status = None - self._id = f'datasource-{uuid4()}' - self.name = name - self._datastream = datastream - self._playback_websocket = None - # self._parent_system = parent_system - self._playback_mode = None - self._url = None - self._auth = None - self._extra_headers = None - # if self._parent_system._parent_node().is_secure: - # self._auth = self._parent_system._parent_node().get_decoded_auth() - # self._extra_headers = {'Authorization': f'Basic {self._auth}'} - - def get_id(self) -> str: - """ - Get the ID of the DataSource - - :return: str UID of the DataSource - """ - return self._id - - def get_name(self): - """ - Get the name of the DataSource - - :return: str name of the DataSource - """ - return self.name - - # def set_playback_mode(self, mode: TemporalModes): - # """ - # Sets the playback mode of the DataSource and regenerates the URL accordingly - # - # :param mode: TemporalModes - # - # :return: - # """ - # self._playback_mode = mode - # self.generate_retrieval_url() - - def initialize(self): - """ - Initializes the DataSource object, resetting the status and closing any open connections if necessary. - - :return: - """ - if self._playback_websocket.is_open(): - self._playback_websocket.close() - self._playback_websocket = None - self._status = "initialized" - - async def connect(self) -> websockets.WebSocketClientProtocol or None: - """ - Attempts to connect to the DataSource's websocket, or HTTP endpoint if in BATCH mode. This is currently for retrieval - :return: The websocket connection if in REAL_TIME or ARCHIVE mode, ``None`` if in BATCH mode. - """ - if self._playback_mode == TemporalModes.REAL_TIME: - self._playback_websocket = await websockets.connect(self._url, - extra_headers=self._extra_headers) - self._status = "connected" - return self._playback_websocket - elif self._playback_mode == TemporalModes.ARCHIVE: - self._playback_websocket = await websockets.connect(self._url, - extra_headers=self._extra_headers) - self._status = "connected" - return self._playback_websocket - elif self._playback_mode == TemporalModes.BATCH: - self._playback_websocket = await websockets.connect(self._url, - extra_headers=self._extra_headers) - self._status = "connected" - return None - - def disconnect(self): - """ - Closes the websocket connection, *should* also stop any future http requests if in BATCH mode. This feature - is *WIP*. - - :return: - """ - self._playback_websocket.close() - - def reset(self): - """ - Resets the DataSource object, closing any open connections and resetting the status. Currently has the same - effect as ``initialize()``. - - :return: - """ - if self._playback_websocket.is_open(): - self._playback_websocket.close() - self._playback_websocket = None - self._status = "initialized" - - def get_status(self): - """ - Get the status code of the DataSource - - :return: - """ - return self._status - - # def get_parent_system(self) -> SystemResource: - # """ - # Retrieve the DataSource's parent System - # - # :return: The parent System object of the DataSource - # """ - # return self._parent_system - - def get_ws_client(self): - """ - Get the websocket client object - - :return: - """ - return self._playback_websocket - - def is_within_timeperiod(self, timeperiod: TimePeriod) -> bool: - """ - Checks if the DataSource's Datastream is within the provided TimePeriod - - :param timeperiod: TimePeriod object - :return: ``True`` if the Datastream is within the TimePeriod, ``False`` otherwise - """ - return timeperiod.does_timeperiod_overlap(self._datastream.valid_time) - - def generate_retrieval_url(self): - """ - Generates the URL for the DataSource based on the playback mode. This url is used for accessing the datastream - on the OSH server. - - :return: - """ - # TODO: need to specify secure vs insecure protocols - if self._playback_mode == TemporalModes.REAL_TIME: - self._url = ( - f'ws://{self._parent_system.get_parent_node().get_address()}:' - f'{self._parent_system.get_parent_node().get_port()}' - f'/sensorhub/api/datastreams/{self._datastream.ds_id}' - f'/observations?f=application%2Fjson') - elif self._playback_mode == TemporalModes.ARCHIVE: - self._url = ( - f'ws://{self._parent_system.get_parent_node().get_address()}:' - f'{self._parent_system.get_parent_node().get_port()}' - f'/sensorhub/api/datastreams/{self._datastream.ds_id}' - f'/observations?f=application%2Fjson&resultTime={self._datastream.valid_time.start}/' - f'{self._datastream.valid_time.end}') - elif self._playback_mode == TemporalModes.BATCH: - # TODO: need to allow for batch counts selection through DS Handler or TimeManager - self._url = ( - f'wss://{self._parent_system.get_parent_node().get_address()}:' - f'{self._parent_system.get_parent_node().get_port()}' - f'/sensorhub/api/datastreams/{self._datastream.ds_id}' - f'/observations?f=application%2Fjson&resultTime={self._datastream.valid_time.start}/' - f'{self._datastream.valid_time.end}') - else: - raise ValueError( - "Playback mode not set. Cannot generate URL for DataSource.") - - def generate_insertion_url(self) -> str: - """ - Generates the URL for the DataSource. This url is used for posting data to the - OSH server. - - :return: - """ - url_result = ( - f'http://{self._parent_system.get_parent_node().get_address()}:' - f'{self._parent_system.get_parent_node().get_port()}' - f'/sensorhub/api/datastreams/{self._datastream.ds_id}' - f'/observations' - ) - return url_result - - def insert_observation(self, observation: ObservationOMJSONInline): - """ - Posts an observation to the server - :param observation: ObservationOMJSONInline object - :return: - """ - api_helper = self._parent_system.get_parent_node().get_api_helper() - api_helper.post_resource(APIResourceTypes.OBSERVATION, parent_res_id=self._datastream.ds_id, - data=observation.model_dump(), req_headers={'Content-Type': 'application/om+json'}) - - -class DataStreamHandler: - """ - Manages a collection of DataSource objects, allowing for easy access and control of multiple datastreams. As well - as providing them access to a message handler for processing incoming data. - """ - datasource_map: dict[str, DataStream] - _message_list: MessageHandler - _playback_mode: TemporalModes - - def __init__(self, playback_mode: TemporalModes = TemporalModes.REAL_TIME): - self.datasource_map = {} - self._message_list = MessageHandler() - self._playback_mode = playback_mode - - def set_playback_mode(self, mode: TemporalModes): - """ - Sets the playback mode for the DataSourceHandler and all of its DataSources - - :param mode: TemporalModes - - :return: - """ - self._playback_mode = mode - - def add_datasource(self, datasource: DataStream): - """ - Adds a DataSource object to the DataSourceHandler - - :param datasource: DataSource - - :return: - """ - # datasource.set_playback_mode(self._playback_mode) - self.datasource_map[datasource.get_id()] = datasource - - def remove_datasource(self, datasource_id: str) -> DataStream: - """ - Removes a DataSource object from the DataSourceHandler - - :param datasource_id: str uid of the DataSource - - :return: the removed DataSource object - """ - return self.datasource_map.pop(datasource_id) - - def initialize_ds(self, datasource_id: str): - """ - Initializes a DataSource object by calling its initialize method - - :param datasource_id: - - :return: - """ - ds = self.datasource_map.get(datasource_id) - ds.initialize() - - def initialize_all(self): - """ - Initializes all DataSource objects in the DataSourceHandler - - :return: - """ - [ds.initialize() for ds in self.datasource_map.values()] - - def set_ds_mode(self): - """ - Sets the playback mode for all DataSource objects in the DataSourceHandler, uses the playback mode of the - DataSourceHandler - :return: - """ - (ds.set_playback_mode(self._playback_mode) for ds in self.datasource_map.values()) - - async def connect_ds(self, datasource_id: str): - """ - Connects a DataSource object by calling its connect method - - :param datasource_id: - - :return: - """ - ds = self.datasource_map.get(datasource_id) - await ds.connect() - - async def connect_all(self, timeperiod: TimePeriod): - """ - Connects all datasources, optionally within a provided TimePeriod - :param timeperiod: TimePeriod object - :return: - """ - # search for datasources that fall within the timeperiod - if timeperiod is not None: - ds_matches = [ds for ds in self.datasource_map.values() if - ds.is_within_timeperiod(timeperiod)] - else: - ds_matches = self.datasource_map.values() - - if self._playback_mode == TemporalModes.REAL_TIME: - [(ds, await ds.connect()) for ds in ds_matches] - for ds in ds_matches: - asyncio.create_task(self._handle_datastream_client(ds)) - elif self._playback_mode == TemporalModes.ARCHIVE: - pass - elif self._playback_mode == TemporalModes.BATCH: - for ds in ds_matches: - asyncio.create_task(self.handle_http_batching(ds)) - - def disconnect_ds(self, datasource_id: str): - """ - Disconnects a DataSource object by calling its disconnect method - :param datasource_id: - :return: - """ - ds = self.datasource_map.get(datasource_id) - ds.disconnect() - - def disconnect_all(self): - """ - Disconnects all DataSource objects in the DataSourceHandler - :return: - """ - [ds.disconnect() for ds in self.datasource_map.values()] - - async def _handle_datastream_client(self, datasource: DataStream): - """ - Handles the websocket client for a DataSource object, passes Observations to the MessageHandler in the - form of MessageWrapper objects - - :param datasource: - - :return: - """ - try: - async for msg in datasource.get_ws_client(): - msg_dict = json.loads(msg.decode('utf-8')) - obs = ObservationOMJSONInline.model_validate(msg_dict) - msg_wrapper = MessageWrapper(datasource=datasource, - message=obs) - self._message_list.add_message(msg_wrapper) - - except Exception as e: - print(f"An error occurred while reading from websocket: {e}") - - async def handle_http_batching(self, datasource: DataStream, - offset: int = None, - query_params: dict = None, - next_link: str = None) -> dict: - """ - Handles the batching of HTTP requests for a DataSource object, passes Observations to the MessageHandler - - :param datasource: - :param offset: - :param query_params: - :param next_link: - - :return: dict of the response from the server - """ - # access api_helper - api_helper = datasource.get_parent_system().get_parent_node().get_api_helper() - # needs to create a new call to make a request to the server if there is a link to a next page - resp = None - if next_link is None: - resp = api_helper.retrieve_resource(APIResourceTypes.OBSERVATION, - parent_res_id=datasource._datastream.ds_id, - req_headers={ - 'Content-Type': 'application/json'}) - elif next_link is not None: - resp = requests.get(next_link, auth=( - datasource._parent_system.get_parent_node()._api_helper.username, - datasource._parent_system.get_parent_node()._api_helper.password)) - results = resp.json() - if 'links' in results: - for link in results['links']: - if link['rel'] == 'next': - # new_offset = link['href'].split('=')[-1] - asyncio.create_task(self.handle_http_batching(datasource, next_link=link['href'])) - - # print(results) - for obs in results['items']: - obs_obj = ObservationOMJSONInline.model_validate(obs) - msg_wrapper = MessageWrapper(datasource=datasource, - message=obs_obj) - self._message_list.add_message(msg_wrapper) - return resp.json() - - def get_message_handler(self) -> MessageHandler: - """ - Get the MessageHandler object from the DataSourceHandler - - :return: MessageHandler object - """ - return self._message_list - - def get_messages(self) -> list[MessageWrapper]: - """ - Get the list of MessageWrapper objects from the MessageHandler - - :return: List of MessageWrapper objects - """ - return self._message_list.get_messages() - - def post_observation(self, datasource: DataStream, observation: ObservationOMJSONInline): - """ - Posts an observation to the server - - :param datasource: DataSource object - :param observation: ObservationOMJSONInline object - - :return: - """ - api_helper = datasource.get_parent_system().get_parent_node().get_api_helper() - api_helper.post_resource(APIResourceTypes.OBSERVATION, parent_res_id=datasource._datastream.ds_id, - data=observation.model_dump(), req_headers={'Content-Type': 'application/json'}) - - -class MessageHandler: - """ - Manages a list of MessageWrapper objects, allowing for easy access and control of multiple messages. Works in - conjunction with the TimeManager to sort messages by their resultTime. - """ - _message_list: list[MessageWrapper] - - def __init__(self): - self._message_list = [] - - def add_message(self, message: MessageWrapper): - """ - Adds a MessageWrapper object to the MessageHandler - - :param message: - - :return: - """ - self._message_list.append(message) - # print(self._message_list) - - def get_messages(self) -> list[MessageWrapper]: - """ - Get the list of MessageWrapper objects - - :return: List of MessageWrapper objects - """ - return self._message_list - - def clear_messages(self): - """ - Empties the list of MessageWrapper objects - - :return: - """ - self._message_list.clear() - - def sort_messages(self) -> list[MessageWrapper]: - """ - Sorts the list of MessageWrapper objects by their resultTime - - :return: the sorted List of MessageWrapper objects - """ - # copy the list - sorted_list = self._message_list.copy() - sorted_list.sort(key=lambda x: x.resultTime) - return sorted_list - - -class MessageWrapper: - """ - Combines a DataSource and a Message into a single object for easier access - """ - - def __init__(self, datasource: DataStream, - message: ObservationOMJSONInline): - self._message = message - self._datasource = datasource - - def get_message(self) -> ObservationOMJSONInline: - """ - Get the observation data from the MessageWrapper - - :return: ObservationOMJSONInline that is easily serializable - """ - return self._message - - def get_message_as_dict(self) -> dict: - """ - Get the observation data from the MessageWrapper as a dictionary - - :return: dict of the observation result data - """ - return self._message.model_dump() - - def __repr__(self): - return f"{self._datasource}, {self._message}" diff --git a/src/oshconnect/oshconnectapi.py b/src/oshconnect/oshconnectapi.py index 0883d64..751c54f 100644 --- a/src/oshconnect/oshconnectapi.py +++ b/src/oshconnect/oshconnectapi.py @@ -9,7 +9,6 @@ from uuid import UUID from .csapi4py.default_api_helpers import APIHelper -from .datasource import MessageWrapper from .datastore import DataStore from .resource_datamodels import DatastreamResource from .streamableresource import Node, System, SessionManager, Datastream @@ -165,12 +164,12 @@ def set_timeperiod(self, start_time: str, end_time: str): tp = TimePeriod(start=start_time, end=end_time) self.timestream = TimeManagement(time_range=tp) - def get_message_list(self) -> list[MessageWrapper]: - """ - Get the list of messages that have been received by the OSHConnect instance. - :return: list of MessageWrapper objects - """ - return self._datasource_handler.get_messages() + # def get_message_list(self) -> list[MessageWrapper]: + # """ + # Get the list of messages that have been received by the OSHConnect instance. + # :return: list of MessageWrapper objects + # """ + # return self._datasource_handler.get_messages() def _insert_system(self, system: System, target_node: Node): """ diff --git a/src/oshconnect/resource_datamodels.py b/src/oshconnect/resource_datamodels.py index 33954fc..0b3fad8 100644 --- a/src/oshconnect/resource_datamodels.py +++ b/src/oshconnect/resource_datamodels.py @@ -7,12 +7,13 @@ from __future__ import annotations from typing import List + from .geometry import Geometry from .api_utils import Link from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny, model_validator from shapely import Point -from .schema_datamodels import DatastreamRecordSchema +from .schema_datamodels import DatastreamRecordSchema, CommandSchema from .timemanagement import DateTimeSchema, TimePeriod @@ -177,6 +178,7 @@ def handle_aliases(cls, values): class ObservationResource(BaseModel): model_config = ConfigDict(populate_by_name=True) + sampling_feature_id: str = Field(None, alias="samplingFeature@Id") procedure_link: Link = Field(None, alias="procedure@link") phenomenon_time: DateTimeSchema = Field(None, alias="phenomenonTime") @@ -188,6 +190,8 @@ class ObservationResource(BaseModel): class ControlStreamResource(BaseModel): model_config = ConfigDict(populate_by_name=True) + + cs_id: str = Field(None, alias="id") name: str = Field(...) description: str = Field(None) valid_time: TimePeriod = Field(..., alias="validTime") @@ -199,6 +203,6 @@ class ControlStreamResource(BaseModel): issue_time: DateTimeSchema = Field(None, alias="issueTime") execution_time: DateTimeSchema = Field(None, alias="executionTime") live: bool = Field(None) - asynchronous: bool = Field(..., alias="asynchronous") - record_schema: SerializeAsAny[DatastreamRecordSchema] = Field(None, alias="schema") + asynchronous: bool = Field(True, alias="async") + command_schema: SerializeAsAny[CommandSchema] = Field(None, alias="schema") links: List[Link] = Field(None) diff --git a/src/oshconnect/schema_datamodels.py b/src/oshconnect/schema_datamodels.py index be27e6a..b9407f5 100644 --- a/src/oshconnect/schema_datamodels.py +++ b/src/oshconnect/schema_datamodels.py @@ -34,38 +34,36 @@ class CommandJSON(BaseModel): params: Union[dict, list, int, float, str] = Field(None) -class ControlStreamJSONSchema(BaseModel): +class CommandSchema(BaseModel): """ - A class to represent the schema of a control stream + Base class representation for control streams' command schemas """ model_config = ConfigDict(populate_by_name=True) - id: str = Field(None) - name: str = Field(...) - description: str = Field(None) - deployment_link: str = Field(None, serialization_alias='deployment@link') - feature_of_interest_link: str = Field(None, serialization_alias='featureOfInterest@link') - sampling_feature_link: str = Field(None, alias='samplingFeature@link') - valid_time: list = Field(None, serialization_alias='validTime') - input_name: str = Field(None, serialization_alias='inputName') - links: list = Field(None) - control_stream_schema: SerializeAsAny[Union[SWEControlChannelSchema, JSONControlChannelSchema]] = Field(..., - serialization_alias='schema') + command_format: str = Field(..., alias='commandFormat') -class SWEControlChannelSchema(BaseModel): + +class SWEJSONCommandSchema(CommandSchema): """ - A class to represent the schema of a control channel + SWE+JSON command schema """ model_config = ConfigDict(populate_by_name=True) - command_format: str = Field("application/swe+json", serialization_alias='commandFormat') + + command_format: str = Field("application/swe+json", alias='commandFormat') encoding: SerializeAsAny[Encoding] = Field(...) record_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='recordSchema') -class JSONControlChannelSchema(BaseModel): +class JSONCommandSchema(CommandSchema): + """ + JSON command schema + """ model_config = ConfigDict(populate_by_name=True) - command_format: str = Field("application/cmd+json", serialization_alias='commandFormat') - params_schema: SerializeAsAny[AnyComponentSchema] = Field(..., serialization_alias='paramsSchema') + + command_format: str = Field("application/json", alias='commandFormat') + params_schema: SerializeAsAny[AnyComponentSchema] = Field(..., alias='parametersSchema') + result_schema: SerializeAsAny[AnyComponentSchema] = Field(None, alias='resultSchema') + feasibility_schema: SerializeAsAny[AnyComponentSchema] = Field(None, alias='feasibilityResultSchema') class DatastreamRecordSchema(BaseModel): @@ -73,7 +71,8 @@ class DatastreamRecordSchema(BaseModel): A class to represent the schema of a datastream """ model_config = ConfigDict(populate_by_name=True) - obs_format: str = Field(..., serialization_alias='obsFormat') + + obs_format: str = Field(..., alias='obsFormat') class SWEDatastreamRecordSchema(DatastreamRecordSchema): diff --git a/src/oshconnect/streamableresource.py b/src/oshconnect/streamableresource.py index 449e4a2..c636654 100644 --- a/src/oshconnect/streamableresource.py +++ b/src/oshconnect/streamableresource.py @@ -9,23 +9,25 @@ import asyncio import base64 +import datetime import json import logging import traceback import uuid -from abc import ABC, abstractmethod +from abc import ABC +from argparse import ArgumentError from dataclasses import dataclass, field from enum import Enum from multiprocessing import Process from multiprocessing.queues import Queue from typing import TypeVar, Generic, Union from uuid import UUID, uuid4 +from collections import deque -from aiohttp import ClientSession, BasicAuth -from aiohttp import WSMsgType, ClientWebSocketResponse - +from .csapi4py.constants import ContentTypes +from .schema_datamodels import JSONCommandSchema from .csapi4py.mqtt import MQTTCommClient -from .csapi4py.constants import APIResourceTypes +from .csapi4py.constants import APIResourceTypes, ObservationFormat from .csapi4py.default_api_helpers import APIHelper from .encoding import JSONEncoding from .resource_datamodels import ControlStreamResource @@ -50,12 +52,12 @@ def convert_auth_to_base64(username: str, password: str) -> str: return base64.b64encode(f"{username}:{password}".encode()).decode() -class OSHClientSession(ClientSession): +class OSHClientSession: verify_ssl = True _streamables: dict[str, 'StreamableResource'] = None def __init__(self, base_url, *args, verify_ssl=True, **kwargs): - super().__init__(base_url, *args, **kwargs) + # super().__init__(base_url, *args, **kwargs) self.verify_ssl = verify_ssl self._streamables = {} @@ -112,11 +114,11 @@ class Node: server_root: str = 'sensorhub' endpoints: Endpoints is_secure: bool - _basic_auth: bytes = None + _basic_auth: bytes _api_helper: APIHelper _systems: list[System] = field(default_factory=list) - _client_session: OSHClientSession = None - _mqtt_client: MQTTCommClient = None + _client_session: OSHClientSession + _mqtt_client: MQTTCommClient _mqtt_port: int = 1883 def __init__(self, protocol: str, address: str, port: int, @@ -153,8 +155,6 @@ def __init__(self, protocol: str, address: str, port: int, client_id_suffix=uuid.uuid4().hex, ) self._mqtt_client.connect() self._mqtt_client.start() - # self._mqtt_client = MQTTCommClient(url=self.address + self.server_root, port=self._mqtt_port, - # username=username, password=password, ) def get_id(self): return self._id @@ -166,7 +166,6 @@ def get_port(self): return self.port def get_api_endpoint(self): - # return f"http{'s' if self.is_secure else ''}://{self.address}:{self.port}/{self.endpoints.connected_systems}" return self._api_helper.get_api_root_url() def add_basicauth(self, username: str, password: str): @@ -178,8 +177,8 @@ def add_basicauth(self, username: str, password: str): def get_decoded_auth(self): return self._basic_auth.decode('utf-8') - def get_basicauth(self): - return BasicAuth(self._api_helper.username, self._api_helper.password) + # def get_basicauth(self): + # return BasicAuth(self._api_helper.username, self._api_helper.password) def get_mqtt_client(self) -> MQTTCommClient: return self._mqtt_client @@ -263,29 +262,32 @@ class StreamableModes(Enum): class StreamableResource(Generic[T], ABC): - _id: UUID = None - _resource_id: str = None - _canonical_link: str = None - _topic: str = None + _id: UUID + _resource_id: str + _canonical_link: str + _topic: str _status: str = Status.STOPPED.value - ws_url: str = None - _client_websocket: ClientWebSocketResponse = None + ws_url: str _message_handler = None - _parent_node: Node = None - _underlying_resource: T = None - _process: Process = None - _msg_reader_queue: asyncio.Queue[Union[str, bytes, float, int]] = None - _msg_writer_queue: asyncio.Queue[Union[str, bytes, float, int]] = None - _mqtt_client: MQTTCommClient = None - _parent_resource_id: str = None + _parent_node: Node + _underlying_resource: T + _process: Process + _msg_reader_queue: asyncio.Queue[Union[str, bytes, float, int]] + _msg_writer_queue: asyncio.Queue[Union[str, bytes, float, int]] + _inbound_deque: deque + _outbound_deque: deque + _mqtt_client: MQTTCommClient + _parent_resource_id: str _connection_mode: StreamableModes = StreamableModes.PUSH.value def __init__(self, node: Node, connection_mode: StreamableModes = StreamableModes.PUSH.value): self._id = uuid4() - self._message_handler = self._default_message_handler_fn self._parent_node = node self._parent_node.register_streamable(self) self._mqtt_client = self._parent_node.get_mqtt_client() + self._connection_mode = connection_mode + self._inbound_deque = deque() + self._outbound_deque = deque() def get_streamable_id(self) -> UUID: return self._id @@ -294,19 +296,21 @@ def get_streamable_id_str(self) -> str: return self._id.hex def initialize(self): - # self._process = Process(target=self.stream, args=()) resource_type = None if isinstance(self._underlying_resource, SystemResource): resource_type = APIResourceTypes.SYSTEM elif isinstance(self._underlying_resource, DatastreamResource): resource_type = APIResourceTypes.DATASTREAM + elif isinstance(self._underlying_resource, ControlStreamResource): + resource_type = APIResourceTypes.CONTROL_CHANNEL if resource_type is None: raise ValueError( "Underlying resource must be set to either SystemResource or DatastreamResource before initialization.") # This needs to be implemented separately for each subclass + res_id = getattr(self._underlying_resource, "ds_id", None) or getattr(self._underlying_resource, "cs_id", None) self.ws_url = self._parent_node.get_api_helper().construct_url(resource_type=resource_type, subresource_type=APIResourceTypes.OBSERVATION, - resource_id=self._underlying_resource.ds_id, + resource_id=res_id, subresource_id=None) self._msg_reader_queue = asyncio.Queue() self._msg_writer_queue = asyncio.Queue() @@ -320,12 +324,6 @@ def start(self): self._status = Status.STARTING.value self._status = Status.STARTED.value - # if asyncio.get_running_loop().is_running(): - # asyncio.create_task(self.stream()) - # else: - # loop = asyncio.get_event_loop() - # loop.create_task(self.stream()) - async def stream(self): session = self._parent_node.get_session() @@ -359,14 +357,18 @@ def get_mqtt_topic(self, subresource: APIResourceTypes | None = None): """ resource_type = None parent_res_type = None - # res_id = None parent_id = None if isinstance(self._underlying_resource, ControlStreamResource): parent_res_type = APIResourceTypes.CONTROL_CHANNEL - resource_type = APIResourceTypes.COMMAND parent_id = self._resource_id + match subresource: + case APIResourceTypes.COMMAND: + resource_type = APIResourceTypes.COMMAND + case APIResourceTypes.STATUS: + resource_type = APIResourceTypes.STATUS + elif isinstance(self._underlying_resource, DatastreamResource): parent_res_type = APIResourceTypes.DATASTREAM resource_type = APIResourceTypes.OBSERVATION @@ -413,18 +415,6 @@ def stop(self): self._process.terminate() self._status = "stopped" - def _default_message_handler_fn(self, ws, msg): - if msg.type == WSMsgType.TEXT: - print(f"Received text message: {msg.data}") - self._msg_reader_queue.put(msg.data) - elif msg.type == WSMsgType.BINARY: - print(f"Received binary message: {msg.data}") - self._msg_reader_queue.put(msg.data) - elif msg.type == WSMsgType.CLOSE: - print("WebSocket closed") - elif msg.type == WSMsgType.ERROR: - print(f"WebSocket error: {ws.exception()}") - def set_parent_node(self, node: Node): self._parent_node = node @@ -437,6 +427,9 @@ def set_parent_resource_id(self, res_id: str): def get_parent_resource_id(self) -> str: return self._parent_resource_id + def set_connection_mode(self, connection_mode: StreamableModes): + self._connection_mode = connection_mode + def poll(self): pass @@ -489,10 +482,10 @@ def _publish_mqtt(self, topic, payload): async def _write_to_mqtt(self): while self._status is Status.STARTED.value: try: - msg = self._msg_writer_queue.get_nowait() + msg = self._outbound_deque.popleft() print(f"Popped message: {msg}, attempting to publish...") self._publish_mqtt(self._topic, msg) - except asyncio.QueueEmpty: + except IndexError: await asyncio.sleep(0.05) except Exception as e: print(f"Error in Write To MQTT {self._id}: {e}") @@ -500,26 +493,50 @@ async def _write_to_mqtt(self): if self._status is Status.STOPPED.value: print("MQTT write task stopping as streamable resource is stopped.") - def set_connection_mode(self, mode: StreamableModes): - self._connection_mode = mode - - @abstractmethod - def publish(self, payload): + def publish(self, payload, topic: str = None): """ Publishes data to the MQTT topic associated with this streamable resource. + :param payload: Data to be published, subclass should determine specifically allowed types + :param topic: Specific implementation determines the topic from the provided string, if None the default topic is used """ - pass + self._publish_mqtt(self._topic, payload) + + def subscribe(self, topic=None, callback=None, qos=0): + """ + Subscribes to the MQTT topic associated with this streamable resource. + :param topic: Specific implementation determines the topic from the provided string, if None the default topic is used + :param callback: Optional callback function to handle incoming messages, if None the default handler is used + :param qos: Quality of Service level for the subscription, default is 0 + """ + t = None + + if topic is None: + t = self._topic + else: + raise ArgumentError("Invalid topic provided, must be None to use default topic.") + + if callback is None: + self._mqtt_client.subscribe(t, qos=qos, msg_callback=self._mqtt_sub_callback) + else: + self._mqtt_client.subscribe(t, qos=qos, msg_callback=callback) def _mqtt_sub_callback(self, client, userdata, msg): print(f"Received MQTT message on topic {msg.topic}: {msg.payload}") - self._msg_reader_queue.put_nowait(msg.payload) + # Appends to right of deque + self._inbound_deque.append(msg.payload) + + def get_inbound_deque(self): + return self._inbound_deque + + def get_outbound_deque(self): + return self._outbound_deque class System(StreamableResource[SystemResource]): name: str label: str datastreams: list[Datastream] - control_channels: list[ControlChannel] + control_channels: list[ControlStream] description: str urn: str _parent_node: Node @@ -544,11 +561,8 @@ def __init__(self, name: str, label: str, urn: str, parent_node: Node, **kwargs) self.description = kwargs['description'] self._underlying_resource = self.to_system_resource() - # self.underlying_resource = self._sys_resource def discover_datastreams(self) -> list[DatastreamResource]: - # res = self._parent_node.get_api_helper().retrieve_resource( - # APIResourceTypes.DATASTREAM, req_headers={}) res = self._parent_node.get_api_helper().get_resource(APIResourceTypes.SYSTEM, self._resource_id, APIResourceTypes.DATASTREAM) datastream_json = res.json()['items'] @@ -595,32 +609,32 @@ def set_system_resource(self, sys_resource: SystemResource): def get_system_resource(self) -> SystemResource: return self._underlying_resource - def add_insert_datastream(self, datastream: DataRecordSchema): + def add_insert_datastream(self, datarecord_schema: DataRecordSchema): """ Adds a datastream to the system while also inserting it into the system's parent node via HTTP POST. - :param datastream: DataRecordSchema to be used to define the datastream + :param datarecord_schema: DataRecordSchema to be used to define the datastream :return: """ - print(f'Adding datastream: {datastream.model_dump_json(exclude_none=True, by_alias=True)}') + print(f'Adding datastream: {datarecord_schema.model_dump_json(exclude_none=True, by_alias=True)}') # Make the request to add the datastream # if successful, add the datastream to the system - datastream_schema = SWEDatastreamRecordSchema(record_schema=datastream, obs_format='application/swe+json', + datastream_schema = SWEDatastreamRecordSchema(record_schema=datarecord_schema, + obs_format='application/swe+json', encoding=JSONEncoding()) - datastream_resource = DatastreamResource(ds_id="default", name=datastream.label, output_name=datastream.label, + datastream_resource = DatastreamResource(ds_id="default", name=datarecord_schema.label, + output_name=datarecord_schema.label, record_schema=datastream_schema, valid_time=TimePeriod(start=TimeInstant.now_as_time_instant(), end=TimeInstant(utc_time=TimeUtils.to_utc_time( "2026-12-31T00:00:00Z")))) api = self._parent_node.get_api_helper() - print( - f'Attempting to create datastream: {datastream_resource.model_dump_json(by_alias=True, exclude_none=True)}') print( f'Attempting to create datastream: {datastream_resource.model_dump(by_alias=True, exclude_none=True)}') res = api.create_resource(APIResourceTypes.DATASTREAM, datastream_resource.model_dump_json(by_alias=True, exclude_none=True), req_headers={ - 'Content-Type': 'application/json' + 'Content-Type': ContentTypes.JSON.value }, parent_res_id=self._resource_id) if res.ok: @@ -630,11 +644,56 @@ def add_insert_datastream(self, datastream: DataRecordSchema): else: raise Exception(f'Failed to create datastream: {datastream_resource.name}') - self.datastreams.append(datastream_resource) - new_ds = Datastream(datastream_id, self._parent_node, datastream_resource) + new_ds = Datastream(self._parent_node, datastream_resource) new_ds.set_parent_resource_id(self._underlying_resource.system_id) + self.datastreams.append(new_ds) return new_ds + def add_and_insert_control_stream(self, control_stream_record_schema: DataRecordSchema, input_name: str = None, + valid_time: TimePeriod = None) -> ControlStream: + """ + Accepts a DataRecordSchema and creates a JSON encoded schema structure ControlStreamResource, which is inserted + into the parent system via the host node. + :param control_stream_record_schema: DataRecordSchema to be used for the control stream + :param input_name: Name of the input, if None the label of the schema is converted to lower and stripped of whitespace + :return: ControlStream object added to the system + """ + input_name_checked = input_name if input_name is not None else control_stream_record_schema.label.lower().replace( + ' ', '') + + now = datetime.datetime.now() + future_time = now.replace(year=now.year + 1) + future_str = future_time.strftime("%Y-%m-%dT%H:%M:%SZ") + + valid_time_checked = valid_time if valid_time else TimePeriod(start=TimeInstant.now_as_time_instant(), + end=TimeInstant( + utc_time=TimeUtils.to_utc_time(future_str))) + + command_schema = JSONCommandSchema(command_format=ObservationFormat.SWE_JSON.value, + params_schema=control_stream_record_schema) + control_stream_resource = ControlStreamResource(name=control_stream_record_schema.label, + input_name=input_name_checked, + command_schema=command_schema, + validTime=valid_time_checked) + api = self._parent_node.get_api_helper() + res = api.create_resource(APIResourceTypes.CONTROL_CHANNEL, + control_stream_resource.model_dump_json(by_alias=True, exclude_none=True), + req_headers={ + 'Content-Type': 'application/json' + }, parent_res_id=self._resource_id) + + if res.ok: + control_channel_id = res.headers['Location'].split('/')[-1] + print(f'Control Stream Resource Location: {control_channel_id}') + control_stream_resource.cs_id = control_channel_id + else: + raise Exception(f'Failed to create control stream: {control_stream_resource.name}') + + new_cs = ControlStream(node=self._parent_node, controlstream_resource=control_stream_resource) + new_cs.set_parent_resource_id(self._underlying_resource.system_id) + self.control_channels.append(new_cs) + return new_cs + def insert_self(self): res = self._parent_node.get_api_helper().create_resource( APIResourceTypes.SYSTEM, self.to_system_resource().model_dump_json(by_alias=True, exclude_none=True), @@ -661,30 +720,21 @@ def retrieve_resource(self): self._underlying_resource = system_resource return None - def publish(self, payload): - self._publish_mqtt(self.get_mqtt_topic(), payload) - class Datastream(StreamableResource[DatastreamResource]): should_poll: bool - # _datastream_resource: DatastreamResource - _parent_node: Node - def __init__(self, id: str = None, parent_node: Node = None, datastream_resource: DatastreamResource = None): + def __init__(self, parent_node: Node = None, datastream_resource: DatastreamResource = None): super().__init__(node=parent_node) - self._parent_node = parent_node self._underlying_resource = datastream_resource self._resource_id = datastream_resource.ds_id def get_id(self): return self._underlying_resource.ds_id - def insert_observation(self, observation: Observation): - pass - @staticmethod def from_resource(ds_resource: DatastreamResource, parent_node: Node): - new_ds = Datastream(id=ds_resource.ds_id, parent_node=parent_node, datastream_resource=ds_resource) + new_ds = Datastream(parent_node=parent_node, datastream_resource=ds_resource) return new_ds def set_resource(self, resource: DatastreamResource): @@ -693,9 +743,6 @@ def set_resource(self, resource: DatastreamResource): def get_resource(self) -> DatastreamResource: return self._underlying_resource - def observation_template(self) -> Observation: - pass - def create_observation(self, obs_data: dict): obs = ObservationResource(result=obs_data, result_time=TimeInstant.now_as_time_instant()) # Validate against the schema @@ -736,9 +783,6 @@ def init_mqtt(self): super().init_mqtt() self._topic = self.get_mqtt_topic(subresource=APIResourceTypes.OBSERVATION) - def publish(self, payload): - self._publish_mqtt(self._topic, payload) - def _queue_push(self, msg): print(f'Pushing message to reader queue: {msg}') self._msg_writer_queue.put_nowait(msg) @@ -747,20 +791,26 @@ def _queue_push(self, msg): def _queue_pop(self): return self._msg_reader_queue.get_nowait() - # def _mqtt_sub_callback(self, client, userdata, msg): - # print(f"MQTT Message received on topic {msg.topic}: {msg.payload}") - # self._queue_push(msg.payload) - def insert(self, data: dict): # self._queue_push(data) encoded = json.dumps(data).encode('utf-8') self._publish_mqtt(self._topic, encoded) -class ControlChannel(StreamableResource[ControlStreamResource]): +class ControlStream(StreamableResource[ControlStreamResource]): + _status_topic: str + _inbound_status_deque: deque + _outbound_status_deque: deque + - def __init__(self, node: Node = None): + def __init__(self, node: Node = None, controlstream_resource: ControlStreamResource = None): super().__init__(node=node) + self._underlying_resource = controlstream_resource + self._inbound_status_deque = deque() + self._outbound_status_deque = deque() + self._resource_id = controlstream_resource.cs_id + # Always make sure this is set after the resource ids are set + self._status_topic = self.get_mqtt_status_topic() def add_underlying_resource(self, resource: ControlStreamResource): self._underlying_resource = resource @@ -769,20 +819,81 @@ def init_mqtt(self): super().init_mqtt() self._topic = self.get_mqtt_topic(subresource=APIResourceTypes.COMMAND) - def publish(self, payload): - self._publish_mqtt(self._topic, payload) + # def subscribe_to_status(self, topic: str): + # # TODO: This should probably be a flag to subscribe to status updates as the commands come in, trying to manage this manually would + # # prove tedious + # pass + # + # def publish_status(self, payload): + # pass + + def get_mqtt_status_topic(self): + return self.get_mqtt_topic(subresource=APIResourceTypes.STATUS) + + def start(self): + super().start() + if self._mqtt_client is not None: + if self._connection_mode is StreamableModes.PULL or self._connection_mode is StreamableModes.BIDIRECTIONAL: + # Subs to command topic by default + self._mqtt_client.subscribe(self._topic, msg_callback=self._mqtt_sub_callback) + else: + try: + loop = asyncio.get_event_loop() + loop.create_task(self._write_to_mqtt()) + except Exception as e: + print(traceback.format_exc()) + print(f"Error starting MQTT write task: {e}") + def get_inbound_deque(self): + return self._inbound_deque -class Observation: - _observation_resource: ObservationResource + def get_outbound_deque(self): + return self._outbound_deque - def __init__(self, observation_res: ObservationResource): - self._observation_resource = observation_res + def get_status_deque_inbound(self): + return self._inbound_status_deque - def to_resource(self) -> ObservationResource: - return self._observation_resource + def get_status_deque_outbound(self): + return self._outbound_status_deque + def publish_command(self, payload): + self.publish(payload, topic=APIResourceTypes.COMMAND.value) -class Output: - name: str - field_map: dict + def publish_status(self, payload): + self.publish(payload, topic=APIResourceTypes.STATUS.value) + + def publish(self, payload, topic: str = 'command'): + """ + Publishes data to the MQTT topic associated with this control stream resource. + :param payload: Data to be published, subclass should determine specifically allowed types + :param topic: Specific implementation determines the topic from the provided string + """ + + if topic == APIResourceTypes.COMMAND.value: + self._publish_mqtt(self._topic, payload) + elif topic == APIResourceTypes.STATUS.value: + self._publish_mqtt(self._status_topic, payload) + else: + raise ValueError(f"Unsupported topic type {topic} for ControlStream publish().") + + def subscribe(self, topic=None, callback=None, qos=0): + """ + Subscribes to the MQTT topic associated with this control stream resource. + :param topic: Specific implementation determines the topic from the provided string + :param callback: Optional callback function to handle incoming messages, if None the default handler is used + :param qos: Quality of Service level for the subscription, default is 0 + """ + + t = None + + if topic is None or topic == APIResourceTypes.COMMAND.value: + t = self._topic + elif topic == APIResourceTypes.STATUS.value: + t = self._status_topic + else: + raise ArgumentError(f"Invalid topic provided {topic}, must be None or one of 'command' or 'status'.") + + if callback is None: + self._mqtt_client.subscribe(t, qos=qos, msg_callback=self._mqtt_sub_callback) + else: + self._mqtt_client.subscribe(t, qos=qos, msg_callback=callback) diff --git a/uv.lock b/uv.lock index 790e496..1afb18e 100644 --- a/uv.lock +++ b/uv.lock @@ -436,7 +436,7 @@ wheels = [ [[package]] name = "oshconnect" -version = "0.3.0a4" +version = "0.3.0a5" source = { virtual = "." } dependencies = [ { name = "aiohttp" },