hydroserverpy 2.0.0b2__tar.gz → 2.0.0b4__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.
Files changed (113) hide show
  1. {hydroserverpy-2.0.0b2/src/hydroserverpy.egg-info → hydroserverpy-2.0.0b4}/PKG-INFO +1 -1
  2. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/setup.cfg +1 -1
  3. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/client.py +27 -1
  4. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/__init__.py +2 -2
  5. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/base.py +49 -58
  6. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/etl/data_connection.py +1 -1
  7. hydroserverpy-2.0.0b4/src/hydroserverpy/api/models/etl/mapping.py +26 -0
  8. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/etl/task.py +21 -14
  9. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/iam/collaborator.py +22 -9
  10. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/iam/role.py +2 -2
  11. hydroserverpy-2.0.0b2/src/hydroserverpy/api/models/iam/apikey.py → hydroserverpy-2.0.0b4/src/hydroserverpy/api/models/iam/service_account.py +18 -33
  12. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/iam/workspace.py +38 -40
  13. hydroserverpy-2.0.0b4/src/hydroserverpy/api/models/monitoring/rule.py +35 -0
  14. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/monitoring/task.py +8 -8
  15. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/products/rating_curve.py +1 -1
  16. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/products/task.py +27 -10
  17. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/products/transformation.py +28 -3
  18. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/sta/datastream.py +13 -23
  19. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/sta/method.py +5 -5
  20. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/sta/monitoring_site.py +1 -1
  21. hydroserverpy-2.0.0b4/src/hydroserverpy/api/models/sta/observation.py +144 -0
  22. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/sta/observed_property.py +4 -4
  23. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/sta/processing_level.py +3 -3
  24. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/sta/result_qualifier.py +4 -4
  25. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/sta/unit.py +2 -2
  26. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/__init__.py +1 -0
  27. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/base.py +15 -18
  28. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/etl/data_connection.py +6 -6
  29. hydroserverpy-2.0.0b4/src/hydroserverpy/api/services/etl/mapping.py +70 -0
  30. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/etl/task.py +16 -28
  31. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/iam/role.py +6 -10
  32. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/iam/workspace.py +78 -68
  33. hydroserverpy-2.0.0b4/src/hydroserverpy/api/services/monitoring/rule.py +84 -0
  34. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/monitoring/task.py +16 -16
  35. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/products/rating_curve.py +6 -6
  36. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/products/task.py +16 -16
  37. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/products/transformation.py +112 -93
  38. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/sta/datastream.py +127 -62
  39. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/sta/method.py +6 -6
  40. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/sta/monitoring_site.py +70 -7
  41. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/sta/observed_property.py +8 -8
  42. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/sta/processing_level.py +10 -9
  43. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/sta/result_qualifier.py +12 -12
  44. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/sta/unit.py +6 -6
  45. hydroserverpy-2.0.0b4/src/hydroserverpy/api/utils.py +51 -0
  46. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/loaders/hydroserver.py +3 -1
  47. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4/src/hydroserverpy.egg-info}/PKG-INFO +1 -1
  48. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy.egg-info/SOURCES.txt +2 -1
  49. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/tests/test_api_client.py +60 -1
  50. hydroserverpy-2.0.0b2/src/hydroserverpy/api/models/etl/mapping.py +0 -10
  51. hydroserverpy-2.0.0b2/src/hydroserverpy/api/models/monitoring/rule.py +0 -50
  52. hydroserverpy-2.0.0b2/src/hydroserverpy/api/models/sta/observation.py +0 -134
  53. hydroserverpy-2.0.0b2/src/hydroserverpy/api/services/monitoring/rule.py +0 -142
  54. hydroserverpy-2.0.0b2/src/hydroserverpy/api/utils.py +0 -22
  55. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/LICENSE +0 -0
  56. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/README.md +0 -0
  57. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/pyproject.toml +0 -0
  58. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/__init__.py +0 -0
  59. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/__init__.py +0 -0
  60. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/etl/__init__.py +0 -0
  61. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/etl/run.py +0 -0
  62. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/iam/__init__.py +0 -0
  63. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/iam/account.py +0 -0
  64. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/monitoring/__init__.py +0 -0
  65. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/orchestration/__init__.py +0 -0
  66. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/orchestration/run.py +0 -0
  67. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/products/__init__.py +0 -0
  68. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/models/sta/__init__.py +0 -0
  69. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/etl/__init__.py +0 -0
  70. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/iam/__init__.py +0 -0
  71. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/monitoring/__init__.py +0 -0
  72. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/products/__init__.py +0 -0
  73. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/api/services/sta/__init__.py +0 -0
  74. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/core/__init__.py +0 -0
  75. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/core/duration.py +0 -0
  76. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/core/timeseries.py +0 -0
  77. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/__init__.py +0 -0
  78. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/exceptions.py +0 -0
  79. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/extractors/__init__.py +0 -0
  80. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/extractors/base.py +0 -0
  81. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/extractors/ftp.py +0 -0
  82. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/extractors/http.py +0 -0
  83. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/extractors/local.py +0 -0
  84. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/hydroserver.py +0 -0
  85. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/loaders/__init__.py +0 -0
  86. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/loaders/base.py +0 -0
  87. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/models/__init__.py +0 -0
  88. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/models/base.py +0 -0
  89. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/models/pipeline.py +0 -0
  90. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/models/timestamp.py +0 -0
  91. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/operations/__init__.py +0 -0
  92. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/operations/arithmetic_expression.py +0 -0
  93. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/operations/base.py +0 -0
  94. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/operations/rating_curve.py +0 -0
  95. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/operations/temporal_aggregation.py +0 -0
  96. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/transformers/__init__.py +0 -0
  97. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/transformers/base.py +0 -0
  98. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/transformers/csv.py +0 -0
  99. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/transformers/json.py +0 -0
  100. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/user_facing_errors.py +0 -0
  101. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/etl/utils.py +0 -0
  102. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/products/__init__.py +0 -0
  103. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/products/aggregation.py +0 -0
  104. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/products/derivation.py +0 -0
  105. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/products/rating_curve.py +0 -0
  106. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/quality/__init__.py +0 -0
  107. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/quality/check.py +0 -0
  108. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/quality/correction.py +0 -0
  109. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy/quality/service.py +0 -0
  110. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy.egg-info/dependency_links.txt +0 -0
  111. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy.egg-info/requires.txt +0 -0
  112. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy.egg-info/top_level.txt +0 -0
  113. {hydroserverpy-2.0.0b2 → hydroserverpy-2.0.0b4}/src/hydroserverpy.egg-info/zip-safe +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydroserverpy
