hydroserverpy 1.2.1__py3-none-any.whl → 1.3.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 (47) hide show
  1. hydroserverpy/__init__.py +1 -1
  2. hydroserverpy/api/{main.py → client.py} +52 -22
  3. hydroserverpy/api/models/__init__.py +1 -2
  4. hydroserverpy/api/models/base.py +180 -47
  5. hydroserverpy/api/models/etl/data_archive.py +31 -59
  6. hydroserverpy/api/models/etl/data_source.py +34 -76
  7. hydroserverpy/api/models/etl/orchestration_system.py +21 -36
  8. hydroserverpy/api/models/iam/apikey.py +57 -38
  9. hydroserverpy/api/models/iam/collaborator.py +55 -19
  10. hydroserverpy/api/models/iam/role.py +32 -4
  11. hydroserverpy/api/models/iam/workspace.py +58 -86
  12. hydroserverpy/api/models/sta/datastream.py +122 -214
  13. hydroserverpy/api/models/sta/observation.py +101 -0
  14. hydroserverpy/api/models/sta/observed_property.py +18 -53
  15. hydroserverpy/api/models/sta/processing_level.py +16 -31
  16. hydroserverpy/api/models/sta/result_qualifier.py +16 -31
  17. hydroserverpy/api/models/sta/sensor.py +27 -88
  18. hydroserverpy/api/models/sta/thing.py +48 -152
  19. hydroserverpy/api/models/sta/unit.py +16 -29
  20. hydroserverpy/api/services/__init__.py +1 -0
  21. hydroserverpy/api/services/base.py +92 -76
  22. hydroserverpy/api/services/etl/data_archive.py +42 -72
  23. hydroserverpy/api/services/etl/data_source.py +42 -72
  24. hydroserverpy/api/services/etl/orchestration_system.py +25 -33
  25. hydroserverpy/api/services/iam/role.py +38 -0
  26. hydroserverpy/api/services/iam/workspace.py +96 -99
  27. hydroserverpy/api/services/sta/datastream.py +151 -210
  28. hydroserverpy/api/services/sta/observed_property.py +31 -49
  29. hydroserverpy/api/services/sta/processing_level.py +30 -36
  30. hydroserverpy/api/services/sta/result_qualifier.py +24 -34
  31. hydroserverpy/api/services/sta/sensor.py +34 -48
  32. hydroserverpy/api/services/sta/thing.py +96 -89
  33. hydroserverpy/api/services/sta/unit.py +30 -34
  34. hydroserverpy/api/utils.py +22 -0
  35. hydroserverpy/etl/extractors/base.py +2 -4
  36. hydroserverpy/etl/loaders/hydroserver_loader.py +1 -0
  37. hydroserverpy/etl/timestamp_parser.py +82 -48
  38. hydroserverpy/etl/transformers/base.py +5 -9
  39. hydroserverpy/etl_csv/hydroserver_etl_csv.py +1 -1
  40. {hydroserverpy-1.2.1.dist-info → hydroserverpy-1.3.0b1.dist-info}/METADATA +1 -1
  41. hydroserverpy-1.3.0b1.dist-info/RECORD +70 -0
  42. hydroserverpy/api/http.py +0 -22
  43. hydroserverpy-1.2.1.dist-info/RECORD +0 -68
  44. {hydroserverpy-1.2.1.dist-info → hydroserverpy-1.3.0b1.dist-info}/WHEEL +0 -0
  45. {hydroserverpy-1.2.1.dist-info → hydroserverpy-1.3.0b1.dist-info}/licenses/LICENSE +0 -0
  46. {hydroserverpy-1.2.1.dist-info → hydroserverpy-1.3.0b1.dist-info}/top_level.txt +0 -0
  47. {hydroserverpy-1.2.1.dist-info → hydroserverpy-1.3.0b1.dist-info}/zip-safe +0 -0
@@ -1,55 +1,52 @@
1
1
  from typing import Optional, Union, List, TYPE_CHECKING
2
2
  from uuid import UUID
3
- from ..base import EndpointService
4
3
  from hydroserverpy.api.models import ResultQualifier
