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.
Files changed (45) hide show
  1. {cwms_python-1.1.0 → cwms_python-1.1.1}/PKG-INFO +1 -1
  2. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries.py +86 -7
  3. {cwms_python-1.1.0 → cwms_python-1.1.1}/pyproject.toml +1 -1
  4. {cwms_python-1.1.0 → cwms_python-1.1.1}/LICENSE +0 -0
  5. {cwms_python-1.1.0 → cwms_python-1.1.1}/README.md +0 -0
  6. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/__init__.py +0 -0
  7. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/api.py +0 -0
  8. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/catalog/blobs.py +0 -0
  9. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/catalog/catalog.py +0 -0
  10. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/catalog/clobs.py +0 -0
  11. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/cwms_types.py +0 -0
  12. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/forecast/forecast_instance.py +0 -0
  13. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/forecast/forecast_spec.py +0 -0
  14. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/levels/location_levels.py +0 -0
  15. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/levels/specified_levels.py +0 -0
  16. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/locations/gate_changes.py +0 -0
  17. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/locations/location_groups.py +0 -0
  18. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/locations/lookups.py +0 -0
  19. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/locations/physical_locations.py +0 -0
  20. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/locks/locks.py +0 -0
  21. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/measurements/measurements.py +0 -0
  22. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/outlets/outlets.py +0 -0
  23. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/outlets/virtual_outlets.py +0 -0
  24. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/project_lock_rights.py +0 -0
  25. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/project_locks.py +0 -0
  26. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/projects.py +0 -0
  27. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/water_supply/accounting.py +0 -0
  28. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/water_supply/water_contracts.py +0 -0
  29. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/projects/water_supply/water_users.py +0 -0
  30. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/properties/properties.py +0 -0
  31. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/ratings/ratings.py +0 -0
  32. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/ratings/ratings_spec.py +0 -0
  33. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/ratings/ratings_template.py +0 -0
  34. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/standard_text/standard_text.py +0 -0
  35. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_bin.py +0 -0
  36. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_group.py +0 -0
  37. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_identifier.py +0 -0
  38. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_profile.py +0 -0
  39. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_profile_instance.py +0 -0
  40. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_profile_parser.py +0 -0
  41. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/timeseries/timeseries_txt.py +0 -0
  42. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/turbines/turbines.py +0 -0
  43. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/users/users.py +0 -0
  44. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/utils/__init__.py +0 -0
  45. {cwms_python-1.1.0 → cwms_python-1.1.1}/cwms/utils/checks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cwms-python
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: Corps water management systems (CWMS) REST API for Data Retrieval of USACE water data
5
5
  License: LICENSE
6
6
  License-File: LICENSE
@@ -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 = 14,
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 trim missing values from the beginning and end of the retrieved values.
413
+ Specifies whether to retrieve time series chunks concurrently.
342
414
  max_workers: integer, default is 20
343
- The maximum number of worker threads that will be spawned for multithreading, If calling more than 3 years of 15 minute data, consider using 30 max_workers
344
- max_days_per_chunk: integer, default is 14
345
- The maximum number of days that would be included in a thread. If calling more than 1 year of 15 minute data, consider using 30 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
- # divide the time range into chunks
390
- chunks = chunk_timeseries_time_range(begin, end, timedelta(days=max_days_per_chunk))
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)
@@ -3,7 +3,7 @@ name = "cwms-python"
3
3
  repository = "https://github.com/HydrologicEngineeringCenter/cwms-python"
4
4
 
5
5
  # Managed by Release Please; runtime versions come from package metadata.
6
- version = "1.1.0"
6
+ version = "1.1.1"
7
7
 
8
8
  packages = [
9
9
  { include = "cwms" },
File without changes
File without changes
File without changes