hydroserverpy 0.5.0b3__py3-none-any.whl → 0.5.0b5__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.

@@ -1,8 +1,8 @@
1
+ import requests
1
2
  import tempfile
2
3
  from typing import Union, List, Optional, TYPE_CHECKING
3
4
  from uuid import UUID
4
5
  from pydantic import BaseModel, Field
5
- from urllib.request import urlopen
6
6
  from hydroserverpy.etl_csv.hydroserver_etl_csv import HydroServerETLCSV
7
7
  from .orchestration_system import OrchestrationSystem
8
8
  from .orchestration_configuration import OrchestrationConfigurationFields
@@ -135,14 +135,17 @@ class DataSource(HydroServerModel, DataSourceFields, OrchestrationConfigurationF
135
135
  )
136
136
  loader.run()
137
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)
138
+ with tempfile.NamedTemporaryFile(mode="w+") as temp_file:
139
+ response = requests.get(
140
+ self.settings["extractor"]["urlTemplate"],
141
+ stream=True,
142
+ timeout=60,
143
+ )
144
+ response.raise_for_status()
145
+ chunk_size = 1024 * 1024 * 10 # Use a 10mb chunk size.
146
+ for chunk in response.iter_content(chunk_size=chunk_size):
147
+ if chunk:
148
+ temp_file.write(chunk.decode("utf-8"))
146
149
  temp_file.seek(0)
147
150
  loader = HydroServerETLCSV(
148
151
  self._connection, data_file=temp_file, data_source=self
@@ -141,20 +141,6 @@ class Datastream(HydroServerModel, DatastreamFields):
141
141
  _connection=_connection, _model_ref="datastreams", _uid=_uid, **data
142
142
  )
143
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
144
  self._workspace = None
159
145
  self._thing = None
160
146
  self._observed_property = None
@@ -167,7 +153,8 @@ class Datastream(HydroServerModel, DatastreamFields):
167
153
  """The workspace this datastream belongs to."""
168
154
 
169
155
  if self._workspace is None:
170
- self._workspace = self._connection.workspaces.get(uid=self._workspace_id)
156
+ datastream = self._connection.request("get", f"/api/data/datastreams/{str(self.uid)}").json()
157
+ self._workspace = self._connection.workspaces.get(uid=datastream["workspaceId"])
171
158
 
172
159
  return self._workspace
173
160
 
@@ -246,7 +233,8 @@ class Datastream(HydroServerModel, DatastreamFields):
246
233
  """The unit this datastream uses."""
247
234
 
248
235
  if self._unit is None:
249
- self._unit = self._connection.units.get(uid=self._unit_id)
236
+ datastream = self._connection.request("get", f"/api/data/datastreams/{str(self.uid)}").json()
237
+ self._unit = self._connection.units.get(uid=datastream["unitId"])
250
238
  self._original_data["unit"] = self._unit
251
239
 
252
240
  return self._unit
@@ -263,9 +251,8 @@ class Datastream(HydroServerModel, DatastreamFields):
263
251
  """The processing level of this datastream."""
264
252
 
265
253
  if self._processing_level is None:
266
- self._processing_level = self._connection.processinglevels.get(
267
- uid=self._processing_level_id
268
- )
254
+ datastream = self._connection.request("get", f"/api/data/datastreams/{str(self.uid)}").json()
255
+ self._processing_level = self._connection.processinglevels.get(uid=datastream["processingLevelId"])
269
256
  self._original_data["processing_level"] = self._processing_level
270
257
 
271
258
  return self._processing_level
@@ -285,13 +272,10 @@ class Datastream(HydroServerModel, DatastreamFields):
285
272
  """Refresh this datastream from HydroServer."""
286
273
 
287
274
  self._workspace = None
288
- self._workspace_id = None
289
275
  self._thing = None
290
276
  self._observed_property = None
291
277
  self._unit = None
292
- self._unit_id = None
293
278
  self._processing_level = None
294
- self._processing_level_id = None
295
279
  self._sensor = None
296
280
  super()._refresh()
297
281
 
@@ -336,3 +320,23 @@ class Datastream(HydroServerModel, DatastreamFields):
336
320
  uid=self.uid,
337
321
  observations=observations,
338
322
  )
323
+
324
+ # TODO: Find a better long-term solution for this issue.
325
+ def sync_phenomenon_end_time(self):
326
+ """Ensures the phenomenon_end_time field matches the actual end time of the observations."""
327
+
328
+ response = self._connection.request(
329
+ "get", f"/api/data/datastreams/{str(self.uid)}/observations",
330
+ params={
331
+ "order": "desc",
332
+ "page": 1,
333
+ "page_size": 1
334
+ }
335
+ ).json()
336
+
337
+ if len(response["phenomenon_time"]) > 0:
338
+ self.phenomenon_end_time = datetime.fromisoformat(response["phenomenon_time"][0])
339
+ else:
340
+ self.phenomenon_end_time = None
341
+
342
+ self.save()
@@ -1,4 +1,5 @@
1
1
  import csv
