hydroserverpy 0.4.0__py3-none-any.whl → 0.5.0b2__py3-none-any.whl
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/__init__.py +2 -3
- hydroserverpy/api/http.py +22 -0
- hydroserverpy/api/main.py +173 -0
- hydroserverpy/api/models/__init__.py +21 -0
- hydroserverpy/api/models/base.py +74 -0
- hydroserverpy/api/models/etl/__init__.py +0 -0
- hydroserverpy/api/models/etl/data_archive.py +105 -0
- hydroserverpy/api/models/etl/data_source.py +150 -0
- hydroserverpy/api/models/etl/orchestration_configuration.py +35 -0
- hydroserverpy/api/models/etl/orchestration_system.py +78 -0
- hydroserverpy/api/models/iam/__init__.py +0 -0
- hydroserverpy/api/models/iam/account.py +12 -0
- hydroserverpy/api/models/iam/collaborator.py +34 -0
- hydroserverpy/api/models/iam/role.py +10 -0
- hydroserverpy/api/models/iam/workspace.py +238 -0
- hydroserverpy/api/models/sta/__init__.py +0 -0
- hydroserverpy/api/models/sta/datastream.py +338 -0
- hydroserverpy/api/models/sta/observed_property.py +72 -0
- hydroserverpy/api/models/sta/processing_level.py +50 -0
- hydroserverpy/api/models/sta/result_qualifier.py +49 -0
- hydroserverpy/api/models/sta/sensor.py +105 -0
- hydroserverpy/api/models/sta/thing.py +217 -0
- hydroserverpy/api/models/sta/unit.py +49 -0
- hydroserverpy/api/services/__init__.py +11 -0
- hydroserverpy/api/services/base.py +102 -0
- hydroserverpy/api/services/etl/__init__.py +0 -0
- hydroserverpy/api/services/etl/data_archive.py +196 -0
- hydroserverpy/api/services/etl/data_source.py +196 -0
- hydroserverpy/api/services/etl/orchestration_system.py +74 -0
- hydroserverpy/api/services/iam/__init__.py +0 -0
- hydroserverpy/api/services/iam/workspace.py +126 -0
- hydroserverpy/api/services/sta/__init__.py +0 -0
- hydroserverpy/api/services/sta/datastream.py +354 -0
- hydroserverpy/api/services/sta/observed_property.py +100 -0
- hydroserverpy/api/services/sta/processing_level.py +78 -0
- hydroserverpy/api/services/sta/result_qualifier.py +74 -0
- hydroserverpy/api/services/sta/sensor.py +116 -0
- hydroserverpy/api/services/sta/thing.py +188 -0
- hydroserverpy/api/services/sta/unit.py +82 -0
- hydroserverpy/etl/loaders/hydroserver_loader.py +1 -1
- hydroserverpy/etl_csv/hydroserver_etl_csv.py +49 -34
- {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b2.dist-info}/METADATA +4 -3
- hydroserverpy-0.5.0b2.dist-info/RECORD +66 -0
- {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b2.dist-info}/WHEEL +1 -1
- hydroserverpy/core/endpoints/__init__.py +0 -9
- hydroserverpy/core/endpoints/base.py +0 -146
- hydroserverpy/core/endpoints/data_loaders.py +0 -93
- hydroserverpy/core/endpoints/data_sources.py +0 -93
- hydroserverpy/core/endpoints/datastreams.py +0 -225
- hydroserverpy/core/endpoints/observed_properties.py +0 -111
- hydroserverpy/core/endpoints/processing_levels.py +0 -111
- hydroserverpy/core/endpoints/result_qualifiers.py +0 -111
- hydroserverpy/core/endpoints/sensors.py +0 -111
- hydroserverpy/core/endpoints/things.py +0 -261
- hydroserverpy/core/endpoints/units.py +0 -111
- hydroserverpy/core/schemas/__init__.py +0 -9
- hydroserverpy/core/schemas/base.py +0 -124
- hydroserverpy/core/schemas/data_loaders.py +0 -73
- hydroserverpy/core/schemas/data_sources.py +0 -223
- hydroserverpy/core/schemas/datastreams.py +0 -330
- hydroserverpy/core/schemas/observed_properties.py +0 -43
- hydroserverpy/core/schemas/processing_levels.py +0 -31
- hydroserverpy/core/schemas/result_qualifiers.py +0 -26
- hydroserverpy/core/schemas/sensors.py +0 -68
- hydroserverpy/core/schemas/things.py +0 -346
- hydroserverpy/core/schemas/units.py +0 -29
- hydroserverpy/core/service.py +0 -200
- hydroserverpy-0.4.0.dist-info/RECORD +0 -51
- /hydroserverpy/{core → api}/__init__.py +0 -0
- {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b2.dist-info/licenses}/LICENSE +0 -0
- {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b2.dist-info}/top_level.txt +0 -0
- {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b2.dist-info}/zip-safe +0 -0
hydroserverpy/__init__.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .quality.service import HydroServerQualityControl
|
|
1
|
+
from .api.main import HydroServer
|
|
3
2
|
from .etl.hydroserver_etl import HydroServerETL
|
|
4
|
-
|
|
3
|
+
from .quality import HydroServerQualityControl
|
|
5
4
|
|
|
6
5
|
__all__ = [
|
|
7
6
|
"HydroServer",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from requests import HTTPError
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def raise_for_hs_status(response):
|
|
6
|
+
try:
|
|
7
|
+
response.raise_for_status()
|
|
8
|
+
except HTTPError as e:
|
|
9
|
+
try:
|
|
10
|
+
http_error_msg = (
|
|
11
|
+
f"{response.status_code} Client Error: "
|
|
12
|
+
f"{str(json.loads(response.content).get('detail'))}"
|
|
13
|
+
)
|
|
14
|
+
except (
|
|
15
|
+
ValueError,
|
|
16
|
+
TypeError,
|
|
17
|
+
):
|
|
18
|
+
http_error_msg = e
|
|
19
|
+
if 400 <= response.status_code < 500:
|
|
20
|
+
raise HTTPError(http_error_msg, response=response)
|
|
21
|
+
else:
|
|
22
|
+
raise HTTPError(str(e), response=response)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Optional, Tuple
|
|
3
|
+
from hydroserverpy.api.http import raise_for_hs_status
|
|
4
|
+
from hydroserverpy.api.services import (
|
|
5
|
+
WorkspaceService,
|
|
6
|
+
ThingService,
|
|
7
|
+
ObservedPropertyService,
|
|
8
|
+
UnitService,
|
|
9
|
+
ProcessingLevelService,
|
|
10
|
+
ResultQualifierService,
|
|
11
|
+
SensorService,
|
|
12
|
+
DatastreamService,
|
|
13
|
+
OrchestrationSystemService,
|
|
14
|
+
DataSourceService,
|
|
15
|
+
DataArchiveService,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HydroServer:
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
host: str,
|
|
23
|
+
email: Optional[str] = None,
|
|
24
|
+
password: Optional[str] = None,
|
|
25
|
+
apikey: Optional[str] = None,
|
|
26
|
+
):
|
|
27
|
+
self.host = host.strip("/")
|
|
28
|
+
self.auth = (
|
|
29
|
+
(
|
|
30
|
+
email or "__key__",
|
|
31
|
+
password or apikey,
|
|
32
|
+
)
|
|
33
|
+
if (email and password) or apikey
|
|
34
|
+
else None
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
self._auth_url = f"{self.host}/api/auth/app/session"
|
|
38
|
+
|
|
39
|
+
self._session = None
|
|
40
|
+
self._timeout = 60
|
|
41
|
+
self._auth_header = None
|
|
42
|
+
|
|
43
|
+
self._init_session()
|
|
44
|
+
|
|
45
|
+
def login(self, email: str, password: str) -> None:
|
|
46
|
+
"""Provide your HydroServer credentials to log in to your account."""
|
|
47
|
+
|
|
48
|
+
self._init_session(auth=(email, password))
|
|
49
|
+
|
|
50
|
+
def logout(self) -> None:
|
|
51
|
+
"""End your HydroServer session."""
|
|
52
|
+
|
|
53
|
+
self._session.delete(self._auth_url, timeout=self._timeout)
|
|
54
|
+
|
|
55
|
+
def _init_session(self, auth: Optional[Tuple[str, str]] = None) -> None:
|
|
56
|
+
if self._session is not None:
|
|
57
|
+
self.logout()
|
|
58
|
+
self._session.close()
|
|
59
|
+
|
|
60
|
+
self._session = requests.Session()
|
|
61
|
+
|
|
62
|
+
auth = auth or self.auth
|
|
63
|
+
|
|
64
|
+
if auth and auth[0] == "__key__":
|
|
65
|
+
self._session.headers.update({"key": auth[1]})
|
|
66
|
+
elif auth:
|
|
67
|
+
self._session.headers.update(
|
|
68
|
+
{"Authorization": f"Bearer {self._authenticate(auth[0], auth[1])}"}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def _authenticate(self, email: str, password: str) -> None:
|
|
72
|
+
response = self._session.post(
|
|
73
|
+
self._auth_url,
|
|
74
|
+
json={"email": email, "password": password},
|
|
75
|
+
timeout=self._timeout,
|
|
76
|
+
)
|
|
77
|
+
response.raise_for_status()
|
|
78
|
+
session_token = response.json().get("meta", {}).get("session_token")
|
|
79
|
+
|
|
80
|
+
if not session_token:
|
|
81
|
+
raise ValueError("Authentication failed: No access token returned.")
|
|
82
|
+
|
|
83
|
+
return session_token
|
|
84
|
+
|
|
85
|
+
def request(self, method, path, *args, **kwargs) -> requests.Response:
|
|
86
|
+
"""Sends a request to HydroServer's API."""
|
|
87
|
+
|
|
88
|
+
for attempt in range(2):
|
|
89
|
+
try:
|
|
90
|
+
response = getattr(self._session, method)(
|
|
91
|
+
f"{self.host}/{path.strip('/')}",
|
|
92
|
+
timeout=self._timeout,
|
|
93
|
+
*args,
|
|
94
|
+
**kwargs,
|
|
95
|
+
)
|
|
96
|
+
raise_for_hs_status(response)
|
|
97
|
+
except (
|
|
98
|
+
requests.exceptions.HTTPError,
|
|
99
|
+
requests.exceptions.ConnectionError,
|
|
100
|
+
) as e:
|
|
101
|
+
if attempt == 0:
|
|
102
|
+
self._init_session()
|
|
103
|
+
continue
|
|
104
|
+
else:
|
|
105
|
+
raise e
|
|
106
|
+
|
|
107
|
+
return response
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def workspaces(self):
|
|
111
|
+
"""Utilities for managing HydroServer workspaces."""
|
|
112
|
+
|
|
113
|
+
return WorkspaceService(self)
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def things(self):
|
|
117
|
+
"""Utilities for managing HydroServer things."""
|
|
118
|
+
|
|
119
|
+
return ThingService(self)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def observedproperties(self):
|
|
123
|
+
"""Utilities for managing HydroServer observed properties."""
|
|
124
|
+
|
|
125
|
+
return ObservedPropertyService(self)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def units(self):
|
|
129
|
+
"""Utilities for managing HydroServer units."""
|
|
130
|
+
|
|
131
|
+
return UnitService(self)
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def processinglevels(self):
|
|
135
|
+
"""Utilities for managing HydroServer processing levels."""
|
|
136
|
+
|
|
137
|
+
return ProcessingLevelService(self)
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def resultqualifiers(self):
|
|
141
|
+
"""Utilities for managing HydroServer result qualifiers."""
|
|
142
|
+
|
|
143
|
+
return ResultQualifierService(self)
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def sensors(self):
|
|
147
|
+
"""Utilities for managing HydroServer sensors."""
|
|
148
|
+
|
|
149
|
+
return SensorService(self)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def datastreams(self):
|
|
153
|
+
"""Utilities for managing HydroServer datastreams."""
|
|
154
|
+
|
|
155
|
+
return DatastreamService(self)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def orchestrationsystems(self):
|
|
159
|
+
"""Utilities for managing HydroServer orchestration systems."""
|
|
160
|
+
|
|
161
|
+
return OrchestrationSystemService(self)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def datasources(self):
|
|
165
|
+
"""Utilities for managing HydroServer data sources."""
|
|
166
|
+
|
|
167
|
+
return DataSourceService(self)
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def dataarchives(self):
|
|
171
|
+
"""Utilities for managing HydroServer data archives."""
|
|
172
|
+
|
|
173
|
+
return DataArchiveService(self)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .iam.account import Account
|
|
2
|
+
from .iam.workspace import Workspace
|
|
3
|
+
from .iam.role import Role
|
|
4
|
+
from .iam.collaborator import Collaborator
|
|
5
|
+
from .iam.account import Account
|
|
6
|
+
from .sta.datastream import Datastream
|
|
7
|
+
from .sta.observed_property import ObservedProperty
|
|
8
|
+
from .sta.processing_level import ProcessingLevel
|
|
9
|
+
from .sta.result_qualifier import ResultQualifier
|
|
10
|
+
from .sta.sensor import Sensor
|
|
11
|
+
from .sta.thing import Thing
|
|
12
|
+
from .sta.unit import Unit
|
|
13
|
+
from .etl.orchestration_system import OrchestrationSystem
|
|
14
|
+
from .etl.data_source import DataSource
|
|
15
|
+
from .etl.data_archive import DataArchive
|
|
16
|
+
|
|
17
|
+
Workspace.model_rebuild()
|
|
18
|
+
Role.model_rebuild()
|
|
19
|
+
Collaborator.model_rebuild()
|
|
20
|
+
|
|
21
|
+
Unit.model_rebuild()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from uuid import UUID
|
|
3
|
+
from pydantic import BaseModel, PrivateAttr, ConfigDict, computed_field
|
|
4
|
+
from pydantic.alias_generators import to_camel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HydroServerBaseModel(BaseModel):
|
|
8
|
+
_uid: Optional[UUID] = PrivateAttr()
|
|
9
|
+
|
|
10
|
+
def __init__(self, _uid: Optional[UUID] = None, **data):
|
|
11
|
+
super().__init__(**data)
|
|
12
|
+
self._uid = _uid
|
|
13
|
+
|
|
14
|
+
@computed_field
|
|
15
|
+
@property
|
|
16
|
+
def uid(self) -> Optional[UUID]:
|
|
17
|
+
"""The unique identifier for this resource."""
|
|
18
|
+
|
|
19
|
+
return self._uid
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(
|
|
22
|
+
validate_assignment=True,
|
|
23
|
+
populate_by_name=True,
|
|
24
|
+
str_strip_whitespace=True,
|
|
25
|
+
alias_generator=to_camel,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class HydroServerModel(HydroServerBaseModel):
|
|
30
|
+
_model_ref: str = PrivateAttr()
|
|
31
|
+
_original_data: Optional[dict] = PrivateAttr()
|
|
32
|
+
|
|
33
|
+
def __init__(self, _connection, _model_ref, _uid: Optional[UUID] = None, **data):
|
|
34
|
+
if isinstance(_uid, str):
|
|
35
|
+
_uid = UUID(_uid)
|
|
36
|
+
|
|
37
|
+
super().__init__(_uid=_uid, **data)
|
|
38
|
+
|
|
39
|
+
self._connection = _connection
|
|
40
|
+
self._model_ref = _model_ref
|
|
41
|
+
self._original_data = self.dict(by_alias=False).copy()
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def _patch_data(self) -> dict:
|
|
45
|
+
return {
|
|
46
|
+
key: getattr(self, key)
|
|
47
|
+
for key, value in self._original_data.items()
|
|
48
|
+
if hasattr(self, key) and getattr(self, key) != value
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
def _refresh(self) -> None:
|
|
52
|
+
"""Refresh this resource from HydroServer."""
|
|
53
|
+
|
|
54
|
+
self._original_data = (
|
|
55
|
+
getattr(self._connection, self._model_ref)
|
|
56
|
+
.get(uid=self.uid)
|
|
57
|
+
.model_dump(exclude=["uid"])
|
|
58
|
+
)
|
|
59
|
+
self.__dict__.update(self._original_data)
|
|
60
|
+
|
|
61
|
+
def _save(self) -> None:
|
|
62
|
+
if self._patch_data:
|
|
63
|
+
entity = getattr(self._connection, self._model_ref).update(
|
|
64
|
+
uid=self.uid, **self._patch_data
|
|
65
|
+
)
|
|
66
|
+
self._original_data = entity.dict(by_alias=False, exclude=["uid"])
|
|
67
|
+
self.__dict__.update(self._original_data)
|
|
68
|
+
|
|
69
|
+
def _delete(self) -> None:
|
|
70
|
+
if not self._uid:
|
|
71
|
+
raise AttributeError("This resource cannot be deleted: UID is not set.")
|
|
72
|
+
|
|
73
|
+
getattr(self._connection, self._model_ref).delete(uid=self._uid)
|
|
74
|
+
self._uid = None
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from typing import Union, Optional, TYPE_CHECKING, List
|
|
2
|
+
from uuid import UUID
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from .orchestration_system import OrchestrationSystem
|
|
5
|
+
from .orchestration_configuration import OrchestrationConfigurationFields
|
|
6
|
+
from ..sta.datastream import Datastream
|
|
7
|
+
from ..base import HydroServerModel
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from hydroserverpy import HydroServer
|
|
11
|
+
from hydroserverpy.api.models import Workspace
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DataArchiveFields(BaseModel):
|
|
15
|
+
name: str = Field(..., max_length=255)
|
|
16
|
+
settings: Optional[dict] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DataArchive(
|
|
20
|
+
HydroServerModel, DataArchiveFields, OrchestrationConfigurationFields
|
|
21
|
+
):
|
|
22
|
+
def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
|
|
23
|
+
super().__init__(
|
|
24
|
+
_connection=_connection, _model_ref="dataarchives", _uid=_uid, **data
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
self._workspace_id = str(data.get("workspace_id") or data["workspaceId"])
|
|
28
|
+
self._orchestration_system_id = str(
|
|
29
|
+
data.get("orchestration_system_id") or data["orchestrationSystem"]["id"]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
self._workspace = None
|
|
33
|
+
|
|
34
|
+
if data.get("orchestrationSystem"):
|
|
35
|
+
self._orchestration_system = OrchestrationSystem(
|
|
36
|
+
_connection=_connection,
|
|
37
|
+
_uid=self._orchestration_system_id,
|
|
38
|
+
**data["orchestrationSystem"]
|
|
39
|
+
)
|
|
40
|
+
else:
|
|
41
|
+
self._orchestration_system = None
|
|
42
|
+
|
|
43
|
+
if data.get("datastreams"):
|
|
44
|
+
self._datastreams = [
|
|
45
|
+
Datastream(_connection=_connection, _uid=datastream["id"], **datastream)
|
|
46
|
+
for datastream in data["datastreams"]
|
|
47
|
+
]
|
|
48
|
+
else:
|
|
49
|
+
self._datastreams = []
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def workspace(self) -> "Workspace":
|
|
53
|
+
"""The workspace this data archive belongs to."""
|
|
54
|
+
|
|
55
|
+
if self._workspace is None and self._workspace_id:
|
|
56
|
+
self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
|
|
57
|
+
|
|
58
|
+
return self._workspace
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def orchestration_system(self) -> "OrchestrationSystem":
|
|
62
|
+
"""The orchestration system that manages this data archive."""
|
|
63
|
+
|
|
64
|
+
if self._orchestration_system is None and self._orchestration_system_id:
|
|
65
|
+
self._orchestration_system = self._connection.orchestration_systems.get(
|
|
66
|
+
uid=self._orchestration_system_id
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return self._orchestration_system
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def datastreams(self) -> List["Datastream"]:
|
|
73
|
+
"""The datastreams this data archive provides data for."""
|
|
74
|
+
|
|
75
|
+
return self._datastreams
|
|
76
|
+
|
|
77
|
+
def refresh(self):
|
|
78
|
+
"""Refresh this data archive from HydroServer."""
|
|
79
|
+
|
|
80
|
+
super()._refresh()
|
|
81
|
+
self._workspace = None
|
|
82
|
+
|
|
83
|
+
def save(self):
|
|
84
|
+
"""Save changes to this data archive to HydroServer."""
|
|
85
|
+
|
|
86
|
+
super()._save()
|
|
87
|
+
|
|
88
|
+
def delete(self):
|
|
89
|
+
"""Delete this data archive from HydroServer."""
|
|
90
|
+
|
|
91
|
+
super()._delete()
|
|
92
|
+
|
|
93
|
+
def add_datastream(self, datastream: Union["Datastream", UUID, str]):
|
|
94
|
+
"""Add a datastream to this data archive."""
|
|
95
|
+
|
|
96
|
+
self._connection.dataarchives.add_datastream(
|
|
97
|
+
uid=self.uid, datastream=datastream
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def remove_datastream(self, datastream: Union["Datastream", UUID, str]):
|
|
101
|
+
"""Remove a datastream from this data archive."""
|
|
102
|
+
|
|
103
|
+
self._connection.dataarchives.remove_datastream(
|
|
104
|
+
uid=self.uid, datastream=datastream
|
|
105
|
+
)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
from typing import Union, List, Optional, TYPE_CHECKING
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from urllib.request import urlopen
|
|
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 HydroServerModel
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from hydroserverpy import HydroServer
|
|
14
|
+
from hydroserverpy.api.models import Workspace
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DataSourceFields(BaseModel):
|
|
18
|
+
name: str = Field(..., max_length=255)
|
|
19
|
+
settings: Optional[dict] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DataSource(HydroServerModel, DataSourceFields, OrchestrationConfigurationFields):
|
|
23
|
+
def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
|
|
24
|
+
super().__init__(
|
|
25
|
+
_connection=_connection, _model_ref="datasources", _uid=_uid, **data
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
self._workspace_id = str(data.get("workspace_id") or data["workspaceId"])
|
|
29
|
+
self._orchestration_system_id = str(
|
|
30
|
+
data.get("orchestration_system_id") or data["orchestrationSystem"]["id"]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
self._workspace = None
|
|
34
|
+
|
|
35
|
+
if data.get("orchestrationSystem"):
|
|
36
|
+
self._orchestration_system = OrchestrationSystem(
|
|
37
|
+
_connection=_connection,
|
|
38
|
+
_uid=self._orchestration_system_id,
|
|
39
|
+
**data["orchestrationSystem"]
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
self._orchestration_system = None
|
|
43
|
+
|
|
44
|
+
if data.get("datastreams"):
|
|
45
|
+
self._datastreams = [
|
|
46
|
+
Datastream(_connection=_connection, _uid=datastream["id"], **datastream)
|
|
47
|
+
for datastream in data["datastreams"]
|
|
48
|
+
]
|
|
49
|
+
else:
|
|
50
|
+
self._datastreams = []
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def workspace(self) -> "Workspace":
|
|
54
|
+
"""The workspace this data source belongs to."""
|
|
55
|
+
|
|
56
|
+
if self._workspace is None and self._workspace_id:
|
|
57
|
+
self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
|
|
58
|
+
|
|
59
|
+
return self._workspace
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def orchestration_system(self) -> "OrchestrationSystem":
|
|
63
|
+
"""The orchestration system that manages this data source."""
|
|
64
|
+
|
|
65
|
+
if self._orchestration_system is None and self._orchestration_system_id:
|
|
66
|
+
self._orchestration_system = self._connection.orchestration_systems.get(
|
|
67
|
+
uid=self._orchestration_system_id
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return self._orchestration_system
|
|
71
|
+
|
|
72
|
+
@orchestration_system.setter
|
|
73
|
+
def orchestration_system(
|
|
74
|
+
self, orchestration_system: Union["OrchestrationSystem", UUID, str]
|
|
75
|
+
):
|
|
76
|
+
if not orchestration_system:
|
|
77
|
+
raise ValueError("Orchestration system of data source cannot be None.")
|
|
78
|
+
if str(getattr(orchestration_system, "uid", orchestration_system)) != str(
|
|
79
|
+
self.orchestration_system.uid
|
|
80
|
+
):
|
|
81
|
+
self._orchestration_system = self._connection.orchestrationsystems.get(
|
|
82
|
+
uid=str(getattr(orchestration_system, "uid", orchestration_system))
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def datastreams(self) -> List["Datastream"]:
|
|
87
|
+
"""The datastreams this data source provides data for."""
|
|
88
|
+
|
|
89
|
+
if self._datastreams is None:
|
|
90
|
+
data_source = self._connection.datasources.get(uid=self.uid)
|
|
91
|
+
self._datastreams = data_source.datastreams
|
|
92
|
+
|
|
93
|
+
return self._datastreams
|
|
94
|
+
|
|
95
|
+
def refresh(self):
|
|
96
|
+
"""Refresh this data source from HydroServer."""
|
|
97
|
+
|
|
98
|
+
super()._refresh()
|
|
99
|
+
self._workspace = None
|
|
100
|
+
self._datastreams = None
|
|
101
|
+
|
|
102
|
+
def save(self):
|
|
103
|
+
"""Save changes to this data source to HydroServer."""
|
|
104
|
+
|
|
105
|
+
super()._save()
|
|
106
|
+
|
|
107
|
+
def delete(self):
|
|
108
|
+
"""Delete this data source from HydroServer."""
|
|
109
|
+
|
|
110
|
+
super()._delete()
|
|
111
|
+
|
|
112
|
+
def add_datastream(self, datastream: Union["Datastream", UUID, str]):
|
|
113
|
+
"""Add a datastream to this data source."""
|
|
114
|
+
|
|
115
|
+
self._connection.datasources.add_datastream(uid=self.uid, datastream=datastream)
|
|
116
|
+
|
|
117
|
+
def remove_datastream(self, datastream: Union["Datastream", UUID, str]):
|
|
118
|
+
"""Remove a datastream from this data source."""
|
|
119
|
+
|
|
120
|
+
self._connection.datasources.remove_datastream(
|
|
121
|
+
uid=self.uid, datastream=datastream
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# TODO: Replace with ETL module.
|
|
125
|
+
def load_data(self):
|
|
126
|
+
"""Load data for this data source."""
|
|
127
|
+
|
|
128
|
+
if self.paused is True:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
if self.settings["extractor"]["type"] == "local":
|
|
132
|
+
with open(self.settings["extractor"]["path"]) as data_file:
|
|
133
|
+
loader = HydroServerETLCSV(
|
|
134
|
+
self._connection, data_file=data_file, data_source=self
|
|
135
|
+
)
|
|
136
|
+
loader.run()
|
|
137
|
+
elif self.settings["extractor"]["type"] == "HTTP":
|
|
138
|
+
with tempfile.NamedTemporaryFile(mode="w") as temp_file:
|
|
139
|
+
with urlopen(self.settings["extractor"]["urlTemplate"]) as response:
|
|
140
|
+
chunk_size = 1024 * 1024 * 10 # Use a 10mb chunk size.
|
|
141
|
+
while True:
|
|
142
|
+
chunk = response.read(chunk_size)
|
|
143
|
+
if not chunk:
|
|
144
|
+
break
|
|
145
|
+
temp_file.write(chunk)
|
|
146
|
+
temp_file.seek(0)
|
|
147
|
+
loader = HydroServerETLCSV(
|
|
148
|
+
self._connection, data_file=temp_file, data_source=self
|
|
149
|
+
)
|
|
150
|
+
loader.run()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from pydantic import AliasPath
|
|
2
|
+
from typing import Optional, Literal
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OrchestrationConfigurationFields(BaseModel):
|
|
8
|
+
interval: Optional[int] = Field(
|
|
9
|
+
None, gt=0, validation_alias=AliasPath("schedule", "interval")
|
|
10
|
+
)
|
|
11
|
+
interval_units: Optional[Literal["minutes", "hours", "days"]] = Field(
|
|
12
|
+
None, validation_alias=AliasPath("schedule", "intervalUnits")
|
|
13
|
+
)
|
|
14
|
+
crontab: Optional[str] = Field(
|
|
15
|
+
None, max_length=255, validation_alias=AliasPath("schedule", "crontab")
|
|
16
|
+
)
|
|
17
|
+
start_time: Optional[datetime] = Field(
|
|
18
|
+
None, validation_alias=AliasPath("schedule", "startTime")
|
|
19
|
+
)
|
|
20
|
+
end_time: Optional[datetime] = Field(
|
|
21
|
+
None, validation_alias=AliasPath("schedule", "endTime")
|
|
22
|
+
)
|
|
23
|
+
last_run_successful: Optional[bool] = Field(
|
|
24
|
+
None, validation_alias=AliasPath("status", "lastRunSuccessful")
|
|
25
|
+
)
|
|
26
|
+
last_run_message: Optional[str] = Field(
|
|
27
|
+
None, max_length=255, validation_alias=AliasPath("status", "lastRunMessage")
|
|
28
|
+
)
|
|
29
|
+
last_run: Optional[datetime] = Field(
|
|
30
|
+
None, validation_alias=AliasPath("status", "lastRun")
|
|
31
|
+
)
|
|
32
|
+
next_run: Optional[datetime] = Field(
|
|
33
|
+
None, validation_alias=AliasPath("status", "nextRun")
|
|
34
|
+
)
|
|
35
|
+
paused: bool = Field(False, validation_alias=AliasPath("status", "paused"))
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from typing import Union, List, TYPE_CHECKING
|
|
2
|
+
from uuid import UUID
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from ..base import HydroServerModel
|
|
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(HydroServerModel, OrchestrationSystemFields):
|
|
17
|
+
def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
|
|
18
|
+
super().__init__(
|
|
19
|
+
_connection=_connection,
|
|
20
|
+
_model_ref="orchestrationsystems",
|
|
21
|
+
_uid=_uid,
|
|
22
|
+
**data
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
self._workspace_id = str(data.get("workspace_id") or data["workspaceId"])
|
|
26
|
+
|
|
27
|
+
self._workspace = None
|
|
28
|
+
self._datasources = None
|
|
29
|
+
self._dataarchives = None
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def workspace(self) -> "Workspace":
|
|
33
|
+
"""The workspace this orchestration system belongs to."""
|
|
34
|
+
|
|
35
|
+
if self._workspace is None and self._workspace_id:
|
|
36
|
+
self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
|
|
37
|
+
|
|
38
|
+
return self._workspace
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def datasources(self) -> List["DataSource"]:
|
|
42
|
+
"""The data sources associated with this workspace."""
|
|
43
|
+
|
|
44
|
+
if self._datasources is None:
|
|
45
|
+
self._datasources = self._connection.datasources.list(
|
|
46
|
+
orchestration_system=self.uid
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return self._datasources
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def dataarchives(self) -> List["DataArchive"]:
|
|
53
|
+
"""The data archives associated with this workspace."""
|
|
54
|
+
|
|
55
|
+
if self._dataarchives is None:
|
|
56
|
+
self._dataarchives = self._connection.dataarchives.list(
|
|
57
|
+
orchestration_system=self.uid
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return self._dataarchives
|
|
61
|
+
|
|
62
|
+
def refresh(self):
|
|
63
|
+
"""Refresh this orchestration system from HydroServer."""
|
|
64
|
+
|
|
65
|
+
super()._refresh()
|
|
66
|
+
self._workspace = None
|
|
67
|
+
self._datasources = None
|
|
68
|
+
self._dataarchives = None
|
|
69
|
+
|
|
70
|
+
def save(self):
|
|
71
|
+
"""Save changes to this orchestration system to HydroServer."""
|
|
72
|
+
|
|
73
|
+
super()._save()
|
|
74
|
+
|
|
75
|
+
def delete(self):
|
|
76
|
+
"""Delete this orchestration system from HydroServer."""
|
|
77
|
+
|
|
78
|
+
super()._delete()
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from pydantic import BaseModel, Field, EmailStr
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Account(BaseModel):
|
|
6
|
+
name: str = Field(..., max_length=255)
|
|
7
|
+
email: EmailStr
|
|
8
|
+
organization_name: Optional[str] = None
|
|
9
|
+
phone: Optional[str] = Field(None, max_length=15)
|
|
10
|
+
address: Optional[str] = Field(None, max_length=255)
|
|
11
|
+
link: Optional[str] = Field(None, max_length=2000)
|
|
12
|
+
user_type: str = Field(..., max_length=255, alias="type")
|