hydroserverpy 0.4.0__py3-none-any.whl → 0.5.0b1__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.

Files changed (65) hide show
  1. hydroserverpy/__init__.py +2 -3
  2. hydroserverpy/api/http.py +24 -0
  3. hydroserverpy/api/main.py +152 -0
  4. hydroserverpy/api/models/__init__.py +18 -0
  5. hydroserverpy/api/models/base.py +74 -0
  6. hydroserverpy/api/models/etl/__init__.py +0 -0
  7. hydroserverpy/api/models/iam/__init__.py +0 -0
  8. hydroserverpy/api/models/iam/account.py +12 -0
  9. hydroserverpy/api/models/iam/collaborator.py +34 -0
  10. hydroserverpy/api/models/iam/role.py +10 -0
  11. hydroserverpy/api/models/iam/workspace.py +203 -0
  12. hydroserverpy/api/models/sta/__init__.py +0 -0
  13. hydroserverpy/api/models/sta/datastream.py +336 -0
  14. hydroserverpy/api/models/sta/observed_property.py +72 -0
  15. hydroserverpy/api/models/sta/processing_level.py +50 -0
  16. hydroserverpy/api/models/sta/result_qualifier.py +49 -0
  17. hydroserverpy/api/models/sta/sensor.py +105 -0
  18. hydroserverpy/api/models/sta/thing.py +217 -0
  19. hydroserverpy/api/models/sta/unit.py +49 -0
  20. hydroserverpy/api/services/__init__.py +8 -0
  21. hydroserverpy/api/services/base.py +92 -0
  22. hydroserverpy/api/services/etl/__init__.py +0 -0
  23. hydroserverpy/api/services/iam/__init__.py +0 -0
  24. hydroserverpy/api/services/iam/workspace.py +126 -0
  25. hydroserverpy/api/services/sta/__init__.py +0 -0
  26. hydroserverpy/api/services/sta/datastream.py +354 -0
  27. hydroserverpy/api/services/sta/observed_property.py +98 -0
  28. hydroserverpy/api/services/sta/processing_level.py +78 -0
  29. hydroserverpy/api/services/sta/result_qualifier.py +74 -0
  30. hydroserverpy/api/services/sta/sensor.py +116 -0
  31. hydroserverpy/api/services/sta/thing.py +188 -0
  32. hydroserverpy/api/services/sta/unit.py +82 -0
  33. hydroserverpy/etl/loaders/hydroserver_loader.py +1 -1
  34. hydroserverpy/etl_csv/hydroserver_etl_csv.py +1 -1
  35. {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b1.dist-info}/METADATA +4 -3
  36. hydroserverpy-0.5.0b1.dist-info/RECORD +59 -0
  37. {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b1.dist-info}/WHEEL +1 -1
  38. hydroserverpy/core/endpoints/__init__.py +0 -9
  39. hydroserverpy/core/endpoints/base.py +0 -146
  40. hydroserverpy/core/endpoints/data_loaders.py +0 -93
  41. hydroserverpy/core/endpoints/data_sources.py +0 -93
  42. hydroserverpy/core/endpoints/datastreams.py +0 -225
  43. hydroserverpy/core/endpoints/observed_properties.py +0 -111
  44. hydroserverpy/core/endpoints/processing_levels.py +0 -111
  45. hydroserverpy/core/endpoints/result_qualifiers.py +0 -111
  46. hydroserverpy/core/endpoints/sensors.py +0 -111
  47. hydroserverpy/core/endpoints/things.py +0 -261
  48. hydroserverpy/core/endpoints/units.py +0 -111
  49. hydroserverpy/core/schemas/__init__.py +0 -9
  50. hydroserverpy/core/schemas/base.py +0 -124
  51. hydroserverpy/core/schemas/data_loaders.py +0 -73
  52. hydroserverpy/core/schemas/data_sources.py +0 -223
  53. hydroserverpy/core/schemas/datastreams.py +0 -330
  54. hydroserverpy/core/schemas/observed_properties.py +0 -43
  55. hydroserverpy/core/schemas/processing_levels.py +0 -31
  56. hydroserverpy/core/schemas/result_qualifiers.py +0 -26
  57. hydroserverpy/core/schemas/sensors.py +0 -68
  58. hydroserverpy/core/schemas/things.py +0 -346
  59. hydroserverpy/core/schemas/units.py +0 -29
  60. hydroserverpy/core/service.py +0 -200
  61. hydroserverpy-0.4.0.dist-info/RECORD +0 -51
  62. /hydroserverpy/{core → api}/__init__.py +0 -0
  63. {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b1.dist-info/licenses}/LICENSE +0 -0
  64. {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b1.dist-info}/top_level.txt +0 -0
  65. {hydroserverpy-0.4.0.dist-info → hydroserverpy-0.5.0b1.dist-info}/zip-safe +0 -0