5
-
4
+ from hydroserverpy.api.utils import normalize_uuid
5
+ from ..base import HydroServerBaseService
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from hydroserverpy import HydroServer
9
9
  from hydroserverpy.api.models import Workspace
10
10
 
11
11
 
12
- class ResultQualifierService(EndpointService):
13
- def __init__(self, connection: "HydroServer"):
14
- self._model = ResultQualifier
15
- self._api_route = "api/data"
16
- self._endpoint_route = "result-qualifiers"
17
-
18
- super().__init__(connection)
12
+ class ResultQualifierService(HydroServerBaseService):
13
+ def __init__(self, client: "HydroServer"):
14
+ self.model = ResultQualifier
15
+ super().__init__(client)
19
16
 
20
17
  def list(
21
18
  self,
22
- workspace: Optional[Union["Workspace", UUID, str]] = None,
19
+ page: int = ...,
20
+ page_size: int = ...,
21
+ order_by: List[str] = ...,
22
+ workspace: Optional[Union["Workspace", UUID, str]] = ...,
23
+ fetch_all: bool = False,
23
24
  ) -> List["ResultQualifier"]:
24
25
  """Fetch a collection of result qualifiers."""
25
26
 
26
- workspace_id = getattr(workspace, "uid", workspace)
27
- workspace_id = str(workspace_id) if workspace_id else None
28
-
29
- return super()._list(
30
- params={"workspace_id": workspace_id} if workspace_id else {},
27
+ return super().list(
28
+ page=page,
29
+ page_size=page_size,
30
+ order_by=order_by,
31
+ workspace_id=normalize_uuid(workspace),
32
+ fetch_all=fetch_all,
31
33
  )
32
34
 
33
- def get(self, uid: Union[UUID, str]) -> "ResultQualifier":
34
- """Get a result qualifier by ID."""
35
-
36
- return super()._get(uid=str(uid))
37
-
38
35
  def create(
39
36
  self,
40
- workspace: Union["Workspace", UUID, str],
41
37
  code: str,
42
- description: str,
38
+ description: Optional[str] = None,
39
+ workspace: Optional[Union["Workspace", UUID, str]] = None,
43
40
  ) -> "ResultQualifier":
44
41
  """Create a new result qualifier."""
45
42
 
46
- kwargs = {
43
+ body = {
47
44
  "code": code,
48
45
  "description": description,
49
- "workspaceId": str(getattr(workspace, "uid", workspace)),
46
+ "workspaceId": normalize_uuid(workspace),
50
47
  }
51
48
 
52
- return super()._create(**kwargs)
49
+ return super().create(**body)
53
50
 
54
51
  def update(
55
52
  self,
@@ -59,16 +56,9 @@ class ResultQualifierService(EndpointService):
59
56
  ) -> "ResultQualifier":
60
57
  """Update a result qualifier."""
61
58
 
62
- kwargs = {
59
+ body = {
63
60
  "code": code,
64
61
  "description": description,
65
62
  }
66
63
 
67
- return super()._update(
68
- uid=str(uid), **{k: v for k, v in kwargs.items() if v is not ...}
69
- )
70
-
71
- def delete(self, uid: Union[UUID, str]) -> None:
72
- """Delete a result qualifier."""
73
-
74
- super()._delete(uid=str(uid))
64
+ return super().update(uid=str(uid), **body)
@@ -1,57 +1,49 @@
1
1
  from typing import Optional, Union, List, TYPE_CHECKING
2
2
  from uuid import UUID
3
- from ..base import SensorThingsService
4
3
  from hydroserverpy.api.models import Sensor
5
-
4
+ from hydroserverpy.api.utils import normalize_uuid
5
+ from ..base import HydroServerBaseService
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from hydroserverpy import HydroServer
9
- from hydroserverpy.api.models import Workspace
10
-
9
+ from hydroserverpy.api.models import Workspace, Thing, Datastream
11
10
 
12
- class SensorService(SensorThingsService):
13
- def __init__(self, connection: "HydroServer"):
14
- self._model = Sensor
15
- self._api_route = "api/data"
16
- self._endpoint_route = "sensors"
17
- self._sta_route = "api/sensorthings/v1.1/Sensors"
18
11
 
19
- super().__init__(connection)
12
+ class SensorService(HydroServerBaseService):
13
+ def __init__(self, client: "HydroServer"):
14
+ self.model = Sensor
15
+ super().__init__(client)
20
16
 
21
17
  def list(
22
18
  self,
23
- workspace: Optional[Union["Workspace", UUID, str]] = None,
24
- page: int = 1,
25
- page_size: int = 100,
19
+ page: int = ...,
20
+ page_size: int = ...,
21
+ order_by: List[str] = ...,
22
+ workspace: Optional[Union["Workspace", UUID, str]] = ...,
23
+ thing: Optional[Union["Thing", UUID, str]] = ...,
24
+ datastream: Optional[Union["Datastream", UUID, str]] = ...,
25
+ encoding_type: str = ...,
26
+ manufacturer: Optional[str] = ...,
27
+ method_type: str = ...,
28
+ fetch_all: bool = False,
26
29
  ) -> List["Sensor"]:
27
30
  """Fetch a collection of sensors."""
28
31
 
29
- params = {"$top": page_size, "$skip": page_size * (page - 1)}
30
-
31
- if workspace:
32
- params["$filter"] = (
33
- f"properties/workspace/id eq '{str(getattr(workspace, 'uid', workspace))}'"
34
- )
35
-
36
- return super()._list(params=params)
37
-
38
- def get(
39
- self, uid: Union[UUID, str], fetch_by_datastream_uid: bool = False
40
- ) -> "Sensor":
41
- """Get a sensor by ID."""
42
-
43
- return self._get(
44
- uid=str(uid),
45
- path=(
46
- f"api/sensorthings/v1.1/Datastreams('{str(uid)}')/Sensor"
47
- if fetch_by_datastream_uid
48
- else None
49
- ),
32
+ return super().list(
33
+ page=page,
34
+ page_size=page_size,
35
+ order_by=order_by,
36
+ workspace_id=normalize_uuid(workspace),
37
+ thing_id=normalize_uuid(thing),
38
+ datastream_id=normalize_uuid(datastream),
39
+ encoding_type=encoding_type,
40
+ manufacturer=manufacturer,
41
+ method_type=method_type,
42
+ fetch_all=fetch_all,
50
43
  )
51
44
 
52
45
  def create(
53
46
  self,
54
- workspace: Union["Workspace", UUID, str],
55
47
  name: str,
56
48
  description: str,
57
49
  encoding_type: str,
@@ -61,10 +53,11 @@ class SensorService(SensorThingsService):
61
53
  sensor_model_link: Optional[str] = None,
62
54
  method_link: Optional[str] = None,
63
55
  method_code: Optional[str] = None,
56
+ workspace: Optional[Union["Workspace", UUID, str]] = None,
64
57
  ) -> "Sensor":
65
58
  """Create a new sensor."""
66
59
 
67
- kwargs = {
60
+ body = {
68
61
  "name": name,
69
62
  "description": description,
70
63
  "encodingType": encoding_type,
@@ -74,10 +67,10 @@ class SensorService(SensorThingsService):
74
67
  "modelLink": sensor_model_link,
75
68
  "methodLink": method_link,
76
69
  "methodCode": method_code,
77
- "workspaceId": str(getattr(workspace, "uid", workspace)),
70
+ "workspaceId": normalize_uuid(workspace),
78
71
  }
79
72
 
80
- return super()._create(**kwargs)
73
+ return super().create(**body)
81
74
 
82
75
  def update(
83
76
  self,
@@ -94,7 +87,7 @@ class SensorService(SensorThingsService):
94
87
  ) -> "Sensor":
95
88
  """Update a sensor."""
96
89
 
97
- kwargs = {
90
+ body = {
98
91
  "name": name,
99
92
  "description": description,
100
93
  "encodingType": encoding_type,
@@ -106,11 +99,4 @@ class SensorService(SensorThingsService):
106
99
  "methodCode": method_code,
107
100
  }
108
101
 
109
- return super()._update(
110
- uid=str(uid), **{k: v for k, v in kwargs.items() if v is not ...}
111
- )
112
-
113
- def delete(self, uid: Union[UUID, str]) -> None:
114
- """Delete a sensor."""
115
-
116
- super()._delete(uid=str(uid))
102
+ return super().update(uid=str(uid), **body)
@@ -1,59 +1,54 @@
1
1
  import json
2
- from typing import TYPE_CHECKING, Union, IO, List, Dict, Optional
2
+ from typing import TYPE_CHECKING, Union, IO, List, Dict, Optional, Tuple
3
3
  from uuid import UUID
4
- from ..base import SensorThingsService
5
4
  from hydroserverpy.api.models import Thing
6
-
5
+ from hydroserverpy.api.utils import normalize_uuid
6
+ from ..base import HydroServerBaseService
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from hydroserverpy import HydroServer
10
10
  from hydroserverpy.api.models import Workspace
11
11
 
12
12
 
13
- class ThingService(SensorThingsService):
14
- def __init__(self, connection: "HydroServer"):
15
- self._model = Thing
16
- self._api_route = "api/data"
17
- self._endpoint_route = "things"
18
- self._sta_route = "api/sensorthings/v1.1/Things"
19
-
20
- super().__init__(connection)
13
+ class ThingService(HydroServerBaseService):
14
+ def __init__(self, client: "HydroServer"):
15
+ self.model = Thing
16
+ super().__init__(client)
21
17
 
22
18
  def list(
23
19
  self,
24
- workspace: Optional[Union["Workspace", UUID, str]] = None,
25
- page: int = 1,
26
- page_size: int = 100,
20
+ page: int = ...,
21
+ page_size: int = ...,
22
+ order_by: List[str] = ...,
23
+ workspace: Union["Workspace", UUID, str] = ...,
24
+ bbox: Tuple[float, float, float, float] = ...,
25
+ state: str = ...,
26
+ county: str = ...,
27
+ country: str = ...,
28
+ site_type: str = ...,
29
+ sampling_feature_type: str = ...,
30
+ sampling_feature_code: str = ...,
31
+ tag: Tuple[str, str] = ...,
32
+ is_private: bool = ...,
33
+ fetch_all: bool = False,
27
34
  ) -> List["Thing"]:
28
35
  """Fetch a collection of things."""
29
36
 
30
- params = {
31
- "$top": page_size,
32
- "$skip": page_size * (page - 1),
33
- "$expand": "Locations",
34
- }
35
-
36
- if workspace:
37
- params["$filter"] = (
38
- f"properties/workspace/id eq '{str(getattr(workspace, 'uid', workspace))}'"
39
- )
40
-
41
- return super()._list(params=params)
42
-
43
- def get(
44
- self, uid: Union[UUID, str], fetch_by_datastream_uid: bool = False
45
- ) -> "Thing":
46
- """Get a thing by ID."""
47
-
48
- params = {"$expand": "Locations"}
49
- return self._get(
50
- uid=str(uid),
51
- path=(
52
- f"api/sensorthings/v1.1/Datastreams('{str(uid)}')/Thing"
53
- if fetch_by_datastream_uid
54
- else None
55
- ),
56
- params=params,
37
+ return super().list(
38
+ page=page,
39
+ page_size=page_size,
40
+ order_by=order_by,
41
+ workspace_id=normalize_uuid(workspace),
42
+ bbox=",".join([str(i) for i in bbox]) if bbox is not ... else bbox,
43
+ state=state,
44
+ county=county,
45
+ country=country,
46
+ site_type=site_type,
47
+ sampling_feature_type=sampling_feature_type,
48
+ sampling_feature_code=sampling_feature_code,
49
+ tag=[f"{tag[0]}:{tag[1]}"] if tag is not ... else tag,
50
+ is_private=is_private,
51
+ fetch_all=fetch_all,
57
52
  )
58
53
 
59
54
  def create(
@@ -76,25 +71,27 @@ class ThingService(SensorThingsService):
76
71
  ) -> "Thing":
77
72
  """Create a new thing."""
78
73
 
79
- kwargs = {
74
+ body = {
80
75
  "name": name,
81
76
  "description": description,
82
77
  "samplingFeatureType": sampling_feature_type,
83
78
  "samplingFeatureCode": sampling_feature_code,
84
79
  "siteType": site_type,
85
80
  "isPrivate": is_private,
86
- "latitude": latitude,
87
- "longitude": longitude,
88
- "elevation_m": elevation_m,
89
- "elevationDatum": elevation_datum,
90
- "state": state,
91
- "county": county,
92
- "country": country,
93
81
  "dataDisclaimer": data_disclaimer,
94
- "workspaceId": str(getattr(workspace, "uid", workspace)),
82
+ "workspaceId": normalize_uuid(workspace),
83
+ "location": {
84
+ "latitude": latitude,
85
+ "longitude": longitude,
86
+ "elevation_m": elevation_m,
87
+ "elevationDatum": elevation_datum,
88
+ "state": state,
89
+ "county": county,
90
+ "country": country,
91
+ }
95
92
  }
96
93
 
97
- return super()._create(**kwargs)
94
+ return super().create(**body)
98
95
 
99
96
  def update(
100
97
  self,
@@ -104,7 +101,7 @@ class ThingService(SensorThingsService):
104
101
  sampling_feature_type: str = ...,
105
102
  sampling_feature_code: str = ...,
106
103
  site_type: str = ...,
107
- is_private: False = ...,
104
+ is_private: bool = ...,
108
105
  latitude: float = ...,
109
106
  longitude: float = ...,
110
107
  elevation_m: Optional[float] = ...,
@@ -116,73 +113,83 @@ class ThingService(SensorThingsService):
116
113
  ) -> "Thing":
117
114
  """Update a thing."""
118
115
 
119
- kwargs = {
116
+ body = {
120
117
  "name": name,
121
118
  "description": description,
122
119
  "samplingFeatureType": sampling_feature_type,
123
120
  "samplingFeatureCode": sampling_feature_code,
124
121
  "siteType": site_type,
125
122
  "isPrivate": is_private,
126
- "latitude": latitude,
127
- "longitude": longitude,
128
- "elevation_m": elevation_m,
129
- "elevationDatum": elevation_datum,
130
- "state": state,
131
- "county": county,
132
- "country": country,
133
123
  "dataDisclaimer": data_disclaimer,
124
+ "location": {
125
+ "latitude": latitude,
126
+ "longitude": longitude,
127
+ "elevation_m": elevation_m,
128
+ "elevationDatum": elevation_datum,
129
+ "state": state,
130
+ "county": county,
131
+ "country": country,
132
+ }
134
133
  }
135
134
 
136
- return super()._update(
137
- uid=str(uid), **{k: v for k, v in kwargs.items() if v is not ...}
138
- )
139
-
140
- def delete(self, uid: Union[UUID, str]) -> None:
141
- """Delete a thing."""
142
-
143
- super()._delete(uid=str(uid))
135
+ return super().update(uid=str(uid), **body)
144
136
 
145
137
  def add_tag(self, uid: Union[UUID, str], key: str, value: str) -> Dict[str, str]:
146
138
  """Tag a HydroServer thing."""
147
139
 
148
- return self._connection.request(
149
- "post",
150
- f"{self._api_route}/{self._endpoint_route}/{str(uid)}/tags",
151
- data=json.dumps({"key": key, "value": value}),
140
+ path = f"/{self.client.base_route}/{self.model.get_route()}/{str(uid)}/tags"
141
+ headers = {"Content-type": "application/json"}
142
+ body = {
143
+ "key": key,
144
+ "value": value
145
+ }
146
+ return self.client.request(
147
+ "post", path, headers=headers, data=json.dumps(body, default=self.default_serializer)
152
148
  ).json()
153
149
 
154
150
  def update_tag(self, uid: Union[UUID, str], key: str, value: str) -> Dict[str, str]:
155
151
  """Update the tag of a HydroServer thing."""
156
152
 
157
- return self._connection.request(
158
- "put",
159
- f"{self._api_route}/{self._endpoint_route}/{str(uid)}/tags",
160
- data=json.dumps({"key": key, "value": value}),
153
+ path = f"/{self.client.base_route}/{self.model.get_route()}/{str(uid)}/tags"
154
+ headers = {"Content-type": "application/json"}
155
+ body = {
156
+ "key": key,
157
+ "value": value
158
+ }
159
+ return self.client.request(
160
+ "put", path, headers=headers, data=json.dumps(body, default=self.default_serializer)
161
161
  ).json()
162
162
 
163
- def delete_tag(self, uid: Union[UUID, str], key: str) -> None:
163
+ def delete_tag(self, uid: Union[UUID, str], key: str, value: str) -> None:
164
164
  """Remove a tag from a HydroServer thing."""
165
165
 
166
- self._connection.request(
167
- "delete",
168
- f"{self._api_route}/{self._endpoint_route}/{str(uid)}/tags",
169
- data=json.dumps({"key": key}),
166
+ path = f"/{self.client.base_route}/{self.model.get_route()}/{str(uid)}/tags"
167
+ headers = {"Content-type": "application/json"}
168
+ body = {
169
+ "key": key,
170
+ "value": value
171
+ }
172
+ self.client.request(
173
+ "delete", path, headers=headers, data=json.dumps(body, default=self.default_serializer)
170
174
  )
171
175
 
172
176
  def add_photo(self, uid: Union[UUID, str], file: IO[bytes]) -> Dict[str, str]:
173
177
  """Add a photo of a HydroServer thing."""
174
178
 
175
- return self._connection.request(
176
- "post",
177
- f"{self._api_route}/{self._endpoint_route}/{str(uid)}/photos",
178
- files={"file": file},
179
+ path = f"/{self.client.base_route}/{self.model.get_route()}/{str(uid)}/photos"
180
+
181
+ return self.client.request(
182
+ "post", path, files={"file": file}
179
183
  ).json()
180
184
 
181
185
  def delete_photo(self, uid: Union[UUID, str], name: str) -> None:
182
186
  """Delete a photo of a HydroServer thing."""
183
187
 
184
- self._connection.request(
185
- "delete",
186
- f"{self._api_route}/{self._endpoint_route}/{str(uid)}/photos",
187
- data=json.dumps({"name": name}),
188
+ path = f"/{self.client.base_route}/{self.model.get_route()}/{str(uid)}/photos"
189
+ headers = {"Content-type": "application/json"}
190
+ body = {
191
+ "name": name
192
+ }
193
+ self.client.request(
194
+ "delete", path, headers=headers, data=json.dumps(body, default=self.default_serializer)
188
195
  )
@@ -1,59 +1,62 @@
1
1
  from typing import Optional, Union, List, TYPE_CHECKING
2
2
  from uuid import UUID
3
- from ..base import EndpointService
4
3
  from hydroserverpy.api.models import Unit
5
-
4
+ from hydroserverpy.api.utils import normalize_uuid
5
+ from ..base import HydroServerBaseService
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from hydroserverpy import HydroServer
9
- from hydroserverpy.api.models import Workspace
10
-
9
+ from hydroserverpy.api.models import Workspace, Thing, Datastream
11
10
 
12
- class UnitService(EndpointService):
13
- def __init__(self, connection: "HydroServer"):
14
- self._model = Unit
15
- self._api_route = "api/data"
16
- self._endpoint_route = "units"
17
11
 
18
- super().__init__(connection)
12
+ class UnitService(HydroServerBaseService):
13
+ def __init__(self, client: "HydroServer"):
14
+ self.model = Unit
15
+ super().__init__(client)
19
16
 
20
17
  def list(
21
18
  self,
22
- workspace: Optional[Union["Workspace", UUID, str]] = None,
19
+ page: int = ...,
20
+ page_size: int = ...,
21
+ order_by: List[str] = ...,
22
+ workspace: Union["Workspace", UUID, str] = ...,
23
+ thing: Optional[Union["Thing", UUID, str]] = ...,
24
+ datastream: Optional[Union["Datastream", UUID, str]] = ...,
25
+ unit_type: str = ...,
26
+ fetch_all: bool = False,
23
27
  ) -> List["Unit"]:
24
28
  """Fetch a collection of units."""
25
29
 
26
- workspace_id = getattr(workspace, "uid", workspace)
27
- workspace_id = str(workspace_id) if workspace_id else None
28
-
29
- return super()._list(
30
- params={"workspace_id": workspace_id} if workspace_id else {},
30
+ return super().list(
31
+ page=page,
32
+ page_size=page_size,
33
+ order_by=order_by,
34
+ workspace_id=normalize_uuid(workspace),
35
+ thing_id=normalize_uuid(thing),
36
+ datastream_id=normalize_uuid(datastream),
37
+ unit_type=unit_type,
38
+ fetch_all=fetch_all,
31
39
  )
32
40
 
33
- def get(self, uid: Union[UUID, str]) -> "Unit":
34
- """Get a unit by ID."""
35
-
36
- return super()._get(uid=str(uid))
37
-
38
41
  def create(
39
42
  self,
40
- workspace: Union["Workspace", UUID, str],
41
43
  name: str,
42
44
  symbol: str,
43
45
  definition: str,
44
46
  unit_type: str,
47
+ workspace: Optional[Union["Workspace", UUID, str]] = None,
45
48
  ) -> "Unit":
46
49
  """Create a new unit."""
47
50
 
48
- kwargs = {
51
+ body = {
49
52
  "name": name,
50
53
  "symbol": symbol,
51
54
  "definition": definition,
52
55
  "type": unit_type,
53
- "workspaceId": str(getattr(workspace, "uid", workspace)),
56
+ "workspaceId": normalize_uuid(workspace),
54
57
  }
55
58
 
56
- return super()._create(**kwargs)
59
+ return super().create(**body)
57
60
 
58
61
  def update(
59
62
  self,
@@ -65,18 +68,11 @@ class UnitService(EndpointService):
65
68
  ) -> "Unit":
66
69
  """Update a unit."""
67
70
 
68
- kwargs = {
71
+ body = {
69
72
  "name": name,
70
73
  "symbol": symbol,
71
74
  "definition": definition,
72
75
  "type": unit_type,
73
76
  }
74
77
 
75
- return super()._update(
76
- uid=str(uid), **{k: v for k, v in kwargs.items() if v is not ...}
77
- )
78
-
79
- def delete(self, uid: Union[UUID, str]) -> None:
80
- """Delete a unit."""
81
-
82
- super()._delete(uid=str(uid))
78
+ return super().update(uid=str(uid), **body)
@@ -0,0 +1,22 @@
1
+ from uuid import UUID
2
+ from typing import Union, Optional, Any
3
+ from pydantic.alias_generators import to_camel
4
+
5
+
6
+ def normalize_uuid(
7
+ obj: Optional[Union[str, UUID, Any]],
8
+ attr: str = "uid"
9
+ ):
10
+ if obj is ...:
11
+ return ...
12
+ if obj is None:
13
+ return None
14
+ if obj and hasattr(obj, attr):
15
+ return str(getattr(obj, attr))
16
+ return str(obj)
17
+
18
+
19
+ def order_by_to_camel(s: str) -> str:
20
+ if s.startswith('-'):
21
+ return '-' + to_camel(s[1:])
22
+ return to_camel(s)
@@ -34,10 +34,8 @@ class Extractor:
34
34
  continue
35
35
 
36
36
  if isinstance(value, (datetime, pd.Timestamp)):
37
- fmt = var.get("timestampFormat", "ISO8601")
38
- offset = var.get("timestampOffset", "+0000")
39
- parser = TimestampParser(fmt, offset)
40
- value = parser.format(value)
37
+ parser = TimestampParser(var["timestamp"])
38
+ value = parser.utc_to_string(value)
41
39
 
42
40
  filled[name] = value
43
41
  if not filled:
@@ -46,6 +46,7 @@ class HydroServerLoader(HydroServer, Loader):
46
46
  logging.info(f"dtypes: {df.dtypes}")
47
47
 
48
48
  df["value"] = pd.to_numeric(df["value"], errors="raise")
49
+ df = df.rename(columns={"timestamp": "phenomenon_time", "value": "result"})
49
50
  self.datastreams.load_observations(uid=col, observations=df)
50
51
 
51
52
  def _fetch_earliest_begin(self, mappings: list[dict]) -> pd.Timestamp: