hydroserverpy 0.2.5__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hydroserverpy might be problematic. Click here for more details.

Files changed (69) hide show
  1. {hydroserverpy-0.2.5/src/hydroserverpy.egg-info → hydroserverpy-0.3.0}/PKG-INFO +6 -3
  2. hydroserverpy-0.3.0/README.md +43 -0
  3. {hydroserverpy-0.2.5 → hydroserverpy-0.3.0}/setup.cfg +6 -3
  4. hydroserverpy-0.3.0/src/hydroserverpy/__init__.py +10 -0
  5. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/__init__.py +9 -0
  6. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/base.py +133 -0
  7. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/data_loaders.py +92 -0
  8. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/data_sources.py +92 -0
  9. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/datastreams.py +188 -0
  10. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/observed_properties.py +93 -0
  11. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/processing_levels.py +93 -0
  12. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/result_qualifiers.py +93 -0
  13. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/sensors.py +93 -0
  14. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/things.py +240 -0
  15. hydroserverpy-0.3.0/src/hydroserverpy/core/endpoints/units.py +93 -0
  16. {hydroserverpy-0.2.5/src/hydroserverpy/components → hydroserverpy-0.3.0/src/hydroserverpy/core/schemas}/__init__.py +1 -2
  17. hydroserverpy-0.3.0/src/hydroserverpy/core/schemas/base.py +117 -0
  18. hydroserverpy-0.3.0/src/hydroserverpy/core/schemas/data_loaders.py +71 -0
  19. hydroserverpy-0.3.0/src/hydroserverpy/core/schemas/data_sources.py +206 -0
  20. hydroserverpy-0.3.0/src/hydroserverpy/core/schemas/datastreams.py +299 -0
  21. hydroserverpy-0.3.0/src/hydroserverpy/core/schemas/observed_properties.py +35 -0
  22. hydroserverpy-0.3.0/src/hydroserverpy/core/schemas/processing_levels.py +27 -0
  23. hydroserverpy-0.3.0/src/hydroserverpy/core/schemas/result_qualifiers.py +23 -0
  24. hydroserverpy-0.3.0/src/hydroserverpy/core/schemas/sensors.py +53 -0
  25. hydroserverpy-0.3.0/src/hydroserverpy/core/schemas/things.py +309 -0
  26. hydroserverpy-0.3.0/src/hydroserverpy/core/schemas/units.py +30 -0
  27. hydroserverpy-0.3.0/src/hydroserverpy/core/service.py +186 -0
  28. hydroserverpy-0.3.0/src/hydroserverpy/etl/__init__.py +0 -0
  29. hydroserverpy-0.2.5/src/hydroserverpy/etl.py → hydroserverpy-0.3.0/src/hydroserverpy/etl/service.py +32 -47
  30. hydroserverpy-0.3.0/src/hydroserverpy/quality/__init__.py +1 -0
  31. hydroserverpy-0.3.0/src/hydroserverpy/quality/service.py +391 -0
  32. {hydroserverpy-0.2.5 → hydroserverpy-0.3.0/src/hydroserverpy.egg-info}/PKG-INFO +6 -3
  33. hydroserverpy-0.3.0/src/hydroserverpy.egg-info/SOURCES.txt +41 -0
  34. {hydroserverpy-0.2.5 → hydroserverpy-0.3.0}/src/hydroserverpy.egg-info/requires.txt +4 -2
  35. hydroserverpy-0.2.5/README.md +0 -7
  36. hydroserverpy-0.2.5/src/hydroserverpy/__init__.py +0 -19
  37. hydroserverpy-0.2.5/src/hydroserverpy/components/data_loaders.py +0 -67
  38. hydroserverpy-0.2.5/src/hydroserverpy/components/data_sources.py +0 -98
  39. hydroserverpy-0.2.5/src/hydroserverpy/components/datastreams.py +0 -47
  40. hydroserverpy-0.2.5/src/hydroserverpy/components/observed_properties.py +0 -48
  41. hydroserverpy-0.2.5/src/hydroserverpy/components/processing_levels.py +0 -48
  42. hydroserverpy-0.2.5/src/hydroserverpy/components/result_qualifiers.py +0 -48
  43. hydroserverpy-0.2.5/src/hydroserverpy/components/sensors.py +0 -48
  44. hydroserverpy-0.2.5/src/hydroserverpy/components/things.py +0 -48
  45. hydroserverpy-0.2.5/src/hydroserverpy/components/units.py +0 -48
  46. hydroserverpy-0.2.5/src/hydroserverpy/components/users.py +0 -28
  47. hydroserverpy-0.2.5/src/hydroserverpy/main.py +0 -62
  48. hydroserverpy-0.2.5/src/hydroserverpy/models.py +0 -218
  49. hydroserverpy-0.2.5/src/hydroserverpy/schemas/data_loaders.py +0 -27
  50. hydroserverpy-0.2.5/src/hydroserverpy/schemas/data_sources.py +0 -58
  51. hydroserverpy-0.2.5/src/hydroserverpy/schemas/datastreams.py +0 -56
  52. hydroserverpy-0.2.5/src/hydroserverpy/schemas/observed_properties.py +0 -33
  53. hydroserverpy-0.2.5/src/hydroserverpy/schemas/processing_levels.py +0 -33
  54. hydroserverpy-0.2.5/src/hydroserverpy/schemas/result_qualifiers.py +0 -32
  55. hydroserverpy-0.2.5/src/hydroserverpy/schemas/sensors.py +0 -39
  56. hydroserverpy-0.2.5/src/hydroserverpy/schemas/things.py +0 -107
  57. hydroserverpy-0.2.5/src/hydroserverpy/schemas/units.py +0 -32
  58. hydroserverpy-0.2.5/src/hydroserverpy/schemas/users.py +0 -28
  59. hydroserverpy-0.2.5/src/hydroserverpy/service.py +0 -170
  60. hydroserverpy-0.2.5/src/hydroserverpy/utils.py +0 -37
  61. hydroserverpy-0.2.5/src/hydroserverpy.egg-info/SOURCES.txt +0 -40
  62. {hydroserverpy-0.2.5 → hydroserverpy-0.3.0}/LICENSE +0 -0
  63. {hydroserverpy-0.2.5 → hydroserverpy-0.3.0}/pyproject.toml +0 -0
  64. {hydroserverpy-0.2.5 → hydroserverpy-0.3.0}/setup.py +0 -0
  65. {hydroserverpy-0.2.5/src/hydroserverpy/schemas → hydroserverpy-0.3.0/src/hydroserverpy/core}/__init__.py +0 -0
  66. {hydroserverpy-0.2.5/src/hydroserverpy → hydroserverpy-0.3.0/src/hydroserverpy/etl}/exceptions.py +0 -0
  67. {hydroserverpy-0.2.5 → hydroserverpy-0.3.0}/src/hydroserverpy.egg-info/dependency_links.txt +0 -0
  68. {hydroserverpy-0.2.5 → hydroserverpy-0.3.0}/src/hydroserverpy.egg-info/top_level.txt +0 -0
  69. {hydroserverpy-0.2.5 → hydroserverpy-0.3.0}/src/hydroserverpy.egg-info/zip-safe +0 -0