@@ -0,0 +1,336 @@
1
+ from typing import Union, Optional, Literal, TYPE_CHECKING
2
+ from pydantic import BaseModel, Field, AliasChoices, AliasPath, field_validator
3
+ from pandas import DataFrame
4
+ from uuid import UUID
5
+ from datetime import datetime
6
+ from ..base import HydroServerModel
7
+
8
+ if TYPE_CHECKING:
9
+ from hydroserverpy import HydroServer
10
+ from hydroserverpy.api.models import (
11
+ Workspace,
12
+ Thing,
13
+ Sensor,
14
+ ObservedProperty,
15
+ Unit,
16
+ ProcessingLevel,
17
+ )
18
+
19
+
20
+ class DatastreamFields(BaseModel):
21
+ name: str = Field(..., max_length=255)
22
+ description: str
23
+ observation_type: str = Field(..., max_length=255)
24
+ sampled_medium: str = Field(
25
+ ...,
26
+ max_length=255,
27
+ validation_alias=AliasChoices(
28
+ "sampledMedium", AliasPath("properties", "sampledMedium")
29
+ ),
30
+ )
31
+ no_data_value: float = Field(
32
+ ...,
33
+ validation_alias=AliasChoices(
34
+ "noDataValue", AliasPath("properties", "noDataValue")
35
+ ),
36
+ )
37
+ aggregation_statistic: str = Field(
38
+ ...,
39
+ max_length=255,
40
+ validation_alias=AliasChoices(
41
+ "aggregationStatistic", AliasPath("properties", "aggregationStatistic")
42
+ ),
43
+ )
44
+ time_aggregation_interval: float = Field(
45
+ ...,
46
+ validation_alias=AliasChoices(
47
+ "timeAggregationInterval",
48
+ AliasPath("properties", "timeAggregationInterval"),
49
+ ),
50
+ )
51
+ status: Optional[str] = Field(
52
+ None,
53
+ max_length=255,
54
+ validation_alias=AliasChoices("status", AliasPath("properties", "status")),
55
+ )
56
+ result_type: str = Field(
57
+ ...,
58
+ max_length=255,
59
+ validation_alias=AliasChoices(
60
+ "resultType", AliasPath("properties", "resultType")
61
+ ),
62
+ )
63
+ value_count: Optional[int] = Field(
64
+ None,
65
+ ge=0,
66
+ validation_alias=AliasChoices(
67
+ "valueCount", AliasPath("properties", "valueCount")
68
+ ),
69
+ )
70
+ phenomenon_begin_time: Optional[datetime] = Field(
71
+ None, validation_alias=AliasChoices("phenomenonBeginTime", "phenomenonTime")
72
+ )
73
+ phenomenon_end_time: Optional[datetime] = Field(
74
+ None, validation_alias=AliasChoices("phenomenonEndTime", "phenomenonTime")
75
+ )
76
+ result_begin_time: Optional[datetime] = Field(
77
+ None, validation_alias=AliasChoices("resultBeginTime", "resultTime")
78
+ )
79
+ result_end_time: Optional[datetime] = Field(
80
+ None, validation_alias=AliasChoices("resultEndTime", "resultTime")
81
+ )
82
+ is_private: bool = Field(
83
+ False,
84
+ validation_alias=AliasChoices(
85
+ "isPrivate", AliasPath("properties", "isPrivate")
86
+ ),
87
+ )
88
+ is_visible: bool = Field(
89
+ True,
90
+ validation_alias=AliasChoices(
91
+ "isVisible", AliasPath("properties", "isVisible")
92
+ ),
93
+ )
94
+ time_aggregation_interval_unit: Literal["seconds", "minutes", "hours", "days"] = (
95
+ Field(
96
+ ...,
97
+ validation_alias=AliasChoices(
98
+ "timeAggregationIntervalUnit",
99
+ AliasPath("properties", "timeAggregationIntervalUnitOfMeasurement"),
100
+ ),
101
+ )
102
+ )
103
+ intended_time_spacing: Optional[float] = Field(
104
+ None,
105
+ validation_alias=AliasChoices(
106
+ "intendedTimeSpacing", AliasPath("properties", "intendedTimeSpacing")
107
+ ),
108
+ )
109
+ intended_time_spacing_unit: Optional[
110
+ Literal["seconds", "minutes", "hours", "days"]
111
+ ] = Field(
112
+ None,
113
+ validation_alias=AliasChoices(
114
+ "intendedTimeSpacingUnit",
115
+ AliasPath("properties", "intendedTimeSpacingUnit"),
116
+ ),
117
+ )
118
+
119
+ @field_validator(
120
+ "phenomenon_begin_time",
121
+ "phenomenon_end_time",
122
+ "result_begin_time",
123
+ "result_end_time",
124
+ mode="before",
125
+ )
126
+ def split_time(cls, value: str, info) -> str:
127
+ if isinstance(value, str):
128
+ parts = value.split("/")
129
+ return parts[0] if "begin" in info.field_name else parts[-1]
130
+ return value
131
+
132
+
133
+ class Datastream(HydroServerModel, DatastreamFields):
134
+ def __init__(
135
+ self,
136
+ _connection: "HydroServer",
137
+ _uid: Union[UUID, str],
138
+ **data,
139
+ ):
140
+ super().__init__(
141
+ _connection=_connection, _model_ref="datastreams", _uid=_uid, **data
142
+ )
143
+
144
+ self._workspace_id = str(
145
+ data.get("workspace_id")
146
+ or data.get("workspaceId")
147
+ or data["properties"]["workspace"]["id"]
148
+ )
149
+ self._processing_level_id = str(
150
+ data.get("workspace_id")
151
+ or data.get("workspaceId")
152
+ or data["properties"]["processingLevelId"]
153
+ )
154
+ self._unit_id = str(
155
+ data.get("unit_id") or data.get("unitId") or data["properties"]["unitId"]
156
+ )
157
+
158
+ self._workspace = None
159
+ self._thing = None
160
+ self._observed_property = None
161
+ self._unit = None
162
+ self._processing_level = None
163
+ self._sensor = None
164
+
165
+ @property
166
+ def workspace(self) -> "Workspace":
167
+ """The workspace this datastream belongs to."""
168
+
169
+ if self._workspace is None:
170
+ self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
171
+
172
+ return self._workspace
173
+
174
+ @property
175
+ def thing(self) -> "Thing":
176
+ """The thing this datastream belongs to."""
177
+
178
+ if self._thing is None:
179
+ self._thing = self._connection.things.get(
180
+ uid=self.uid,
181
+ fetch_by_datastream_uid=True,
182
+ )
183
+ self._original_data["thing"] = self._thing
184
+
185
+ return self._thing
186
+
187
+ @thing.setter
188
+ def thing(self, thing: Union["Thing", UUID, str]):
189
+ if not thing:
190
+ raise ValueError("Thing of datastream cannot be None.")
191
+ if str(getattr(thing, "uid", thing)) != str(self.thing.uid):
192
+ self._thing = self._connection.things.get(
193
+ uid=str(getattr(thing, "uid", thing))
194
+ )
195
+
196
+ @property
197
+ def sensor(self) -> "Sensor":
198
+ """The sensor this datastream uses."""
199
+
200
+ if self._sensor is None:
201
+ self._sensor = self._connection.sensors.get(
202
+ uid=self.uid,
203
+ fetch_by_datastream_uid=True,
204
+ )
205
+ self._original_data["sensor"] = self._sensor
206
+
207
+ return self._sensor
208
+
209
+ @sensor.setter
210
+ def sensor(self, sensor: Union["Sensor", UUID, str]):
211
+ if not sensor:
212
+ raise ValueError("Sensor of datastream cannot be None.")
213
+ if str(getattr(sensor, "uid", sensor)) != str(self.sensor.uid):
214
+ self._sensor = self._connection.sensors.get(
215
+ uid=str(getattr(sensor, "uid", sensor))
216
+ )
217
+
218
+ @property
219
+ def observed_property(self) -> "Thing":
220
+ """The observed property of this datastream."""
221
+
222
+ if self._observed_property is None:
223
+ self._observed_property = self._connection.observedproperties.get(
224
+ uid=self.uid,
225
+ fetch_by_datastream_uid=True,
226
+ )
227
+ self._original_data["observed_property"] = self._observed_property
228
+
229
+ return self._observed_property
230
+
231
+ @observed_property.setter
232
+ def observed_property(
233
+ self, observed_property: Union["ObservedProperty", UUID, str]
234
+ ):
235
+ if not observed_property:
236
+ raise ValueError("Observed property of datastream cannot be None.")
237
+ if str(getattr(observed_property, "uid", observed_property)) != str(
238
+ self.observed_property.uid
239
+ ):
240
+ self._observed_property = self._connection.observedproperties.get(
241
+ uid=str(getattr(observed_property, "uid", observed_property))
242
+ )
243
+
244
+ @property
245
+ def unit(self) -> "Unit":
246
+ """The unit this datastream uses."""
247
+
248
+ if self._unit is None:
249
+ self._unit = self._connection.units.get(uid=self._unit_id)
250
+ self._original_data["unit"] = self._unit
251
+
252
+ return self._unit
253
+
254
+ @unit.setter
255
+ def unit(self, unit: Union["Unit", UUID, str]):
256
+ if not unit:
257
+ raise ValueError("Unit of datastream cannot be None.")
258
+ if str(getattr(unit, "uid", unit)) != str(self.unit.uid):
259
+ self._unit = self._connection.units.get(uid=str(getattr(unit, "uid", unit)))
260
+
261
+ @property
262
+ def processing_level(self) -> "Thing":
263
+ """The processing level of this datastream."""
264
+
265
+ if self._processing_level is None:
266
+ self._processing_level = self._connection.processinglevels.get(uid=self._processing_level_id)
267
+ self._original_data["processing_level"] = self._processing_level
268
+
269
+ return self._processing_level
270
+
271
+ @processing_level.setter
272
+ def processing_level(self, processing_level: Union["ProcessingLevel", UUID, str]):
273
+ if not processing_level:
274
+ raise ValueError("Processing level of datastream cannot be None.")
275
+ if str(getattr(processing_level, "uid", processing_level)) != str(
276
+ self.processing_level.uid
277
+ ):
278
+ self._processing_level = self._connection.processinglevels.get(
279
+ uid=str(getattr(processing_level, "uid", processing_level))
280
+ )
281
+
282
+ def refresh(self):
283
+ """Refresh this datastream from HydroServer."""
284
+
285
+ self._workspace = None
286
+ self._workspace_id = None
287
+ self._thing = None
288
+ self._observed_property = None
289
+ self._unit = None
290
+ self._unit_id = None
291
+ self._processing_level = None
292
+ self._processing_level_id = None
293
+ self._sensor = None
294
+ super()._refresh()
295
+
296
+ def save(self):
297
+ """Save changes to this datastream to HydroServer."""
298
+
299
+ super()._save()
300
+
301
+ def delete(self):
302
+ """Delete this datastream from HydroServer."""
303
+
304
+ super()._delete()
305
+
306
+ def get_observations(
307
+ self,
308
+ start_time: datetime = None,
309
+ end_time: datetime = None,
310
+ page: int = 1,
311
+ page_size: int = 100000,
312
+ include_quality: bool = False,
313
+ fetch_all: bool = False,
314
+ ) -> DataFrame:
315
+ """Retrieve the observations for this datastream."""
316
+
317
+ return self._connection.datastreams.get_observations(
318
+ uid=self.uid,
319
+ start_time=start_time,
320
+ end_time=end_time,
321
+ page=page,
322
+ page_size=page_size,
323
+ include_quality=include_quality,
324
+ fetch_all=fetch_all,
325
+ )
326
+
327
+ def load_observations(
328
+ self,
329
+ observations: DataFrame,
330
+ ) -> None:
331
+ """Load a DataFrame of observations to the datastream."""
332
+
333
+ return self._connection.datastreams.load_observations(
334
+ uid=self.uid,
335
+ observations=observations,
336
+ )
@@ -0,0 +1,72 @@
1
+ from typing import Union, TYPE_CHECKING
2
+ from uuid import UUID
3
+ from pydantic import BaseModel, Field, AliasChoices, AliasPath
4
+ from ..base import HydroServerModel
5
+
6
+ if TYPE_CHECKING:
7
+ from hydroserverpy import HydroServer
8
+ from hydroserverpy.api.models import Workspace
9
+
10
+
11
+ class ObservedPropertyFields(BaseModel):
12
+ name: str = Field(..., max_length=255)
13
+ definition: str
14
+ description: str
15
+ observed_property_type: str = Field(
16
+ ...,
17
+ max_length=255,
18
+ serialization_alias="type",
19
+ validation_alias=AliasChoices("type", AliasPath("properties", "variableType")),
20
+ )
21
+ code: str = Field(
22
+ ...,
23
+ max_length=255,
24
+ validation_alias=AliasChoices("code", AliasPath("properties", "variableCode")),
25
+ )
26
+
27
+
28
+ class ObservedProperty(HydroServerModel, ObservedPropertyFields):
29
+ def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
30
+ super().__init__(
31
+ _connection=_connection, _model_ref="observedproperties", _uid=_uid, **data
32
+ )
33
+
34
+ self._workspace_id = (
35
+ data.get("workspace_id")
36
+ or data.get("workspaceId")
37
+ or (
38
+ None
39
+ if data.get("properties", {}).get("workspace") is None
40
+ else data.get("properties", {}).get("workspace", {}).get("id")
41
+ )
42
+ )
43
+ self._workspace_id = (
44
+ str(self._workspace_id) if self._workspace_id is not None else None
45
+ )
46
+
47
+ self._workspace = None
48
+
49
+ @property
50
+ def workspace(self) -> "Workspace":
51
+ """The workspace this observed property belongs to."""
52
+
53
+ if self._workspace is None and self._workspace_id:
54
+ self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
55
+
56
+ return self._workspace
57
+
58
+ def refresh(self):
59
+ """Refresh this observed property from HydroServer."""
60
+
61
+ super()._refresh()
62
+ self._workspace = None
63
+
64
+ def save(self):
65
+ """Save changes to this observed property to HydroServer."""
66
+
67
+ super()._save()
68
+
69
+ def delete(self):
70
+ """Delete this observed property from HydroServer."""
71
+
72
+ super()._delete()
@@ -0,0 +1,50 @@
1
+ from typing import Union, Optional, 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
9
+
10
+
11
+ class ProcessingLevelFields(BaseModel):
12
+ code: str = Field(..., max_length=255)
13
+ definition: Optional[str] = None
14
+ explanation: Optional[str] = None
15
+
16
+
17
+ class ProcessingLevel(HydroServerModel, ProcessingLevelFields):
18
+ def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
19
+ super().__init__(
20
+ _connection=_connection, _model_ref="processinglevels", _uid=_uid, **data
21
+ )
22
+
23
+ self._workspace_id = str(data.get("workspace_id") or data["workspaceId"])
24
+
25
+ self._workspace = None
26
+
27
+ @property
28
+ def workspace(self) -> "Workspace":
29
+ """The workspace this processing level belongs to."""
30
+
31
+ if self._workspace is None and self._workspace_id:
32
+ self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
33
+
34
+ return self._workspace
35
+
36
+ def refresh(self):
37
+ """Refresh this processing level from HydroServer."""
38
+
39
+ super()._refresh()
40
+ self._workspace = None
41
+
42
+ def save(self):
43
+ """Save changes to this processing level to HydroServer."""
44
+
45
+ super()._save()
46
+
47
+ def delete(self):
48
+ """Delete this processing level from HydroServer."""
49
+
50
+ super()._delete()
@@ -0,0 +1,49 @@
1
+ from typing import Union, Optional, 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
9
+
10
+
11
+ class ResultQualifierFields(BaseModel):
12
+ code: str = Field(..., max_length=255)
13
+ description: str
14
+
15
+
16
+ class ResultQualifier(HydroServerModel, ResultQualifierFields):
17
+ def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
18
+ super().__init__(
19
+ _connection=_connection, _model_ref="resultqualifiers", _uid=_uid, **data
20
+ )
21
+
22
+ self._workspace_id = str(data.get("workspace_id") or data["workspaceId"])
23
+
24
+ self._workspace = None
25
+
26
+ @property
27
+ def workspace(self) -> "Workspace":
28
+ """The workspace this result qualifier belongs to."""
29
+
30
+ if self._workspace is None and self._workspace_id:
31
+ self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
32
+
33
+ return self._workspace
34
+
35
+ def refresh(self):
36
+ """Refresh this result qualifier from HydroServer."""
37
+
38
+ super()._refresh()
39
+ self._workspace = None
40
+
41
+ def save(self):
42
+ """Save changes to this result qualifier to HydroServer."""
43
+
44
+ super()._save()
45
+
46
+ def delete(self):
47
+ """Delete this result qualifier from HydroServer."""
48
+
49
+ super()._delete()
@@ -0,0 +1,105 @@
1
+ from typing import Union, Optional, TYPE_CHECKING
2
+ from uuid import UUID
3
+ from pydantic import BaseModel, Field, AliasChoices, AliasPath
4
+ from ..base import HydroServerModel
5
+
6
+ if TYPE_CHECKING:
7
+ from hydroserverpy import HydroServer
8
+ from hydroserverpy.api.models import Workspace
9
+
10
+
11
+ class SensorFields(BaseModel):
12
+ name: str = Field(..., max_length=255)
13
+ description: str
14
+ encoding_type: str = Field(..., max_length=255)
15
+ manufacturer: Optional[str] = Field(
16
+ None,
17
+ max_length=255,
18
+ validation_alias=AliasChoices(
19
+ "manufacturer", AliasPath("metadata", "sensorModel", "sensorManufacturer")
20
+ ),
21
+ )
22
+ sensor_model: Optional[str] = Field(
23
+ None,
24
+ max_length=255,
25
+ alias="model",
26
+ validation_alias=AliasChoices(
27
+ "model", AliasPath("metadata", "sensorModel", "sensorModelName")
28
+ ),
29
+ )
30
+ sensor_model_link: Optional[str] = Field(
31
+ None,
32
+ max_length=500,
33
+ alias="modelLink",
34
+ validation_alias=AliasChoices(
35
+ "modelLink", AliasPath("metadata", "sensorModel", "sensorModelUrl")
36
+ ),
37
+ )
38
+ method_type: str = Field(
39
+ ...,
40
+ max_length=100,
41
+ validation_alias=AliasChoices(
42
+ "methodType", AliasPath("metadata", "methodType")
43
+ ),
44
+ )
45
+ method_link: Optional[str] = Field(
46
+ None,
47
+ max_length=500,
48
+ validation_alias=AliasChoices(
49
+ "methodLink", AliasPath("metadata", "methodLink")
50
+ ),
51
+ )
52
+ method_code: Optional[str] = Field(
53
+ None,
54
+ max_length=50,
55
+ validation_alias=AliasChoices(
56
+ "methodCode", AliasPath("metadata", "methodCode")
57
+ ),
58
+ )
59
+
60
+
61
+ class Sensor(HydroServerModel, SensorFields):
62
+ def __init__(self, _connection: "HydroServer", _uid: Union[UUID, str], **data):
63
+ super().__init__(
64
+ _connection=_connection, _model_ref="sensors", _uid=_uid, **data
65
+ )
66
+
67
+ self._workspace_id = (
68
+ data.get("workspace_id")
69
+ or data.get("workspaceId")
70
+ or (
71
+ None
72
+ if data.get("properties", {}).get("workspace") is None
73
+ else data.get("properties", {}).get("workspace", {}).get("id")
74
+ )
75
+ )
76
+ self._workspace_id = (
77
+ str(self._workspace_id) if self._workspace_id is not None else None
78
+ )
79
+
80
+ self._workspace = None
81
+
82
+ @property
83
+ def workspace(self) -> "Workspace":
84
+ """The workspace this sensor belongs to."""
85
+
86
+ if self._workspace is None and self._workspace_id:
87
+ self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
88
+
89
+ return self._workspace
90
+
91
+ def refresh(self):
92
+ """Refresh this sensor from HydroServer."""
93
+
94
+ super()._refresh()
95
+ self._workspace = None
96
+
97
+ def save(self):
98
+ """Save changes to this sensor to HydroServer."""
99
+
100
+ super()._save()
101
+
102
+ def delete(self):
103
+ """Delete this sensor from HydroServer."""
104
+
105
+ super()._delete()