2
+ import math
2
3
  import logging
3
4
  import croniter
4
5
  import pandas as pd
@@ -51,7 +52,7 @@ class HydroServerETLCSV:
51
52
  self._file_header_error = False
52
53
  self._file_timestamp_error = False
53
54
 
54
- self._chunk_size = 10000
55
+ self._chunk_size = 1000
55
56
  self._observations = {}
56
57
 
57
58
  def run(self):
@@ -124,6 +125,9 @@ class HydroServerETLCSV:
124
125
  timestamp = self._parse_row_timestamp(row)
125
126
 
126
127
  for datastream in self._datastreams.values():
128
+ if index == self._data_source.settings["transformer"]["dataStartRow"]:
129
+ datastream.sync_phenomenon_end_time()
130
+
127
131
  if str(datastream.uid) not in self._datastream_start_row_indexes.keys():
128
132
  if (
129
133
  not datastream.phenomenon_end_time
@@ -138,14 +142,27 @@ class HydroServerETLCSV:
138
142
  if str(datastream.uid) not in self._observations.keys():
139
143
  self._observations[str(datastream.uid)] = []
140
144
 
145
+ raw_result = row[
146
+ self._datastream_column_indexes[
147
+ self._datastream_mapping[str(datastream.uid)]
148
+ ]
149
+ ]
150
+
151
+ if isinstance(raw_result, (int, float)):
152
+ result = raw_result
153
+ else:
154
+ try:
155
+ result = float(raw_result)
156
+ except (TypeError, ValueError):
157
+ result = datastream.no_data_value
158
+
159
+ if math.isnan(result):
160
+ result = datastream.no_data_value
161
+
141
162
  self._observations[str(datastream.uid)].append(
142
163
  {
143
164
  "phenomenon_time": timestamp,
144
- "result": row[
145
- self._datastream_column_indexes[
146
- self._datastream_mapping[str(datastream.uid)]
147
- ]
148
- ],
165
+ "result": result,
149
166
  }
150
167
  )
151
168
 
@@ -282,8 +299,9 @@ class HydroServerETLCSV:
282
299
  uid=datastream_id,
283
300
  observations=observations_df,
284
301
  )
285
- except HTTPError:
302
+ except HTTPError as e:
286
303
  failed_datastreams.append(datastream_id)
304
+ logger.error(f"Failed to POST observations to datastream: {str(datastream_id)} - {e}")
287
305
 
288
306
  elif datastream_id in self._failed_datastreams:
