cwms-python 1.1.0__tar.gz → 1.1.1__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.
- {cwms_python-1.1.0 → cwms_python-1.1.1}/PKG-INFO +1 -1
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries.py +86 -7
- {cwms_python-1.1.0 → cwms_python-1.1.1}/pyproject.toml +1 -1
- {cwms_python-1.1.0 → cwms_python-1.1.1}/LICENSE +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/README.md +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/__init__.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/api.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/catalog/blobs.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/catalog/catalog.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/catalog/clobs.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/cwms_types.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/forecast/forecast_instance.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/forecast/forecast_spec.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/levels/location_levels.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/levels/specified_levels.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/locations/gate_changes.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/locations/location_groups.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/locations/lookups.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/locations/physical_locations.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/locks/locks.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/measurements/measurements.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/outlets/outlets.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/outlets/virtual_outlets.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/project_lock_rights.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/project_locks.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/projects.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/water_supply/accounting.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/water_supply/water_contracts.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/water_supply/water_users.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/properties/properties.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/ratings/ratings.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/ratings/ratings_spec.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/ratings/ratings_template.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/standard_text/standard_text.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_bin.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_group.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_identifier.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_profile.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_profile_instance.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_profile_parser.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_txt.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/turbines/turbines.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/users/users.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/utils/__init__.py +0 -0
- {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/utils/checks.py +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import concurrent.futures
|
|
2
2
|
import logging
|
|
3
|
+
import re
|
|
3
4
|
from datetime import datetime, timedelta, timezone
|
|
4
5
|
from typing import Any, Dict, List, Optional, Tuple
|
|
5
6
|
|
|
@@ -10,6 +11,74 @@ import cwms.api as api
|
|
|
10
11
|
from cwms.catalog.catalog import get_ts_extents
|
|
11
12
|
from cwms.cwms_types import JSON, Data
|
|
12
13
|
|
|
14
|
+
_DEFAULT_CHUNK_DAYS = 365
|
|
15
|
+
_MIN_INTERVAL_MINUTES = 2
|
|
16
|
+
_FINE_INTERVAL_MINUTES = 15
|
|
17
|
+
_FINE_INTERVAL_CHUNK_DAYS = 365
|
|
18
|
+
_HOURLY_CHUNK_DAYS = 365
|
|
19
|
+
_SIX_HOURLY_CHUNK_DAYS = 1460
|
|
20
|
+
_COARSE_INTERVAL_CHUNK_DAYS = 2920
|
|
21
|
+
_INTERVAL_PATTERN = re.compile(
|
|
22
|
+
r"^(?P<count>\d+)(?P<unit>"
|
|
23
|
+
r"Minute|Minutes|Hour|Hours|Day|Days|"
|
|
24
|
+
r"Week|Weeks|Month|Months|Year|Years)$"
|
|
25
|
+
)
|
|
26
|
+
_INTERVAL_MINUTES = {
|
|
27
|
+
"Minute": 1,
|
|
28
|
+
"Minutes": 1,
|
|
29
|
+
"Hour": 60,
|
|
30
|
+
"Hours": 60,
|
|
31
|
+
"Day": 24 * 60,
|
|
32
|
+
"Days": 24 * 60,
|
|
33
|
+
"Week": 7 * 24 * 60,
|
|
34
|
+
"Weeks": 7 * 24 * 60,
|
|
35
|
+
"Month": 30 * 24 * 60,
|
|
36
|
+
"Months": 30 * 24 * 60,
|
|
37
|
+
"Year": 365 * 24 * 60,
|
|
38
|
+
"Years": 365 * 24 * 60,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_timeseries_chunk_size(ts_id: str) -> timedelta:
|
|
43
|
+
"""Return the default request chunk size for a time series interval.
|
|
44
|
+
|
|
45
|
+
Local regular time series intervals, such as ``~15Minutes``, use the same
|
|
46
|
+
chunk size as their regular interval. Unrecognized intervals retain the
|
|
47
|
+
conservative default used for 15-minute through hourly data.
|
|
48
|
+
"""
|
|
49
|
+
ts_id_parts = ts_id.split(".")
|
|
50
|
+
if len(ts_id_parts) < 4:
|
|
51
|
+
return timedelta(days=_DEFAULT_CHUNK_DAYS)
|
|
52
|
+
|
|
53
|
+
interval = ts_id_parts[3].removeprefix("~")
|
|
54
|
+
match = _INTERVAL_PATTERN.fullmatch(interval)
|
|
55
|
+
if match is None:
|
|
56
|
+
return timedelta(days=_DEFAULT_CHUNK_DAYS)
|
|
57
|
+
|
|
58
|
+
interval_minutes = (
|
|
59
|
+
int(match.group("count")) * _INTERVAL_MINUTES[match.group("unit")]
|
|
60
|
+
)
|
|
61
|
+
if interval_minutes < _MIN_INTERVAL_MINUTES:
|
|
62
|
+
return timedelta(days=_DEFAULT_CHUNK_DAYS)
|
|
63
|
+
|
|
64
|
+
# These bands balance request overhead and response size based on production
|
|
65
|
+
# CDA timings. Fine intervals scale toward about 35,000 expected values.
|
|
66
|
+
if interval_minutes < _FINE_INTERVAL_MINUTES:
|
|
67
|
+
chunk_days = max(
|
|
68
|
+
1,
|
|
69
|
+
round(
|
|
70
|
+
_FINE_INTERVAL_CHUNK_DAYS * interval_minutes / _FINE_INTERVAL_MINUTES
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
elif interval_minutes <= 60:
|
|
74
|
+
chunk_days = _HOURLY_CHUNK_DAYS
|
|
75
|
+
elif interval_minutes <= 6 * 60:
|
|
76
|
+
chunk_days = _SIX_HOURLY_CHUNK_DAYS
|
|
77
|
+
else:
|
|
78
|
+
chunk_days = _COARSE_INTERVAL_CHUNK_DAYS
|
|
79
|
+
|
|
80
|
+
return timedelta(days=chunk_days)
|
|
81
|
+
|
|
13
82
|
|
|
14
83
|
def get_multi_timeseries_df(
|
|
15
84
|
ts_ids: list[str],
|
|
@@ -136,6 +205,9 @@ def chunk_timeseries_time_range(
|
|
|
136
205
|
List[Tuple[datetime, datetime]]
|
|
137
206
|
A list of tuples, where each tuple represents the start and end of a chunk.
|
|
138
207
|
"""
|
|
208
|
+
if chunk_size <= timedelta(0):
|
|
209
|
+
raise ValueError("chunk_size must be greater than zero")
|
|
210
|
+
|
|
139
211
|
chunks = []
|
|
140
212
|
current = begin
|
|
141
213
|
while current < end:
|
|
@@ -298,7 +370,7 @@ def get_timeseries(
|
|
|
298
370
|
trim: Optional[bool] = True,
|
|
299
371
|
multithread: Optional[bool] = True,
|
|
300
372
|
max_workers: int = 20,
|
|
301
|
-
max_days_per_chunk: int =
|
|
373
|
+
max_days_per_chunk: Optional[int] = None,
|
|
302
374
|
) -> Data:
|
|
303
375
|
"""Retrieves time series values from a specified time series and time window. Value date-times
|
|
304
376
|
obtained are always in UTC.
|
|
@@ -338,15 +410,18 @@ def get_timeseries(
|
|
|
338
410
|
trim: boolean, optional, default is True
|
|
339
411
|
Specifies whether to trim missing values from the beginning and end of the retrieved values.
|
|
340
412
|
multithread: boolean, optional, default is True
|
|
341
|
-
Specifies whether to
|
|
413
|
+
Specifies whether to retrieve time series chunks concurrently.
|
|
342
414
|
max_workers: integer, default is 20
|
|
343
|
-
The maximum number of worker threads
|
|
344
|
-
max_days_per_chunk: integer, default is
|
|
345
|
-
The maximum number of days
|
|
415
|
+
The maximum number of worker threads used for concurrent requests.
|
|
416
|
+
max_days_per_chunk: integer, optional, default is None
|
|
417
|
+
The maximum number of days included in each request. By default,
|
|
418
|
+
the chunk size is selected from the time series interval.
|
|
346
419
|
Returns
|
|
347
420
|
-------
|
|
348
421
|
cwms data type. data.json will return the JSON output and data.df will return a dataframe. dates are all in UTC
|
|
349
422
|
"""
|
|
423
|
+
if max_days_per_chunk is not None and max_days_per_chunk <= 0:
|
|
424
|
+
raise ValueError("max_days_per_chunk must be greater than zero")
|
|
350
425
|
|
|
351
426
|
selector = "values"
|
|
352
427
|
endpoint = "timeseries"
|
|
@@ -386,8 +461,12 @@ def get_timeseries(
|
|
|
386
461
|
)
|
|
387
462
|
return Data(response, selector=selector)
|
|
388
463
|
|
|
389
|
-
|
|
390
|
-
|
|
464
|
+
chunk_size = (
|
|
465
|
+
timedelta(days=max_days_per_chunk)
|
|
466
|
+
if max_days_per_chunk is not None
|
|
467
|
+
else get_timeseries_chunk_size(ts_id)
|
|
468
|
+
)
|
|
469
|
+
chunks = chunk_timeseries_time_range(begin, end, chunk_size)
|
|
391
470
|
|
|
392
471
|
# find max worker thread
|
|
393
472
|
max_workers = max(min(len(chunks), max_workers), 1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|