3
- Version: 2.0.0b2
3
+ Version: 2.0.0b4
4
4
  Summary: A Python client for managing HydroServer data
5
5
  Home-page: https://github.com/hydroserver2/hydroserver/tree/main/packages/hydroserverpy
6
6
  Author: Kenneth Lippold
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = hydroserverpy
3
- version = 2.0.0b2
3
+ version = 2.0.0b4
4
4
  description = A Python client for managing HydroServer data
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
@@ -13,6 +13,7 @@ from hydroserverpy.api.services import (
13
13
  DatastreamService,
14
14
  DataConnectionService,
15
15
  TaskService,
16
+ EtlMappingService,
16
17
  MonitoringTaskService,
17
18
  MonitoringRuleService,
18
19
  RatingCurveService,
@@ -25,7 +26,7 @@ class HydroServer:
25
26
  def __init__(
26
27
  self,
27
28
  host: str,
28
- base_route: str = "/api/data",
29
+ base_route: str = "/api/ogc",
29
30
  email: Optional[str] = None,
30
31
  password: Optional[str] = None,
31
32
  apikey: Optional[str] = None,
@@ -45,9 +46,28 @@ class HydroServer:
45
46
 
46
47
  self._init_session()
47
48
 
49
+ @staticmethod
50
+ def _encode_query_value(value):
51
+ """
52
+ Encodes booleans as the lowercase 'true' and 'false' the API requires; requests would
53
+ otherwise send str(value), i.e. 'True' and 'False'.
54
+ """
55
+
56
+ if isinstance(value, bool):
57
+ return "true" if value else "false"
58
+ if isinstance(value, (list, tuple)):
59
+ return [HydroServer._encode_query_value(item) for item in value]
60
+
61
+ return value
62
+
48
63
  def request(self, method, path, *args, **kwargs) -> requests.Response:
49
64
  """Sends a request to HydroServer's API."""
50
65
 
66
+ if isinstance(kwargs.get("params"), dict):
67
+ kwargs["params"] = {
68
+ key: self._encode_query_value(value) for key, value in kwargs["params"].items()
69
+ }
70
+
51
71
  for attempt in range(2):
52
72
  try:
53
73
  response = getattr(self._session, method)(
@@ -172,6 +192,12 @@ class HydroServer:
172
192
 
173
193
  return TaskService(self)
174
194
 
195
+ @property
196
+ def etlmappings(self):
197
+ """Utilities for managing HydroServer ETL mappings."""
198
+
199
+ return EtlMappingService(self)
200
+
175
201
  @property
176
202
  def monitoringtasks(self):
177
203
  """Utilities for managing HydroServer monitoring tasks."""
@@ -2,7 +2,7 @@ from .iam.account import Account
2
2
  from .iam.workspace import Workspace
3
3
  from .iam.role import Role
4
4
  from .iam.collaborator import Collaborator
5
- from .iam.apikey import APIKey
5
+ from .iam.service_account import ServiceAccount
6
6
  from .iam.account import Account
7
7
  from .sta.datastream import Datastream
8
8
  from .sta.observation import ObservationCollection
@@ -30,4 +30,4 @@ from .products.transformation import (
30
30
  Workspace.model_rebuild()
31
31
  Role.model_rebuild()
32
32
  Collaborator.model_rebuild()
33
- APIKey.model_rebuild()
33
+ ServiceAccount.model_rebuild()
@@ -97,10 +97,9 @@ class HydroServerBaseModel(BaseModel):
97
97
  class HydroServerCollection:
98
98
  items: List["HydroServerBaseModel"]
99
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
100
+ sortby: Optional[List[str]] = None
101
+ offset: Optional[int] = None
102
+ limit: Optional[int] = None
104
103
  total_count: Optional[int] = None
105
104
 
106
105
  _service: Optional[Any] = field(init=False, repr=False)
@@ -116,40 +115,36 @@ class HydroServerCollection:
116
115
  self._service = service
117
116
 
118
117
  self.filters = data.get("filters")
119
- self.order_by = data.get("order_by")
120
- self.page = self._resolve_int_metadata("page", "X-Page", response, data)
121
- self.page_size = self._resolve_int_metadata(
122
- "page_size", "X-Page-Size", response, data
123
- )
124
- self.total_pages = self._resolve_int_metadata(
125
- "total_pages", "X-Total-Pages", response, data
126
- )
127
- self.total_count = self._resolve_int_metadata(
128
- "total_count", "X-Total-Count", response, data
129
- )
118
+ self.sortby = data.get("sortby")
119
+
120
+ payload = response.json() if response is not None else {}
121
+ meta = payload.get("meta", {}) if isinstance(payload, dict) else {}
122
+
123
+ self.offset = self._resolve_int_metadata("offset", meta, data)
124
+ self.limit = self._resolve_int_metadata("limit", meta, data)
125
+ self.total_count = self._resolve_int_metadata("total_count", meta, data, meta_key="numberMatched")
130
126
 
131
127
  if "items" in data:
132
128
  self.items = data["items"]
133
129
  elif response is not None:
134
- self.items = [model(client=client, **entity) for entity in response.json()]
130
+ self.items = [model(client=client, **entity) for entity in payload.get("data", [])]
135
131
  else:
136
132
  self.items = []
137
133
 
138
134
  @staticmethod
139
135
  def _resolve_int_metadata(
140
136
  field_name: str,
141
- header_name: str,
142
- response: Optional[Response],
137
+ meta: dict,
143
138
  data: dict,
139
+ meta_key: Optional[str] = None,
144
140
  ) -> Optional[int]:
145
141
  field_value = data.get(field_name)
146
142
  if field_value is not None:
147
143
  return int(field_value)
148
144
 
149
- if response:
150
- header_value = response.headers.get(header_name)
151
- if header_value is not None:
152
- return int(header_value)
145
+ meta_value = meta.get(meta_key or field_name)
146
+ if meta_value is not None:
147
+ return int(meta_value)
153
148
 
154
149
  return None
155
150
 
@@ -163,11 +158,13 @@ class HydroServerCollection:
163
158
  if not self._service:
164
159
  raise NotImplementedError("Pagination not enabled for this collection.")
165
160
 
161
+ limit = self.limit or 100
162
+
166
163
  return self._service.list(
167
164
  **(self.filters or {}),
168
- page=(self.page or 0) + 1,
169
- page_size=self.page_size or 100,
170
- order_by=self.order_by or ...
165
+ offset=(self.offset or 0) + limit,
166
+ limit=limit,
167
+ sortby=self.sortby or ...
171
168
  )
172
169
 
173
170
  def previous_page(self):
@@ -176,14 +173,16 @@ class HydroServerCollection:
176
173
  if not self._service:
177
174
  raise NotImplementedError("Pagination not enabled for this collection.")
178
175
 
179
- if not self.page or self.page <= 1:
176
+ if not self.offset:
180
177
  return None
181
178
 
179
+ limit = self.limit or 100
180
+
182
181
  return self._service.list(
183
182
  **(self.filters or {}),
184
- page=self.page - 1,
185
- page_size=self.page_size or 100,
186
- order_by=self.order_by or ...
183
+ offset=max(0, self.offset - limit),
184
+ limit=limit,
185
+ sortby=self.sortby or ...
187
186
  )
188
187
 
189
188
  def fetch_all(self) -> "HydroServerCollection":
@@ -192,30 +191,23 @@ class HydroServerCollection:
192
191
  if not self._service:
193
192
  raise NotImplementedError("Pagination not enabled for this collection.")
194
193
 
195
- all_items = []
196
- current_page = self.page or 1
197
- page_size = self.page_size or 100
198
- total_pages = self.total_pages
199
-
200
- page_num = 1
201
- while total_pages is None or page_num <= total_pages:
202
- if page_num == current_page:
203
- all_items.extend(self.items)
204
- else:
205
- page = self._service.list(
206
- **(self.filters or {}),
207
- page=page_num,
208
- page_size=page_size,
209
- order_by=self.order_by or ...
210
- )
211
- if not page.items:
212
- break
213
- all_items.extend(page.items)
214
-
215
- if page.total_pages is not None:
216
- total_pages = page.total_pages
217
-
218
- page_num += 1
194
+ all_items = list(self.items)
195
+ limit = self.limit or 100
196
+ next_offset = (self.offset or 0) + len(self.items)
197
+ last_page_size = len(self.items)
198
+
199
+ while last_page_size == limit:
200
+ page = self._service.list(
201
+ **(self.filters or {}),
202
+ offset=next_offset,
203
+ limit=limit,
204
+ sortby=self.sortby or ...
205
+ )
206
+ if not page.items:
207
+ break
208
+ all_items.extend(page.items)
209
+ last_page_size = len(page.items)
210
+ next_offset += last_page_size
219
211
 
220
212
  return self.__class__(
221
213
  model=type(self.items[0]) if self.items else None,
@@ -223,9 +215,8 @@ class HydroServerCollection:
223
215
  service=self._service,
224
216
  items=all_items,
225
217
  filters=self.filters,
226
- order_by=self.order_by,
227
- page=1,
228
- page_size=len(all_items),
229
- total_pages=1,
230
- total_count=len(all_items)
218
+ sortby=self.sortby,
219
+ offset=0,
220
+ limit=len(all_items),
221
+ total_count=len(all_items),
231
222
  )
@@ -109,4 +109,4 @@ class DataConnection(HydroServerBaseModel):
109
109
 
110
110
  @classmethod
111
111
  def get_route(cls):
112
- return "etl/data-connections"
112
+ return "collections/etl-data-connections/items"
@@ -0,0 +1,26 @@
1
+ import uuid
2
+ from typing import ClassVar, TYPE_CHECKING
3
+ from pydantic import AliasChoices, AliasPath, Field
4
+ from ..base import HydroServerBaseModel
5
+
6
+ if TYPE_CHECKING:
7
+ from hydroserverpy import HydroServer
8
+
9
+
10
+ class EtlMapping(HydroServerBaseModel):
11
+ task_id: uuid.UUID = Field(validation_alias="etlTaskId")
12
+ source_identifier: str
13
+ target_datastream_id: uuid.UUID = Field(
14
+ validation_alias=AliasChoices(
15
+ "targetDatastreamId", AliasPath("targetDatastream", "id")
16
+ )
17
+ )
18
+
19
+ _editable_fields: ClassVar[set[str]] = {"source_identifier", "target_datastream_id"}
20
+
21
+ def __init__(self, client: "HydroServer", **data):
22
+ super().__init__(client=client, service=client.etlmappings, **data)
23
+
24
+ @classmethod
25
+ def get_route(cls):
26
+ return "collections/etl-mappings/items"
@@ -1,7 +1,8 @@
1
1
  import uuid
2
2
  from datetime import datetime
3
+ from functools import cached_property
3
4
  from typing import Any, ClassVar, Dict, List, Literal, Optional, Union, TYPE_CHECKING
4
- from pydantic import Field, AliasPath
5
+ from pydantic import AliasChoices, Field, AliasPath
5
6
  from ..base import HydroServerBaseModel
6
7
  from ..orchestration.run import TaskRun
7
8
  from .mapping import EtlMapping
@@ -15,7 +16,11 @@ class EtlTask(HydroServerBaseModel):
15
16
  name: str
16
17
  description: Optional[str] = None
17
18
  task_variables: Dict[str, Any] = Field(default_factory=dict)
18
- data_connection_id: uuid.UUID
19
+ data_connection_id: uuid.UUID = Field(
20
+ validation_alias=AliasChoices(
21
+ "dataConnectionId", AliasPath("dataConnection", "id")
22
+ )
23
+ )
19
24
  enabled: Optional[bool] = Field(None, validation_alias=AliasPath("schedule", "enabled"))
20
25
  start_time: Optional[datetime] = Field(None, validation_alias=AliasPath("schedule", "startTime"))
21
26
  crontab: Optional[str] = Field(None, validation_alias=AliasPath("schedule", "crontab"))
@@ -25,7 +30,7 @@ class EtlTask(HydroServerBaseModel):
25
30
  )
26
31
  next_run_at: Optional[datetime] = Field(None, validation_alias=AliasPath("schedule", "nextRunAt"))
27
32
  latest_run: Optional[TaskRun] = None
28
- mappings: List[EtlMapping] = []
33
+ mapping_count: int = 0
29
34
 
30
35
  _editable_fields: ClassVar[set[str]] = set()
31
36
 
@@ -46,10 +51,6 @@ class EtlTask(HydroServerBaseModel):
46
51
  name=self.name,
47
52
  description=self.description,
48
53
  task_variables=self.task_variables,
49
- mappings=[
50
- {"source_identifier": m.source_identifier, "target_datastream_id": str(m.target_datastream_id)}
51
- for m in self.mappings
52
- ],
53
54
  crontab=self.crontab,
54
55
  interval=self.interval,
55
56
  interval_period=self.interval_period,
@@ -61,7 +62,13 @@ class EtlTask(HydroServerBaseModel):
61
62
 
62
63
  @classmethod
63
64
  def get_route(cls):
64
- return "etl/tasks"
65
+ return "collections/etl-tasks/items"
66
+
67
+ @cached_property
68
+ def mappings(self) -> List[EtlMapping]:
69
+ """All source-to-datastream mappings for this ETL task."""
70
+
71
+ return self.client.etlmappings.list(etl_task=self.uid, fetch_all=True).items
65
72
 
66
73
  @property
67
74
  def data_connection(self) -> "DataConnection":
@@ -76,9 +83,9 @@ class EtlTask(HydroServerBaseModel):
76
83
 
77
84
  def list_runs(
78
85
  self,
79
- page: int = ...,
80
- page_size: int = ...,
81
- order_by: List[str] = ...,
86
+ offset: int = ...,
87
+ limit: int = ...,
88
+ sortby: List[str] = ...,
82
89
  status: str = ...,
83
90
  started_at_min: datetime = ...,
84
91
  started_at_max: datetime = ...,
@@ -89,9 +96,9 @@ class EtlTask(HydroServerBaseModel):
89
96
 
90
97
  return self.client.etltasks.list_runs(
91
98
  uid=self.uid,
92
- page=page,
93
- page_size=page_size,
94
- order_by=order_by,
99
+ offset=offset,
100
+ limit=limit,
101
+ sortby=sortby,
95
102
  status=status,
96
103
  started_at_min=started_at_min,
97
104
  started_at_max=started_at_max,
@@ -1,6 +1,5 @@
1
- from typing import Union, ClassVar, TYPE_CHECKING
1
+ from typing import Optional, Union, ClassVar, TYPE_CHECKING
2
2
  from uuid import UUID
3
- from pydantic import Field, AliasPath
4
3
  from hydroserverpy.api.models.iam.role import Role
5
4
  from hydroserverpy.api.utils import normalize_uuid
6
5
  from ..base import HydroServerBaseModel
@@ -8,13 +7,13 @@ from ..base import HydroServerBaseModel
8
7
  if TYPE_CHECKING:
9
8
  from hydroserverpy import HydroServer
10
9
  from hydroserverpy.api.models.iam.workspace import Workspace
11
- from hydroserverpy.api.models.iam.account import Account
12
10
 
13
11
 
14
12
  class Collaborator(HydroServerBaseModel):
15
- user: "Account"
16
- role_id: Union[UUID, str] = Field(..., validation_alias=AliasPath("role", "id"))
13
+ role_id: Union[UUID, str]
17
14
  workspace_id: Union[UUID, str]
15
+ user_email: Optional[str] = None
16
+ service_account_email: Optional[str] = None
18
17
 
19
18
  _editable_fields: ClassVar[set[str]] = {"role_id"}
20
19
 
@@ -22,7 +21,19 @@ class Collaborator(HydroServerBaseModel):
22
21
  super().__init__(client=client, service=None, **data)
23
22
 
24
23
  self._workspace = None
25
- self._role = Role(client=client, **data.get("role"))
24
+ self._role = None
25
+
26
+ @property
27
+ def email(self) -> str:
28
+ """The email identifying whichever principal (user or service account) this collaborator is."""
29
+
30
+ return self.user_email or self.service_account_email
31
+
32
+ @property
33
+ def is_service_account(self) -> bool:
34
+ """Whether this collaborator is a service account rather than a user."""
35
+
36
+ return self.service_account_email is not None
26
37
 
27
38
  @property
28
39
  def workspace(self) -> "Workspace":
@@ -54,17 +65,19 @@ class Collaborator(HydroServerBaseModel):
54
65
  """Saves changes to this resource to HydroServer."""
55
66
 
56
67
  if self.unsaved_changes:
57
- self.client.workspaces.edit_collaborator_role(
58
- uid=str(self.workspace_id), email=self.user.email, role=self.role
68
+ saved = self.client.workspaces.edit_collaborator_role(
69
+ uid=str(self.workspace_id), email=self.email, role=self.role_id
59
70
  )
60
71
  self._role = None
61
72
  self._server_data["role_id"] = self.role_id
62
73
  self.__dict__.update({"role_id": self.role_id})
74
+ if saved is not None:
75
+ self.__dict__.update(saved.__dict__)
63
76
 
64
77
  def delete(self):
65
78
  """Deletes this resource from HydroServer."""
66
79
 
67
80
  self.client.workspaces.remove_collaborator(
68
- uid=str(self.workspace_id), email=self.user.email
81
+ uid=str(self.workspace_id), email=self.email
69
82
  )
70
83
  self.uid = None
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
10
10
 
11
11
  class Role(HydroServerBaseModel):
12
12
  name: str = Field(..., max_length=255)
13
- description: str
13
+ description: Optional[str] = None
14
14
  workspace_id: Optional[Union[UUID, str]] = None
15
15
 
16
16
  def __init__(self, client: "HydroServer", **data):
@@ -20,7 +20,7 @@ class Role(HydroServerBaseModel):
20
20
 
21
21
  @classmethod
22
22
  def get_route(cls):
23
- return "roles"
23
+ return "collections/roles/items"
24
24
 
25
25
  @property
26
26
  def workspace(self) -> "Workspace":
@@ -1,7 +1,6 @@
1
1
  from typing import Optional, Union, ClassVar, TYPE_CHECKING
2
2
  from uuid import UUID
3
3
  from datetime import datetime
4
- from hydroserverpy.api.models.iam.role import Role
5
4
  from hydroserverpy.api.utils import normalize_uuid
6
5
  from ..base import HydroServerBaseModel
7
6
 
@@ -10,48 +9,32 @@ if TYPE_CHECKING:
10
9
  from hydroserverpy.api.models import Workspace
11
10
 
12
11
 
13
- class APIKey(HydroServerBaseModel):
12
+ class ServiceAccount(HydroServerBaseModel):
14
13
  name: str
15
- role_id: Union[UUID, str]
14
+ email: str
16
15
  workspace_id: Union[UUID, str]
17
16
  description: Optional[str] = None
18
17
  is_active: bool
19
- expires_at: Optional[datetime] = None
18
+ key_expires_at: Optional[datetime] = None
19
+ created_at: datetime
20
+ last_used_at: Optional[datetime] = None
20
21
 
21
- _editable_fields: ClassVar[set[str]] = {"name", "description", "role_id", "is_active", "expires_at"}
22
+ _editable_fields: ClassVar[set[str]] = {"name", "description", "is_active", "key_expires_at"}
22
23
 
23
24
  def __init__(self, client: "HydroServer", **data):
24
25
  super().__init__(client=client, service=None, **data)
25
26
 
26
27
  self._workspace = None
27
- self._role = None
28
28
 
29
29
  @property
30
30
  def workspace(self) -> "Workspace":
31
- """The workspace this API key belongs to."""
31
+ """The workspace this service account belongs to."""
32
32
 
33
33
  if self._workspace is None:
34
34
  self._workspace = self.client.workspaces.get(uid=self.workspace_id)
35
35
 
36
36
  return self._workspace
37
37
 
38
- @property
39
- def role(self) -> "Role":
40
- """The role this API key is assigned."""
41
-
42
- if self._role is None:
43
- self._role = self.client.roles.get(uid=self.role_id)
44
-
45
- return self._role
46
-
47
- @role.setter
48
- def role(self, role: Union["Role", UUID, str] = ...):
49
- if not role:
50
- raise ValueError("Role of API key cannot be None.")
51
- if normalize_uuid(role) != str(self.role_id):
52
- self.role_id = normalize_uuid(role)
53
- self._role = None
54
-
55
38
  def save(self):
56
39
  """Saves changes to this resource to HydroServer."""
57
40
 
@@ -59,8 +42,8 @@ class APIKey(HydroServerBaseModel):
59
42
  raise AttributeError("Data cannot be saved: UID is not set.")
60
43
 
61
44
  if self.unsaved_changes:
62
- saved_resource = self.client.workspaces.update_api_key(
63
- uid=self.workspace_id, api_key_id=self.uid, **self.unsaved_changes
45
+ saved_resource = self.client.workspaces.update_service_account(
46
+ uid=self.workspace_id, service_account_id=self.uid, **self.unsaved_changes
64
47
  )
65
48
  self._server_data = saved_resource.dict(by_alias=False).copy()
66
49
  self.__dict__.update(saved_resource.__dict__)
@@ -71,7 +54,9 @@ class APIKey(HydroServerBaseModel):
71
54
  if self.uid is None:
72
55
  raise ValueError("Cannot refresh data without a valid ID.")
73
56
 
74
- refreshed_resource = self.client.workspaces.get_api_key(uid=self.workspace_id, api_key_id=self.uid)
57
+ refreshed_resource = self.client.workspaces.get_service_account(
58
+ uid=self.workspace_id, service_account_id=self.uid
59
+ )
75
60
  self._server_data = refreshed_resource.dict(by_alias=False).copy()
76
61
  self.__dict__.update(refreshed_resource.__dict__)
77
62
 
@@ -81,16 +66,16 @@ class APIKey(HydroServerBaseModel):
81
66
  if self.uid is None:
82
67
  raise AttributeError("Cannot delete data without a valid ID.")
83
68
 
84
- self.client.workspaces.delete_api_key(
85
- uid=self.workspace_id, api_key_id=self.uid
69
+ self.client.workspaces.delete_service_account(
70
+ uid=self.workspace_id, service_account_id=self.uid
86
71
  )
87
72
  self.uid = None
88
73
 
89
- def regenerate(self):
90
- """Regenerates this API key. WARNING: Previous key will be invalidated."""
74
+ def regenerate(self) -> str:
75
+ """Regenerates this service account's key. WARNING: the previous key will be invalidated."""
91
76
 
92
- _, key = self.client.workspaces.regenerate_api_key(
93
- uid=self.workspace_id, api_key_id=self.uid
77
+ _, key = self.client.workspaces.regenerate_service_account_key(
78
+ uid=self.workspace_id, service_account_id=self.uid
94
79
  )
95
80
 
96
81
  return key