hydroserverpy 1.2.0__tar.gz → 1.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.
- {hydroserverpy-1.2.0/src/hydroserverpy.egg-info → hydroserverpy-1.3.0}/PKG-INFO +1 -1
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/README.md +1 -1
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/setup.cfg +1 -1
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/__init__.py +1 -1
- hydroserverpy-1.2.0/src/hydroserverpy/api/main.py → hydroserverpy-1.3.0/src/hydroserverpy/api/client.py +52 -22
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/models/__init__.py +1 -2
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/base.py +207 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/etl/data_archive.py +77 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/etl/data_source.py +111 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/etl/orchestration_system.py +63 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/iam/apikey.py +96 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/iam/collaborator.py +70 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/iam/role.py +38 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/models/iam/workspace.py +58 -86
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/sta/datastream.py +250 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/sta/observation.py +101 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/sta/observed_property.py +37 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/sta/processing_level.py +35 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/sta/result_qualifier.py +34 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/sta/sensor.py +44 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/sta/thing.py +113 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/models/sta/unit.py +36 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/services/__init__.py +1 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/services/base.py +118 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/services/etl/data_archive.py +42 -72
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/services/etl/data_source.py +42 -72
- hydroserverpy-1.3.0/src/hydroserverpy/api/services/etl/orchestration_system.py +66 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/services/iam/role.py +38 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/services/iam/workspace.py +232 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/services/sta/datastream.py +293 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/services/sta/observed_property.py +82 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/services/sta/processing_level.py +72 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/services/sta/result_qualifier.py +64 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/services/sta/sensor.py +34 -48
- hydroserverpy-1.3.0/src/hydroserverpy/api/services/sta/thing.py +195 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/services/sta/unit.py +78 -0
- hydroserverpy-1.3.0/src/hydroserverpy/api/utils.py +22 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/extractors/base.py +3 -5
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/loaders/hydroserver_loader.py +1 -0
- hydroserverpy-1.3.0/src/hydroserverpy/etl/timestamp_parser.py +109 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/transformers/base.py +6 -10
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl_csv/hydroserver_etl_csv.py +18 -24
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0/src/hydroserverpy.egg-info}/PKG-INFO +1 -1
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy.egg-info/SOURCES.txt +4 -2
- hydroserverpy-1.2.0/src/hydroserverpy/api/http.py +0 -22
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/base.py +0 -74
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/etl/data_archive.py +0 -105
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/etl/data_source.py +0 -153
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/etl/orchestration_system.py +0 -78
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/iam/apikey.py +0 -77
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/iam/collaborator.py +0 -34
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/iam/role.py +0 -10
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/sta/datastream.py +0 -342
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/sta/observed_property.py +0 -72
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/sta/processing_level.py +0 -50
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/sta/result_qualifier.py +0 -49
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/sta/sensor.py +0 -105
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/sta/thing.py +0 -217
- hydroserverpy-1.2.0/src/hydroserverpy/api/models/sta/unit.py +0 -49
- hydroserverpy-1.2.0/src/hydroserverpy/api/services/base.py +0 -102
- hydroserverpy-1.2.0/src/hydroserverpy/api/services/etl/orchestration_system.py +0 -74
- hydroserverpy-1.2.0/src/hydroserverpy/api/services/iam/workspace.py +0 -235
- hydroserverpy-1.2.0/src/hydroserverpy/api/services/sta/datastream.py +0 -354
- hydroserverpy-1.2.0/src/hydroserverpy/api/services/sta/observed_property.py +0 -100
- hydroserverpy-1.2.0/src/hydroserverpy/api/services/sta/processing_level.py +0 -78
- hydroserverpy-1.2.0/src/hydroserverpy/api/services/sta/result_qualifier.py +0 -74
- hydroserverpy-1.2.0/src/hydroserverpy/api/services/sta/thing.py +0 -188
- hydroserverpy-1.2.0/src/hydroserverpy/api/services/sta/unit.py +0 -82
- hydroserverpy-1.2.0/src/hydroserverpy/etl/timestamp_parser.py +0 -75
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/LICENSE +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/pyproject.toml +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/setup.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/models/etl/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/models/etl/orchestration_configuration.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/models/iam/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/models/iam/account.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/models/sta/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/services/etl/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/services/iam/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/api/services/sta/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/extractors/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/extractors/ftp_extractor.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/extractors/http_extractor.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/extractors/local_file_extractor.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/hydroserver_etl.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/loaders/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/loaders/base.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/transformers/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/transformers/csv_transformer.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/transformers/json_transformer.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl/types.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl_csv/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/etl_csv/exceptions.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/quality/__init__.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy/quality/service.py +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy.egg-info/dependency_links.txt +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy.egg-info/requires.txt +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy.egg-info/top_level.txt +0 -0
- {hydroserverpy-1.2.0 → hydroserverpy-1.3.0}/src/hydroserverpy.egg-info/zip-safe +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import requests
|
|
2
|
+
import json
|
|
2
3
|
from typing import Optional, Tuple
|
|
3
|
-
from hydroserverpy.api.http import raise_for_hs_status
|
|
4
4
|
from hydroserverpy.api.services import (
|
|
5
5
|
WorkspaceService,
|
|
6
|
+
RoleService,
|
|
6
7
|
ThingService,
|
|
7
8
|
ObservedPropertyService,
|
|
8
9
|
UnitService,
|
|
@@ -20,11 +21,14 @@ class HydroServer:
|
|
|
20
21
|
def __init__(
|
|
21
22
|
self,
|
|
22
23
|
host: str,
|
|
24
|
+
auth_route: str = "/api/auth",
|
|
25
|
+
base_route: str = "/api/data",
|
|
23
26
|
email: Optional[str] = None,
|
|
24
27
|
password: Optional[str] = None,
|
|
25
28
|
apikey: Optional[str] = None,
|
|
26
29
|
):
|
|
27
30
|
self.host = host.strip("/")
|
|
31
|
+
self.base_route = base_route
|
|
28
32
|
self.auth = (
|
|
29
33
|
(
|
|
30
34
|
email or "__key__",
|
|
@@ -34,7 +38,7 @@ class HydroServer:
|
|
|
34
38
|
else None
|
|
35
39
|
)
|
|
36
40
|
|
|
37
|
-
self._auth_url = f"{self.host}/
|
|
41
|
+
self._auth_url = f"{self.host}{auth_route}/app/session"
|
|
38
42
|
|
|
39
43
|
self._session = None
|
|
40
44
|
self._timeout = 60
|
|
@@ -52,6 +56,30 @@ class HydroServer:
|
|
|
52
56
|
|
|
53
57
|
self._session.delete(self._auth_url, timeout=self._timeout)
|
|
54
58
|
|
|
59
|
+
def request(self, method, path, *args, **kwargs) -> requests.Response:
|
|
60
|
+
"""Sends a request to HydroServer's API."""
|
|
61
|
+
|
|
62
|
+
for attempt in range(2):
|
|
63
|
+
try:
|
|
64
|
+
response = getattr(self._session, method)(
|
|
65
|
+
f"{self.host}/{path.strip('/')}",
|
|
66
|
+
timeout=self._timeout,
|
|
67
|
+
*args,
|
|
68
|
+
**kwargs,
|
|
69
|
+
)
|
|
70
|
+
self._raise_for_hs_status(response)
|
|
71
|
+
except (
|
|
72
|
+
requests.exceptions.HTTPError,
|
|
73
|
+
requests.exceptions.ConnectionError,
|
|
74
|
+
) as e:
|
|
75
|
+
if attempt == 0:
|
|
76
|
+
self._init_session()
|
|
77
|
+
continue
|
|
78
|
+
else:
|
|
79
|
+
raise e
|
|
80
|
+
|
|
81
|
+
return response
|
|
82
|
+
|
|
55
83
|
def _init_session(self, auth: Optional[Tuple[str, str]] = None) -> None:
|
|
56
84
|
if self._session is not None:
|
|
57
85
|
self.logout()
|
|
@@ -82,29 +110,25 @@ class HydroServer:
|
|
|
82
110
|
|
|
83
111
|
return session_token
|
|
84
112
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _raise_for_hs_status(response):
|
|
115
|
+
try:
|
|
116
|
+
response.raise_for_status()
|
|
117
|
+
except requests.HTTPError as e:
|
|
89
118
|
try:
|
|
90
|
-
|
|
91
|
-
f"{
|
|
92
|
-
|
|
93
|
-
*args,
|
|
94
|
-
**kwargs,
|
|
119
|
+
http_error_msg = (
|
|
120
|
+
f"{response.status_code} Client Error: "
|
|
121
|
+
f"{str(json.loads(response.content).get('detail'))}"
|
|
95
122
|
)
|
|
96
|
-
raise_for_hs_status(response)
|
|
97
123
|
except (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return response
|
|
124
|
+
ValueError,
|
|
125
|
+
TypeError,
|
|
126
|
+
):
|
|
127
|
+
http_error_msg = e
|
|
128
|
+
if 400 <= response.status_code < 500:
|
|
129
|
+
raise requests.HTTPError(http_error_msg, response=response)
|
|
130
|
+
else:
|
|
131
|
+
raise requests.HTTPError(str(e), response=response)
|
|
108
132
|
|
|
109
133
|
@property
|
|
110
134
|
def workspaces(self):
|
|
@@ -112,6 +136,12 @@ class HydroServer:
|
|
|
112
136
|
|
|
113
137
|
return WorkspaceService(self)
|
|
114
138
|
|
|
139
|
+
@property
|
|
140
|
+
def roles(self):
|
|
141
|
+
"""Utilities for managing HydroServer workspaces."""
|
|
142
|
+
|
|
143
|
+
return RoleService(self)
|
|
144
|
+
|
|
115
145
|
@property
|
|
116
146
|
def things(self):
|
|
117
147
|
"""Utilities for managing HydroServer things."""
|
|
@@ -5,6 +5,7 @@ from .iam.collaborator import Collaborator
|
|
|
5
5
|
from .iam.apikey import APIKey
|
|
6
6
|
from .iam.account import Account
|
|
7
7
|
from .sta.datastream import Datastream
|
|
8
|
+
from .sta.observation import ObservationCollection
|
|
8
9
|
from .sta.observed_property import ObservedProperty
|
|
9
10
|
from .sta.processing_level import ProcessingLevel
|
|
10
11
|
from .sta.result_qualifier import ResultQualifier
|
|
@@ -19,5 +20,3 @@ Workspace.model_rebuild()
|
|
|
19
20
|
Role.model_rebuild()
|
|
20
21
|
Collaborator.model_rebuild()
|
|
21
22
|
APIKey.model_rebuild()
|
|
22
|
-
|
|
23
|
-
Unit.model_rebuild()
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import Type, List, Dict, Any, Optional, ClassVar, TYPE_CHECKING
|
|
3
|
+
from requests import Response
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, PrivateAttr, Field
|
|
6
|
+
from pydantic.alias_generators import to_camel
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from hydroserverpy import HydroServer
|
|
10
|
+
from hydroserverpy.api.services.base import HydroServerBaseService
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HydroServerBaseModel(BaseModel):
|
|
14
|
+
uid: Optional[uuid.UUID] = Field(..., alias="id")
|
|
15
|
+
|
|
16
|
+
_client: "HydroServer" = PrivateAttr()
|
|
17
|
+
_service: "HydroServerBaseService" = PrivateAttr()
|
|
18
|
+
_server_data: Dict[str, Any] = PrivateAttr()
|
|
19
|
+
_editable_fields: ClassVar[set[str]] = set()
|
|
20
|
+
|
|
21
|
+
def __init__(self, *, client: "HydroServer", service: Optional["HydroServerBaseService"] = None, **data):
|
|
22
|
+
super().__init__(**data)
|
|
23
|
+
|
|
24
|
+
self._client = client
|
|
25
|
+
self._service = service
|
|
26
|
+
self._server_data = self.dict(by_alias=False).copy()
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def get_route(cls):
|
|
30
|
+
raise NotImplementedError("Route not defined")
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def client(self) -> "HydroServer":
|
|
34
|
+
return self._client
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def service(self) -> "HydroServerBaseService":
|
|
38
|
+
return self._service
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def unsaved_changes(self) -> dict:
|
|
42
|
+
return {
|
|
43
|
+
k: v for k, v in self.__dict__.items()
|
|
44
|
+
if k in self._editable_fields and k in self._server_data and v != self._server_data[k]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def save(self):
|
|
48
|
+
"""Saves changes to this resource to HydroServer."""
|
|
49
|
+
|
|
50
|
+
if not self.service:
|
|
51
|
+
raise NotImplementedError("Saving not enabled for this object.")
|
|
52
|
+
|
|
53
|
+
if not self.uid:
|
|
54
|
+
raise AttributeError("Data cannot be saved: UID is not set.")
|
|
55
|
+
|
|
56
|
+
if self.unsaved_changes:
|
|
57
|
+
saved_resource = self.service.update(
|
|
58
|
+
self.uid, **self.unsaved_changes
|
|
59
|
+
)
|
|
60
|
+
self._server_data = saved_resource.dict(by_alias=False).copy()
|
|
61
|
+
self.__dict__.update(saved_resource.__dict__)
|
|
62
|
+
|
|
63
|
+
def refresh(self):
|
|
64
|
+
"""Refreshes this resource from HydroServer."""
|
|
65
|
+
|
|
66
|
+
if not self.service:
|
|
67
|
+
raise NotImplementedError("Refreshing not enabled for this object.")
|
|
68
|
+
|
|
69
|
+
if self.uid is None:
|
|
70
|
+
raise ValueError("Cannot refresh data without a valid ID.")
|
|
71
|
+
|
|
72
|
+
refreshed_resource = self.service.get(self.uid)
|
|
73
|
+
self._server_data = refreshed_resource.dict(by_alias=False).copy()
|
|
74
|
+
self.__dict__.update(refreshed_resource.__dict__)
|
|
75
|
+
|
|
76
|
+
def delete(self):
|
|
77
|
+
"""Deletes this resource from HydroServer."""
|
|
78
|
+
|
|
79
|
+
if not self.service:
|
|
80
|
+
raise NotImplementedError("Deleting not enabled for this object.")
|
|
81
|
+
|
|
82
|
+
if self.uid is None:
|
|
83
|
+
raise AttributeError("Cannot delete data without a valid ID.")
|
|
84
|
+
|
|
85
|
+
self.service.delete(self.uid)
|
|
86
|
+
self.uid = None
|
|
87
|
+
|
|
88
|
+
model_config = ConfigDict(
|
|
89
|
+
validate_assignment=True,
|
|
90
|
+
populate_by_name=True,
|
|
91
|
+
str_strip_whitespace=True,
|
|
92
|
+
alias_generator=to_camel,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class HydroServerCollection:
|
|
98
|
+
items: List["HydroServerBaseModel"]
|
|
99
|
+
filters: Optional[dict[str, Any]] = None
|
|
100
|
+
order_by: Optional[List[str]] = None
|
|
101
|
+
page: Optional[int] = None
|
|
102
|
+
page_size: Optional[int] = None
|
|
103
|
+
total_pages: Optional[int] = None
|
|
104
|
+
total_count: Optional[int] = None
|
|
105
|
+
|
|
106
|
+
_service: Optional["HydroServerBaseService"] = field(init=False, repr=False)
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
model: Type["HydroServerBaseModel"],
|
|
111
|
+
client: "HydroServer",
|
|
112
|
+
service: Optional["HydroServerBaseService"] = None,
|
|
113
|
+
response: Optional[Response] = None,
|
|
114
|
+
**data
|
|
115
|
+
):
|
|
116
|
+
self._service = service
|
|
117
|
+
|
|
118
|
+
self.filters = data.get("filters")
|
|
119
|
+
self.order_by = data.get("order_by")
|
|
120
|
+
self.page = data.get("page") or (int(response.headers.get("X-Page")) if response else None)
|
|
121
|
+
self.page_size = data.get("page_size") or (int(response.headers.get("X-Page-Size")) if response else None)
|
|
122
|
+
self.total_pages = data.get("total_pages") or (int(response.headers.get("X-Total-Pages")) if response else None)
|
|
123
|
+
self.total_count = data.get("total_count") or (int(response.headers.get("X-Total-Count")) if response else None)
|
|
124
|
+
|
|
125
|
+
if "items" in data:
|
|
126
|
+
self.items = data["items"]
|
|
127
|
+
elif response is not None:
|
|
128
|
+
self.items = [model(client=client, **entity) for entity in response.json()]
|
|
129
|
+
else:
|
|
130
|
+
self.items = []
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def service(self) -> "HydroServerBaseService":
|
|
134
|
+
return self._service
|
|
135
|
+
|
|
136
|
+
def next_page(self):
|
|
137
|
+
"""Fetches the next page of data from HydroServer."""
|
|
138
|
+
|
|
139
|
+
if not self._service:
|
|
140
|
+
raise NotImplementedError("Pagination not enabled for this collection.")
|
|
141
|
+
|
|
142
|
+
return self._service.list(
|
|
143
|
+
**(self.filters or {}),
|
|
144
|
+
page=(self.page or 0) + 1,
|
|
145
|
+
page_size=self.page_size or 100,
|
|
146
|
+
order_by=self.order_by or ...
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def previous_page(self):
|
|
150
|
+
"""Fetches the previous page of data from HydroServer."""
|
|
151
|
+
|
|
152
|
+
if not self._service:
|
|
153
|
+
raise NotImplementedError("Pagination not enabled for this collection.")
|
|
154
|
+
|
|
155
|
+
if not self.page or self.page <= 1:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
return self._service.list(
|
|
159
|
+
**(self.filters or {}),
|
|
160
|
+
page=self.page - 1,
|
|
161
|
+
page_size=self.page_size or 100,
|
|
162
|
+
order_by=self.order_by or ...
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def fetch_all(self) -> "HydroServerCollection":
|
|
166
|
+
"""Fetches all pages of data from HydroServer for this collection."""
|
|
167
|
+
|
|
168
|
+
if not self._service:
|
|
169
|
+
raise NotImplementedError("Pagination not enabled for this collection.")
|
|
170
|
+
|
|
171
|
+
all_items = []
|
|
172
|
+
current_page = self.page or 1
|
|
173
|
+
page_size = self.page_size or 100
|
|
174
|
+
total_pages = self.total_pages
|
|
175
|
+
|
|
176
|
+
page_num = 1
|
|
177
|
+
while total_pages is None or page_num <= total_pages:
|
|
178
|
+
if page_num == current_page:
|
|
179
|
+
all_items.extend(self.items)
|
|
180
|
+
else:
|
|
181
|
+
page = self._service.list(
|
|
182
|
+
**(self.filters or {}),
|
|
183
|
+
page=page_num,
|
|
184
|
+
page_size=page_size,
|
|
185
|
+
order_by=self.order_by or ...
|
|
186
|
+
)
|
|
187
|
+
if not page.items:
|
|
188
|
+
break
|
|
189
|
+
all_items.extend(page.items)
|
|
190
|
+
|
|
191
|
+
if page.total_pages is not None:
|
|
192
|
+
total_pages = page.total_pages
|
|
193
|
+
|
|
194
|
+
page_num += 1
|
|
195
|
+
|
|
196
|
+
return self.__class__(
|
|
197
|
+
model=type(self.items[0]) if self.items else None,
|
|
198
|
+
client=self.items[0].client if self.items else None,
|
|
199
|
+
service=self._service,
|
|
200
|
+
items=all_items,
|
|
201
|
+
filters=self.filters,
|
|
202
|
+
order_by=self.order_by,
|
|
203
|
+
page=1,
|
|
204
|
+
page_size=len(all_items),
|
|
205
|
+
total_pages=1,
|
|
206
|
+
total_count=len(all_items)
|
|
207
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import Union, ClassVar, Optional, TYPE_CHECKING, List
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from .orchestration_system import OrchestrationSystem
|
|
5
|
+
from .orchestration_configuration import OrchestrationConfigurationFields
|
|
6
|
+
from ..sta.datastream import Datastream
|
|
7
|
+
from ..base import HydroServerBaseModel
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from hydroserverpy import HydroServer
|
|
11
|
+
from hydroserverpy.api.models import Workspace
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DataArchive(
|
|
15
|
+
HydroServerBaseModel, OrchestrationConfigurationFields
|
|
16
|
+
):
|
|
17
|
+
name: str = Field(..., max_length=255)
|
|
18
|
+
settings: Optional[dict] = None
|
|
19
|
+
orchestration_system_id: uuid.UUID
|
|
20
|
+
workspace_id: uuid.UUID
|
|
21
|
+
|
|
22
|
+
_editable_fields: ClassVar[set[str]] = {
|
|
23
|
+
"name", "settings", "interval", "interval_units", "crontab", "start_time", "end_time", "last_run_successful",
|
|
24
|
+
"last_run_message", "last_run", "next_run", "paused"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def __init__(self, client: "HydroServer", **data):
|
|
28
|
+
super().__init__(client=client, service=client.dataarchives, **data)
|
|
29
|
+
|
|
30
|
+
self._workspace = None
|
|
31
|
+
self._orchestration_system = None
|
|
32
|
+
self._datastreams = None
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def get_route(cls):
|
|
36
|
+
return "data-archives"
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def workspace(self) -> "Workspace":
|
|
40
|
+
"""The workspace this data archive belongs to."""
|
|
41
|
+
|
|
42
|
+
if self._workspace is None:
|
|
43
|
+
self._workspace = self.client.workspaces.get(uid=self.workspace_id)
|
|
44
|
+
|
|
45
|
+
return self._workspace
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def orchestration_system(self) -> "OrchestrationSystem":
|
|
49
|
+
"""The orchestration system that manages this data archive."""
|
|
50
|
+
|
|
51
|
+
if self._orchestration_system is None:
|
|
52
|
+
self._orchestration_system = self.client.orchestrationsystems.get(uid=self.orchestration_system_id)
|
|
53
|
+
|
|
54
|
+
return self._orchestration_system
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def datastreams(self) -> List["Datastream"]:
|
|
58
|
+
"""The datastreams this data archive provides data for."""
|
|
59
|
+
|
|
60
|
+
if self._datastreams is None:
|
|
61
|
+
self._datastreams = self.client.datastreams.list(data_archive=self.uid, fetch_all=True).items
|
|
62
|
+
|
|
63
|
+
return self._datastreams
|
|
64
|
+
|
|
65
|
+
def add_datastream(self, datastream: Union["Datastream", uuid.UUID, str]):
|
|
66
|
+
"""Add a datastream to this data archive."""
|
|
67
|
+
|
|
68
|
+
self.client.dataarchives.add_datastream(
|
|
69
|
+
uid=self.uid, datastream=datastream
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def remove_datastream(self, datastream: Union["Datastream", uuid.UUID, str]):
|
|
73
|
+
"""Remove a datastream from this data archive."""
|
|
74
|
+
|
|
75
|
+
self.client.dataarchives.remove_datastream(
|
|
76
|
+
uid=self.uid, datastream=datastream
|
|
77
|
+
)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
import tempfile
|
|
3
|
+
import requests
|
|
4
|
+
from typing import Union, ClassVar, Optional, TYPE_CHECKING, List
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
from hydroserverpy.etl_csv.hydroserver_etl_csv import HydroServerETLCSV
|
|
7
|
+
from .orchestration_system import OrchestrationSystem
|
|
8
|
+
from .orchestration_configuration import OrchestrationConfigurationFields
|
|
9
|
+
from ..sta.datastream import Datastream
|
|
10
|
+
from ..base import HydroServerBaseModel
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from hydroserverpy import HydroServer
|
|
14
|
+
from hydroserverpy.api.models import Workspace
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DataSource(
|
|
18
|
+
HydroServerBaseModel, OrchestrationConfigurationFields
|
|
19
|
+
):
|
|
20
|
+
name: str = Field(..., max_length=255)
|
|
21
|
+
settings: Optional[dict] = None
|
|
22
|
+
orchestration_system_id: uuid.UUID
|
|
23
|
+
workspace_id: uuid.UUID
|
|
24
|
+
|
|
25
|
+
_editable_fields: ClassVar[set[str]] = {
|
|
26
|
+
"name", "settings", "interval", "interval_units", "crontab", "start_time", "end_time", "last_run_successful",
|
|
27
|
+
"last_run_message", "last_run", "next_run", "paused"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def __init__(self, client: "HydroServer", **data):
|
|
31
|
+
super().__init__(client=client, service=client.datasources, **data)
|
|
32
|
+
|
|
33
|
+
self._workspace = None
|
|
34
|
+
self._orchestration_system = None
|
|
35
|
+
self._datastreams = None
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def get_route(cls):
|
|
39
|
+
return "data-sources"
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def workspace(self) -> "Workspace":
|
|
43
|
+
"""The workspace this data source belongs to."""
|
|
44
|
+
|
|
45
|
+
if self._workspace is None:
|
|
46
|
+
self._workspace = self.client.workspaces.get(uid=self.workspace_id)
|
|
47
|
+
|
|
48
|
+
return self._workspace
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def orchestration_system(self) -> "OrchestrationSystem":
|
|
52
|
+
"""The orchestration system that manages this data source."""
|
|
53
|
+
|
|
54
|
+
if self._orchestration_system is None:
|
|
55
|
+
self._orchestration_system = self.client.orchestrationsystems.get(uid=self.orchestration_system_id)
|
|
56
|
+
|
|
57
|
+
return self._orchestration_system
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def datastreams(self) -> List["Datastream"]:
|
|
61
|
+
"""The datastreams this data source provides data for."""
|
|
62
|
+
|
|
63
|
+
if self._datastreams is None:
|
|
64
|
+
self._datastreams = self.client.datastreams.list(data_source=self.uid, fetch_all=True).items
|
|
65
|
+
|
|
66
|
+
return self._datastreams
|
|
67
|
+
|
|
68
|
+
def add_datastream(self, datastream: Union["Datastream", uuid.UUID, str]):
|
|
69
|
+
"""Add a datastream to this data source."""
|
|
70
|
+
|
|
71
|
+
self.client.datasources.add_datastream(
|
|
72
|
+
uid=self.uid, datastream=datastream
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def remove_datastream(self, datastream: Union["Datastream", uuid.UUID, str]):
|
|
76
|
+
"""Remove a datastream from this data source."""
|
|
77
|
+
|
|
78
|
+
self.client.datasources.remove_datastream(
|
|
79
|
+
uid=self.uid, datastream=datastream
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# TODO: Replace with ETL module.
|
|
83
|
+
def load_data(self):
|
|
84
|
+
"""Load data for this data source."""
|
|
85
|
+
|
|
86
|
+
if self.paused is True:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
if self.settings["extractor"]["type"] == "local":
|
|
90
|
+
with open(self.settings["extractor"]["sourceUri"]) as data_file:
|
|
91
|
+
loader = HydroServerETLCSV(
|
|
92
|
+
self.client, data_file=data_file, data_source=self
|
|
93
|
+
)
|
|
94
|
+
loader.run()
|
|
95
|
+
elif self.settings["extractor"]["type"] == "HTTP":
|
|
96
|
+
with tempfile.NamedTemporaryFile(mode="w+") as temp_file:
|
|
97
|
+
response = requests.get(
|
|
98
|
+
self.settings["extractor"]["sourceUri"],
|
|
99
|
+
stream=True,
|
|
100
|
+
timeout=60,
|
|
101
|
+
)
|
|
102
|
+
response.raise_for_status()
|
|
103
|
+
chunk_size = 1024 * 1024 * 10 # Use a 10mb chunk size.
|
|
104
|
+
for chunk in response.iter_content(chunk_size=chunk_size):
|
|
105
|
+
if chunk:
|
|
106
|
+
temp_file.write(chunk.decode("utf-8"))
|
|
107
|
+
temp_file.seek(0)
|
|
108
|
+
loader = HydroServerETLCSV(
|
|
109
|
+
self.client, data_file=temp_file, data_source=self
|
|
110
|
+
)
|
|
111
|
+
loader.run()
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from typing import Optional, ClassVar, List, TYPE_CHECKING
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from ..base import HydroServerBaseModel
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from hydroserverpy import HydroServer
|
|
8
|
+
from hydroserverpy.api.models import Workspace, DataSource, DataArchive
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OrchestrationSystemFields(BaseModel):
|
|
12
|
+
name: str = Field(..., max_length=255)
|
|
13
|
+
orchestration_system_type: str = Field(..., max_length=255, alias="type")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OrchestrationSystem(HydroServerBaseModel):
|
|
17
|
+
name: str = Field(..., max_length=255)
|
|
18
|
+
orchestration_system_type: str = Field(..., max_length=255, alias="type")
|
|
19
|
+
workspace_id: Optional[uuid.UUID] = None
|
|
20
|
+
|
|
21
|
+
_editable_fields: ClassVar[set[str]] = {"name", "orchestration_system_type"}
|
|
22
|
+
|
|
23
|
+
def __init__(self, client: "HydroServer", **data):
|
|
24
|
+
super().__init__(client=client, service=client.orchestrationsystems, **data)
|
|
25
|
+
|
|
26
|
+
self._workspace = None
|
|
27
|
+
self._datasources = None
|
|
28
|
+
self._dataarchives = None
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def get_route(cls):
|
|
32
|
+
return "orchestration-systems"
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def workspace(self) -> "Workspace":
|
|
36
|
+
"""The workspace this orchestration system belongs to."""
|
|
37
|
+
|
|
38
|
+
if self._workspace is None and self.workspace_id:
|
|
39
|
+
self._workspace = self.client.workspaces.get(uid=self.workspace_id)
|
|
40
|
+
|
|
41
|
+
return self._workspace
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def datasources(self) -> List["DataSource"]:
|
|
45
|
+
"""The data sources associated with this workspace."""
|
|
46
|
+
|
|
47
|
+
if self._datasources is None:
|
|
48
|
+
self._datasources = self.client.datasources.list(
|
|
49
|
+
orchestration_system=self.uid, fetch_all=True
|
|
50
|
+
).items
|
|
51
|
+
|
|
52
|
+
return self._datasources
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def dataarchives(self) -> List["DataArchive"]:
|
|
56
|
+
"""The data archives associated with this workspace."""
|
|
57
|
+
|
|
58
|
+
if self._dataarchives is None:
|
|
59
|
+
self._dataarchives = self.client.dataarchives.list(
|
|
60
|
+
orchestration_system=self.uid, fetch_all=True
|
|
61
|
+
).items
|
|
62
|
+
|
|
63
|
+
return self._dataarchives
|