@@ -1,14 +1,17 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hydroserverpy
3
- Version: 0.2.5
3
+ Version: 0.3.0
4
+ Requires-Python: <4,>=3.9
4
5
  License-File: LICENSE
5
6
  Requires-Dist: requests>=2
6
- Requires-Dist: pydantic<2.0.0,>=1.6
7
+ Requires-Dist: pydantic>=2.6
8
+ Requires-Dist: pandas>=2.2
9
+ Requires-Dist: numpy>=2.0
7
10
  Requires-Dist: pyyaml>=5
8
11
  Requires-Dist: simplejson>=3
9
12
  Requires-Dist: crontab>=1
10
13
  Requires-Dist: python-dateutil>=2.8.2
11
14
  Requires-Dist: croniter>=2.0.1
12
- Requires-Dist: frost-sta-client>=1.1.44
15
+ Requires-Dist: country-list>=1.1.0
13
16
  Provides-Extra: docs
14
17
  Requires-Dist: sphinx_autodoc_typehints; extra == "docs"
@@ -0,0 +1,43 @@
1
+ # HydroServer Python Client
2
+
3
+ The hydroserverpy Python package provides an interface for managing HydroServer data and metadata, loading observations, and performing data quality control. This guide will go over how to install the package and connect to a HydroServer instance. Full hydroserverpy documentation can be found [here](https://hydroserver2.github.io/hydroserverpy).
4
+
5
+ ## Installation
6
+
7
+ You can install the package via pip:
8
+
9
+ ```bash
10
+ pip install hydroserverpy
11
+ ```
12
+
13
+ ## Connecting to HydroServer
14
+
15
+ To connect to HydroServer, you need to initialize the client with the instance of HydroServer you're using and your user credentials if you want to access and modify your own data. If you don't provide authentication credentials you can read public data, but you will not be able to create or modify any data.
16
+
17
+ ### Example: Anonymous User
18
+
19
+ ```python
20
+ from hydroserverpy import HydroServer
21
+
22
+ # Initialize HydroServer connection.
23
+ hs_api = HydroServer(
24
+ host='https://playground.hydroserver.org'
25
+ )
26
+ ```
27
+
28
+ ### Example: Basic Authentication
29
+
30
+ ```python
31
+ from hydroserverpy import HydroServer
32
+
33
+ # Initialize HydroServer connection with credentials.
34
+ hs_api = HydroServer(
35
+ host='https://playground.hydroserver.org',
36
+ username='user@example.com',
37
+ password='******'
38
+ )
39
+ ```
40
+
41
+ ## Funding and Acknowledgements
42
+
43
+ Funding for this project was provided by the National Oceanic & Atmospheric Administration (NOAA), awarded to the Cooperative Institute for Research to Operations in Hydrology (CIROH) through the NOAA Cooperative Agreement with The University of Alabama (NA22NWS4320003).
@@ -1,21 +1,24 @@
1
1
  [metadata]
2
2
  name = hydroserverpy
3
- version = 0.2.5
3
+ version = 0.3.0
4
4
 
5
5
  [options]
6
6
  package_dir =
7
7
  =src
8
8
  packages = find:
9
9
  zip_safe = True
10
+ python_requires = >=3.9, <4
10
11
  install_requires =
11
12
  requests >= 2
12
- pydantic >= 1.6, < 2.0.0
13
+ pydantic >= 2.6
14
+ pandas >= 2.2
15
+ numpy >= 2.0
13
16
  pyyaml >= 5
14
17
  simplejson >= 3
15
18
  crontab >= 1
16
19
  python-dateutil >= 2.8.2
17
20
  croniter >= 2.0.1
18
- frost-sta-client >= 1.1.44
21
+ country-list >= 1.1.0
19
22
 
20
23
  [options.extras_require]
21
24
  docs =
@@ -0,0 +1,10 @@
1
+ from .core.service import HydroServer
2
+ from .quality.service import HydroServerQualityControl
3
+ from .etl.service import HydroServerETL
4
+
5
+
6
+ __all__ = [
7
+ "HydroServer",
8
+ "HydroServerQualityControl",
9
+ "HydroServerETL",
10
+ ]
@@ -0,0 +1,9 @@
1
+ from .data_loaders import DataLoaderEndpoint
2
+ from .data_sources import DataSourceEndpoint
3
+ from .datastreams import DatastreamEndpoint
4
+ from .observed_properties import ObservedPropertyEndpoint
5
+ from .processing_levels import ProcessingLevelEndpoint
6
+ from .result_qualifiers import ResultQualifierEndpoint
7
+ from .sensors import SensorEndpoint
8
+ from .things import ThingEndpoint
9
+ from .units import UnitEndpoint
@@ -0,0 +1,133 @@
1
+ import json
2
+ from uuid import UUID
3
+ from typing import TYPE_CHECKING, Type, Union, List, TypeVar, Optional
4
+
5
+ if TYPE_CHECKING:
6
+ from hydroserverpy import HydroServer
7
+ from hydroserverpy.core.schemas.base import HydroServerCoreModel
8
+
9
+ HydroServerModelType = TypeVar('HydroServerModelType', bound=HydroServerCoreModel)
10
+
11
+
12
+ def expand_docstring(model: Optional[Type['HydroServerCoreModel']] = None, include_uid: bool = False):
13
+ def decorator(func):
14
+ docstring = func.__doc__
15
+ if model is not None or include_uid is True:
16
+ docstring += '\n'
17
+ if include_uid is True:
18
+ docstring += f':param uid: The entity ID.\n'
19
+ docstring += f':type uid: Union[UUID, str]\n'
20
+ if model is not None:
21
+ for field_name, field in model.model_fields.items():
22
+ docstring += f':param {field_name}: {field.description}\n'
23
+ docstring += f':type {field_name}: {getattr(field.annotation, "__name__", field.annotation)}\n'
24
+ func.__doc__ = docstring
25
+ return func
26
+ return decorator
27
+
28
+
29
+ class HydroServerEndpoint:
30
+ """
31
+ A base class for interacting with specific API endpoints within a HydroServer service.
32
+
33
+ :ivar _model: The model class associated with this endpoint.
34
+ :ivar _api_route: The base route of the API.
35
+ :ivar _endpoint_route: The specific route of the endpoint.
36
+ """
37
+
38
+ _model: Type['HydroServerCoreModel']
39
+ _api_route: str
40
+ _endpoint_route: str
41
+
42
+ def __init__(self, service: 'HydroServer') -> None:
43
+ """
44
+ Initialize the HydroServerEndpoint.
45
+
46
+ :param service: The HydroServer service instance to use for requests.
47
+ :type service: HydroServer
48
+ """
49
+
50
+ self._service = service
51
+
52
+ def _get(self, uid: Optional[Union[UUID, str]] = None, params: dict = None) -> Union[
53
+ List['HydroServerModelType'], 'HydroServerModelType'
54
+ ]:
55
+ """
56
+ Fetch an entity collection or single entity from a HydroServer endpoint.
57
+
58
+ :param uid: The unique identifier of the entity to retrieve.
59
+ :type uid: Optional[Union[UUID, str]]
60
+ :returns: A model instance representing the entity.
61
+ :rtype: HydroServerCoreModel
62
+ """
63
+
64
+ if params is None:
65
+ params = {}
66
+
67
+ path = f'{self._api_route}/data/{self._endpoint_route}{"/" + str(uid) if uid else ""}'
68
+ response = getattr(self._service, '_request')('get', path, params=params)
69
+
70
+ if uid:
71
+ entity = json.loads(response.content)
72
+ result = self._model(_endpoint=self, _uid=UUID(str(entity.pop('id'))), **entity)
73
+ else:
74
+ result = [
75
+ self._model(_endpoint=self, _uid=UUID(str(entity.pop('id'))), **entity)
76
+ for entity in json.loads(response.content)
77
+ ]
78
+
79
+ return result
80
+
81
+ def _post(self, **kwargs) -> 'HydroServerModelType':
82
+ """
83
+ Create a new entity using the endpoint.
84
+
85
+ :param kwargs: The attributes to set on the new entity.
86
+ :returns: A model instance representing the newly created entity.
87
+ :rtype: HydroServerModelType
88
+ """
89
+
90
+ response = getattr(self._service, '_request')(
91
+ 'post', f'{self._api_route}/data/{self._endpoint_route}',
92
+ headers={'Content-type': 'application/json'},
93
+ data=self._model(_endpoint=self, **kwargs).json(exclude_unset=True, by_alias=True),
94
+ )
95
+ entity = json.loads(response.content)
96
+
97
+ return self._model(_endpoint=self, _uid=UUID(str(entity.pop('id'))), **entity)
98
+
99
+ def _patch(self, uid: Union[UUID, str], **kwargs) -> 'HydroServerModelType':
100
+ """
101
+ Update an existing entity in the endpoint.
102
+
103
+ :param uid: The unique identifier of the entity to update.
104
+ :type uid: Union[UUID, str]
105
+ :param kwargs: The attributes to update on the entity.
106
+ :returns: A model instance representing the updated entity.
107
+ :rtype: HydroServerModelType
108
+ """
109
+
110
+ response = getattr(self._service, '_request')(
111
+ 'patch', f'{self._api_route}/data/{self._endpoint_route}/{str(uid)}',
112
+ headers={'Content-type': 'application/json'},
113
+ data=json.dumps({
114
+ self._model.model_fields[key].serialization_alias: value
115
+ for key, value in kwargs.items()
116
+ }, default=str)
117
+ )
118
+ entity = json.loads(response.content)
119
+
120
+ return self._model(_endpoint=self, _uid=UUID(str(entity.pop('id'))), **entity)
121
+
122
+ def _delete(self, uid: Union[UUID, str]) -> None:
123
+ """
124
+ Delete an entity from the endpoint by its unique identifier.
125
+
126
+ :param uid: The unique identifier of the entity to delete.
127
+ :type uid: Union[UUID, str]
128
+ :returns: None
129
+ """
130
+
131
+ getattr(self._service, '_request')(
132
+ 'delete', f'{self._api_route}/data/{self._endpoint_route}/{str(uid)}',
133
+ )
@@ -0,0 +1,92 @@
1
+ import json
2
+ from typing import Union, List, TYPE_CHECKING
3
+ from uuid import UUID
4
+ from hydroserverpy.core.endpoints.base import HydroServerEndpoint, expand_docstring
5
+ from hydroserverpy.core.endpoints.data_sources import DataSourceEndpoint
6
+ from hydroserverpy.core.schemas import DataLoader, DataSource
7
+
8
+ if TYPE_CHECKING:
9
+ from hydroserverpy.core.service import HydroServer
10
+
11
+
12
+ class DataLoaderEndpoint(HydroServerEndpoint):
13
+ """
14
+ An endpoint for interacting with DataLoader entities in the HydroServer service.
15
+
16
+ :ivar _model: The model class associated with this endpoint, set to `DataLoader`.
17
+ :ivar _api_route: The base route of the API, derived from the service.
18
+ :ivar _endpoint_route: The specific route of the endpoint, set to `'data-loaders'`.
19
+ """
20
+
21
+ def __init__(self, service: 'HydroServer') -> None:
22
+ """
23
+ Initialize the DataLoaderEndpoint.
24
+
25
+ :param service: The HydroServer service instance to use for requests.
26
+ :type service: HydroServer
27
+ """
28
+
29
+ super().__init__(service)
30
+ self._model = DataLoader
31
+ self._api_route = self._service.api_route
32
+ self._endpoint_route = 'data-loaders'
33
+
34
+ def list(self) -> List[DataLoader]:
35
+ """
36
+ Retrieve a collection of data loaders owned by the logged-in user.
37
+ """
38
+
39
+ return super()._get()
40
+
41
+ @expand_docstring(include_uid=True)
42
+ def get(self, uid: Union[UUID, str]) -> DataLoader:
43
+ """
44
+ Retrieve a data loader owned by the logged-in user.
45
+ """
46
+
47
+ return super()._get(uid)
48
+
49
+ @expand_docstring(model=DataLoader)
50
+ def create(self, **kwargs) -> DataLoader:
51
+ """
52
+ Create a new data loader in HydroServer.
53
+ """
54
+
55
+ return super()._post(**kwargs)
56
+
57
+ @expand_docstring(model=DataLoader, include_uid=True)
58
+ def update(self, uid: Union[UUID, str], **kwargs) -> DataLoader:
59
+ """
60
+ Update an existing data loader in HydroServer.
61
+ """
62
+
63
+ return super()._patch(uid=uid, **kwargs)
64
+
65
+ @expand_docstring(include_uid=True)
66
+ def delete(self, uid: Union[UUID, str]) -> None:
67
+ """
68
+ Delete an existing data loader in HydroServer.
69
+ """
70
+
71
+ super()._delete(uid=uid)
72
+
73
+ def list_data_sources(self, uid: Union[UUID, str]) -> List[DataSource]:
74
+ """
75
+ Retrieve a list of data source entities associated with a specific data loader.
76
+
77
+ :param uid: The unique identifier of the data loader.
78
+ :type uid: Union[UUID, str]
79
+ :returns: A list of data sour instances associated with the data loader.
80
+ :rtype: List[DataSource]
81
+ """
82
+
83
+ response = getattr(self._service, '_request')(
84
+ 'get', f'{self._api_route}/data/{self._endpoint_route}/{str(uid)}/data-sources'
85
+ )
86
+
87
+ endpoint = DataSourceEndpoint(self._service)
88
+
89
+ return [
90
+ DataSource(_endpoint=endpoint, _uid=UUID(str(entity.pop('id'))), **entity)
91
+ for entity in json.loads(response.content)
92
+ ]
@@ -0,0 +1,92 @@
1
+ import json
2
+ from typing import Union, List, TYPE_CHECKING
3
+ from uuid import UUID
4
+ from hydroserverpy.core.endpoints.base import HydroServerEndpoint, expand_docstring
5
+ from hydroserverpy.core.endpoints.datastreams import DatastreamEndpoint
6
+ from hydroserverpy.core.schemas import DataSource, Datastream
7
+
8
+ if TYPE_CHECKING:
9
+ from hydroserverpy.core.service import HydroServer
10
+
11
+
12
+ class DataSourceEndpoint(HydroServerEndpoint):
13
+ """
14
+ An endpoint for interacting with data source entities in the HydroServer service.
15
+
16
+ :ivar _model: The model class associated with this endpoint, set to `DataSource`.
17
+ :ivar _api_route: The base route of the API, derived from the service.
18
+ :ivar _endpoint_route: The specific route of the endpoint, set to `'data-sources'`.
19
+ """
20
+
21
+ def __init__(self, service: 'HydroServer') -> None:
22
+ """
23
+ Initialize the DataSourceEndpoint.
24
+
25
+ :param service: The HydroServer service instance to use for requests.
26
+ :type service: HydroServer
27
+ """
28
+
29
+ super().__init__(service)
30
+ self._model = DataSource
31
+ self._api_route = self._service.api_route
32
+ self._endpoint_route = 'data-sources'
33
+
34
+ def list(self) -> List[DataSource]:
35
+ """
36
+ Retrieve a collection of data sources owned by the logged-in user.
37
+ """
38
+
39
+ return super()._get()
40
+
41
+ @expand_docstring(include_uid=True)
42
+ def get(self, uid: Union[UUID, str]) -> DataSource:
43
+ """
44
+ Retrieve a data source owned by the logged-in user.
45
+ """
46
+
47
+ return super()._get(uid)
48
+
49
+ @expand_docstring(model=DataSource)
50
+ def create(self, **kwargs) -> DataSource:
51
+ """
52
+ Create a new data source in HydroServer.
53
+ """
54
+
55
+ return super()._post(**kwargs)
56
+
57
+ @expand_docstring(model=DataSource, include_uid=True)
58
+ def update(self, uid: Union[UUID, str], **kwargs) -> DataSource:
59
+ """
60
+ Update an existing data source in HydroServer.
61
+ """
62
+
63
+ return super()._patch(uid=uid, **kwargs)
64
+
65
+ @expand_docstring(include_uid=True)
66
+ def delete(self, uid: Union[UUID, str]) -> None:
67
+ """
68
+ Delete an existing data source in HydroServer.
69
+ """
70
+
71
+ super()._delete(uid=uid)
72
+
73
+ def list_datastreams(self, uid: Union[UUID, str]) -> List[Datastream]:
74
+ """
75
+ Retrieve a list of datastream entities associated with a specific data source.
76
+
77
+ :param uid: The unique identifier of the data source.
78
+ :type uid: Union[UUID, str]
79
+ :returns: A list of datastream instances associated with the data source.
80
+ :rtype: List[Datastream]
81
+ """
82
+
83
+ response = getattr(self._service, '_request')(
84
+ 'get', f'{self._api_route}/data/{self._endpoint_route}/{str(uid)}/datastreams'
85
+ )
86
+
87
+ endpoint = DatastreamEndpoint(self._service)
88
+
89
+ return [
90
+ Datastream(_endpoint=endpoint, _uid=UUID(str(entity.pop('id'))), **entity)
91
+ for entity in json.loads(response.content)
92
+ ]
@@ -0,0 +1,188 @@
1
+ import json
2
+ import pandas as pd
3
+ from typing import List, Union, TYPE_CHECKING
4
+ from uuid import UUID
5
+ from datetime import datetime
6
+ from hydroserverpy.core.endpoints.base import HydroServerEndpoint, expand_docstring
7
+ from hydroserverpy.core.schemas import Datastream
8
+
9
+ if TYPE_CHECKING:
10
+ from hydroserverpy.core.service import HydroServer
11
+
12
+
13
+ class DatastreamEndpoint(HydroServerEndpoint):
14
+ """
15
+ An endpoint for interacting with datastream entities in the HydroServer service.
16
+
17
+ :ivar _model: The model class associated with this endpoint, set to `Datastream`.
18
+ :ivar _api_route: The base route of the API, derived from the service.
19
+ :ivar _endpoint_route: The specific route of the endpoint, set to `'datastreams'`.
20
+ """
21
+
22
+ def __init__(self, service) -> None:
23
+ """
24
+ Initialize the DatastreamEndpoint.
25
+
26
+ :param service: The HydroServer service instance to use for requests.
27
+ :type service: HydroServer
28
+ """
29
+
30
+ super().__init__(service)
31
+ self._model = Datastream
32
+ self._api_route = self._service.api_route
33
+ self._endpoint_route = 'datastreams'
34
+
35
+ def list(self, owned_only: bool = False, primary_owned_only: bool = False) -> List[Datastream]:
36
+ """
37
+ Retrieve a collection of datastreams owned by the logged-in user.
38
+
39
+ :param owned_only: Only list datastreams owned by the logged-in user.
40
+ :param primary_owned_only: Only list datastreams primary owned by the logged-in user.
41
+ """
42
+
43
+ return super()._get(params={
44
+ 'owned_only': owned_only,
45
+ 'primary_owned_only': primary_owned_only,
46
+ })
47
+
48
+ @expand_docstring(include_uid=True)
49
+ def get(self, uid: Union[UUID, str]) -> Datastream:
50
+ """
51
+ Retrieve a datastream owned by the logged-in user.
52
+ """
53
+
54
+ return super()._get(uid)
55
+
56
+ @expand_docstring(model=Datastream)
57
+ def create(self, **kwargs) -> Datastream:
58
+ """
59
+ Create a new datastream in HydroServer.
60
+ """
61
+
62
+ return super()._post(**kwargs)
63
+
64
+ @expand_docstring(model=Datastream, include_uid=True)
65
+ def update(self, uid: Union[UUID, str], **kwargs) -> Datastream:
66
+ """
67
+ Update an existing datastream in HydroServer.
68
+ """
69
+
70
+ return super()._patch(uid=uid, **kwargs)
71
+
72
+ @expand_docstring(include_uid=True)
73
+ def delete(self, uid: Union[UUID, str]) -> None:
74
+ """
75
+ Delete an existing datastream in HydroServer.
76
+ """
77
+
78
+ super()._delete(uid=uid)
79
+
80
+ def get_observations(
81
+ self,
82
+ uid: Union[UUID, str],
83
+ start_time: datetime = None,
84
+ end_time: datetime = None,
85
+ page: int = 1,
86
+ page_size: int = 100000,
87
+ include_quality: bool = False,
88
+ fetch_all: bool = False
89
+ ) -> pd.DataFrame:
90
+ """
91
+ Retrieve observations from a specific datastream.
92
+
93
+ :param uid: The unique identifier of the datastream.
94
+ :type uid: Union[UUID, str]
95
+ :param start_time: The start time for filtering observations.
96
+ :type start_time: datetime, optional
97
+ :param end_time: The end time for filtering observations.
98
+ :type end_time: datetime, optional
99
+ :param page: The page number to retrieve (used for pagination).
100
+ :type page: int, optional
101
+ :param page_size: The number of observations per page.
102
+ :type page_size: int, optional
103
+ :param include_quality: Whether to include quality information with each observation.
104
+ :type include_quality: bool, optional
105
+ :param fetch_all: Whether to fetch all observations (ignoring pagination).
106
+ :type fetch_all: bool, optional
107
+ :returns: A DataFrame containing the retrieved observations.
108
+ :rtype: pd.DataFrame
109
+ """
110
+
111
+ filters = []
112
+ if start_time:
113
+ filters.append(f'phenomenonTime ge {start_time.strftime("%Y-%m-%dT%H:%M:%S%z")}')
114
+ if end_time:
115
+ filters.append(f'phenomenonTime le {end_time.strftime("%Y-%m-%dT%H:%M:%S%z")}')
116
+
117
+ if fetch_all:
118
+ page = 1
119
+
120
+ observations = []
121
+
122
+ while True:
123
+ response = getattr(self._service, '_request')(
124
+ 'get', f'{self._api_route}/sensorthings/v1.1/Datastreams(\'{str(uid)}\')/Observations',
125
+ params={
126
+ '$resultFormat': 'dataArray',
127
+ '$select': f'phenomenonTime,result{",resultQuality" if include_quality else ""}',
128
+ '$count': True,
129
+ '$top': page_size,
130
+ '$skip': (page - 1) * page_size,
131
+ '$filter': ' and '.join(filters) if filters else None
132
+ }
133
+ )
134
+ response_content = json.loads(response.content)
135
+ data_array = response_content['value'][0]['dataArray'] if response_content['value'] else []
136
+ observations.extend([[
137
+ obs[0], obs[1],
138
+ obs[2]['qualityCode'] if obs[2]['qualityCode'] else None,
139
+ obs[2]['resultQualifiers'] if obs[2]['resultQualifiers'] else None
140
+ ] if include_quality else [obs[0], obs[1]] for obs in data_array])
141
+ if not fetch_all or len(data_array) < page_size:
142
+ break
143
+ page += 1
144
+
145
+ columns = ['timestamp', 'value']
146
+ if include_quality:
147
+ columns.extend(['quality_code', 'result_quality'])
148
+
149
+ data_frame = pd.DataFrame(observations, columns=columns)
150
+ data_frame['timestamp'] = pd.to_datetime(data_frame['timestamp'])
151
+
152
+ return data_frame
153
+
154
+ def load_observations(
155
+ self,
156
+ uid: Union[UUID, str],
157
+ observations: pd.DataFrame,
158
+ ) -> None:
159
+ """
160
+ Load observations to a specific datastream.
161
+
162
+ :param uid: The unique identifier of the datastream.
163
+ :type uid: Union[UUID, str]
164
+ :param observations: A DataFrame containing the observations to upload.
165
+ :type observations: pd.DataFrame
166
+ :returns: None
167
+ """
168
+
169
+ data_array = [
170
+ [
171
+ row['timestamp'].strftime('%Y-%m-%dT%H:%M:%S%z'),
172
+ row['value'],
173
+ {
174
+ 'qualityCode': row.get('quality_code', None),
175
+ 'resultQualifiers': row.get('result_qualifiers', []),
176
+ } if 'quality_code' in row or 'result_qualifiers' in row else {}
177
+ ] for _, row in observations.iterrows()
178
+ ]
179
+
180
+ getattr(self._service, '_request')(
181
+ 'post', f'{self._api_route}/sensorthings/v1.1/CreateObservations',
182
+ headers={'Content-type': 'application/json'},
183
+ data=json.dumps([{
184
+ 'Datastream': {'@iot.id': str(uid)},
185
+ 'components': ['phenomenonTime', 'result', 'resultQuality'],
186
+ 'dataArray': data_array
187
+ }]),
188
+ )