289
307
  logger.info(
@@ -308,13 +326,13 @@ class HydroServerETLCSV:
308
326
 
309
327
  if self._data_source.crontab is not None:
310
328
  next_run = croniter.croniter(
311
- self._data_source.crontab, datetime.now()
329
+ self._data_source.crontab, datetime.now(timezone.utc)
312
330
  ).get_next(datetime)
313
331
  elif (
314
332
  self._data_source.interval is not None
315
333
  and self._data_source.interval_units is not None
316
334
  ):
317
- next_run = datetime.now() + timedelta(
335
+ next_run = datetime.now(timezone.utc) + timedelta(
318
336
  **{self._data_source.interval_units: self._data_source.interval}
319
337
  )
320
338
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hydroserverpy
3
- Version: 0.5.0b3
3
+ Version: 0.5.0b5
4
4
  Requires-Python: <4,>=3.9
5
5
  License-File: LICENSE
6
6
  Requires-Dist: requests>=2
@@ -6,7 +6,7 @@ hydroserverpy/api/models/__init__.py,sha256=ELrf3b7Aix7YcVF__Q_8e_G_FF8GYlX0J5l7
6
6
  hydroserverpy/api/models/base.py,sha256=dc2tfMSgizymxAAOVURfy7Jzeh6xIiiq7hfWZI7l1_Q,2280
7
7
  hydroserverpy/api/models/etl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  hydroserverpy/api/models/etl/data_archive.py,sha256=u-gpvUsaWaw0kyF3bPMm2e55Jx2yhvSV9ufXXaNtrTc,3429
9
- hydroserverpy/api/models/etl/data_source.py,sha256=ca-9KKVhkLNaUn3vOIk-JgdWk58fTRme8YKIesk8WIw,5455
9
+ hydroserverpy/api/models/etl/data_source.py,sha256=x88Z-CAzDljQEbGlif1KmQ4zNjQBiJrKNqirpoCZnPs,5538
10
10
  hydroserverpy/api/models/etl/orchestration_configuration.py,sha256=ElSrgi7ioFZJFJg6aGogW5ZZk7fA17y4p--yWwiOhZ0,1367
11
11
  hydroserverpy/api/models/etl/orchestration_system.py,sha256=25En2G0z1gQzN-RW3UlrEGgkC952QDW21oYnawCX8hY,2357
12
12
  hydroserverpy/api/models/iam/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -15,7 +15,7 @@ hydroserverpy/api/models/iam/collaborator.py,sha256=jp661DKDCwk8c8HFPAV-YVhEc80F
15
15
  hydroserverpy/api/models/iam/role.py,sha256=8FVTj_1QwtPF9tk7baliMVg000kjc5N8oP6eYo8vTDY,275
16
16
  hydroserverpy/api/models/iam/workspace.py,sha256=s9u1oZyOdxM7txjJARFcIBrWMHQSDxODdreiatFsXJs,7331
17
17
  hydroserverpy/api/models/sta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- hydroserverpy/api/models/sta/datastream.py,sha256=sB-KifvegbyDUnyPE_NCHFrab1ZSVVb6g-Gs7kUgMiE,10774
18
+ hydroserverpy/api/models/sta/datastream.py,sha256=vRjgwAKaoBJEtgUXrZjIS-VuIZsCilm7FRwbvLS8Y8o,11186
19
19
  hydroserverpy/api/models/sta/observed_property.py,sha256=ThTg8aPMHPxbk9Hzpxw3AwM16gE1xvYpRK8UkiOdGeA,2180
20
20
  hydroserverpy/api/models/sta/processing_level.py,sha256=y5_0wX7QGXgswvukXJtbpOiTCZ9pI8E08DXaTSUHakg,1470
21
21
  hydroserverpy/api/models/sta/result_qualifier.py,sha256=IJcY04KjP9e2D-jPzUJjH2PC-JvDNCjbi5LKkTVSwgw,1416
@@ -55,12 +55,12 @@ hydroserverpy/etl/transformers/csv_transformer.py,sha256=9DKSO4NfUUDlr_c6UnH4AU3
55
55
  hydroserverpy/etl/transformers/json_transformer.py,sha256=ity0MXcYjEnlun4Y6cVSrnjrglKrK4JOXXHxWHIHN2A,2323
56
56
  hydroserverpy/etl_csv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
57
  hydroserverpy/etl_csv/exceptions.py,sha256=0UY8YUlNepG0y6FfH36hJyR1bOhwYHSZIdUSSMTg7GA,314
58
- hydroserverpy/etl_csv/hydroserver_etl_csv.py,sha256=y7AI6MjrcM9Dh-Id7G77dLzogZ7CSO4JhFR-AirMnJA,13854
58
+ hydroserverpy/etl_csv/hydroserver_etl_csv.py,sha256=0ueBphEaAAlsb0cn71Ihgd5zOD8Zdu4Ts_yGwvXW53M,14544
59
59
  hydroserverpy/quality/__init__.py,sha256=GGBMkFSXciJLYrbV-NraFrj_mXWCy_GTcy9KKrKXU4c,84
60
60
  hydroserverpy/quality/service.py,sha256=U02UfLKVmFvr5ySiH0n0JYzUIabq5uprrHIiwcqBlqY,13879
61
- hydroserverpy-0.5.0b3.dist-info/licenses/LICENSE,sha256=xVqFxDw3QOEJukakL7gQCqIMTQ1dlSCTo6Oc1otNW80,1508
62
- hydroserverpy-0.5.0b3.dist-info/METADATA,sha256=O1XE90QxIZLWaNymaUyCubt3NyuqJ--m-b7vuzHQ9H8,532
63
- hydroserverpy-0.5.0b3.dist-info/WHEEL,sha256=lTU6B6eIfYoiQJTZNc-fyaR6BpL6ehTzU3xGYxn2n8k,91
64
- hydroserverpy-0.5.0b3.dist-info/top_level.txt,sha256=Zf37hrncXLOYvXhgCrf5mZdeq81G9fShdE2LfYbtb7w,14
65
- hydroserverpy-0.5.0b3.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
66
- hydroserverpy-0.5.0b3.dist-info/RECORD,,
61
+ hydroserverpy-0.5.0b5.dist-info/licenses/LICENSE,sha256=xVqFxDw3QOEJukakL7gQCqIMTQ1dlSCTo6Oc1otNW80,1508
62
+ hydroserverpy-0.5.0b5.dist-info/METADATA,sha256=2MHOiPK4LfT1plINuLzDUiT0TwVmD2yqZu3edn0BYCI,532
63
+ hydroserverpy-0.5.0b5.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
64
+ hydroserverpy-0.5.0b5.dist-info/top_level.txt,sha256=Zf37hrncXLOYvXhgCrf5mZdeq81G9fShdE2LfYbtb7w,14
65
+ hydroserverpy-0.5.0b5.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
66
+ hydroserverpy-0.5.0b5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.1)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5