hydroserverpy 1.4.0b3__tar.gz → 1.5.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 (77) hide show
  1. hydroserverpy-1.5.1/PKG-INFO +66 -0
  2. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/README.md +2 -2
  3. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/setup.cfg +6 -3
  4. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/etl_configuration.py +10 -10
  5. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/extractors/http_extractor.py +2 -5
  6. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/extractors/local_file_extractor.py +1 -1
  7. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/loaders/hydroserver_loader.py +0 -2
  8. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/schedule.py +2 -2
  9. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/status.py +1 -1
  10. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/timestamp_parser.py +5 -2
  11. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/transformers/base.py +7 -0
  12. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/transformers/csv_transformer.py +1 -1
  13. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/transformers/json_transformer.py +5 -0
  14. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/iam/workspace.py +14 -2
  15. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/sta/datastream.py +4 -0
  16. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/sta/observation.py +3 -1
  17. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/sta/datastream.py +4 -1
  18. hydroserverpy-1.5.1/src/hydroserverpy.egg-info/PKG-INFO +66 -0
  19. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy.egg-info/requires.txt +2 -2
  20. hydroserverpy-1.4.0b3/PKG-INFO +0 -19
  21. hydroserverpy-1.4.0b3/src/hydroserverpy.egg-info/PKG-INFO +0 -19
  22. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/LICENSE +0 -0
  23. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/pyproject.toml +0 -0
  24. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/setup.py +0 -0
  25. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/__init__.py +0 -0
  26. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/__init__.py +0 -0
  27. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/client.py +0 -0
  28. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/__init__.py +0 -0
  29. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/base.py +0 -0
  30. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/__init__.py +0 -0
  31. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/data_archive.py +0 -0
  32. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/data_source.py +0 -0
  33. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/extractors/__init__.py +0 -0
  34. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/extractors/base.py +0 -0
  35. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/extractors/ftp_extractor.py +0 -0
  36. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/factories.py +0 -0
  37. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/loaders/__init__.py +0 -0
  38. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/loaders/base.py +0 -0
  39. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/orchestration_configuration.py +0 -0
  40. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/orchestration_system.py +0 -0
  41. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/transformers/__init__.py +0 -0
  42. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/etl/types.py +0 -0
  43. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/iam/__init__.py +0 -0
  44. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/iam/account.py +0 -0
  45. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/iam/apikey.py +0 -0
  46. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/iam/collaborator.py +0 -0
  47. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/iam/role.py +0 -0
  48. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/sta/__init__.py +0 -0
  49. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/sta/observed_property.py +0 -0
  50. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/sta/processing_level.py +0 -0
  51. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/sta/result_qualifier.py +0 -0
  52. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/sta/sensor.py +0 -0
  53. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/sta/thing.py +0 -0
  54. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/models/sta/unit.py +0 -0
  55. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/__init__.py +0 -0
  56. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/base.py +0 -0
  57. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/etl/__init__.py +0 -0
  58. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/etl/data_archive.py +0 -0
  59. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/etl/data_source.py +0 -0
  60. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/etl/orchestration_system.py +0 -0
  61. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/iam/__init__.py +0 -0
  62. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/iam/role.py +0 -0
  63. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/iam/workspace.py +0 -0
  64. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/sta/__init__.py +0 -0
  65. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/sta/observed_property.py +0 -0
  66. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/sta/processing_level.py +0 -0
  67. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/sta/result_qualifier.py +0 -0
  68. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/sta/sensor.py +0 -0
  69. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/sta/thing.py +0 -0
  70. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/services/sta/unit.py +0 -0
  71. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/api/utils.py +0 -0
  72. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/quality/__init__.py +0 -0
  73. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy/quality/service.py +0 -0
  74. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy.egg-info/SOURCES.txt +0 -0
  75. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy.egg-info/dependency_links.txt +0 -0
  76. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy.egg-info/top_level.txt +0 -0
  77. {hydroserverpy-1.4.0b3 → hydroserverpy-1.5.1}/src/hydroserverpy.egg-info/zip-safe +0 -0
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: hydroserverpy
3
+ Version: 1.5.1
4
+ Summary: A Python client for managing HydroServer data
5
+ Requires-Python: <4,>=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: requests>=2
9
+ Requires-Dist: pydantic>=2.6
10
+ Requires-Dist: pydantic[email]>=2.6
11
+ Requires-Dist: pandas>=2.1
12
+ Requires-Dist: numpy>=1.22.4
13
+ Requires-Dist: pyyaml>=5
14
+ Requires-Dist: simplejson>=3
15
+ Requires-Dist: crontab>=1
16
+ Requires-Dist: python-dateutil>=2.8.2
17
+ Requires-Dist: croniter>=2.0.1
18
+ Requires-Dist: jmespath>=1.0.1
19
+ Provides-Extra: docs
20
+ Requires-Dist: sphinx_autodoc_typehints; extra == "docs"
21
+ Dynamic: license-file
22
+
23
+ # HydroServer Python Client
24
+
25
+ The hydroserverpy Python package provides an interface for managing HydroServer data and metadata, loading observations, and performing data quality control. This guide will go over how to install the package and connect to a HydroServer instance. Full hydroserverpy documentation and examples can be found [here](https://hydroserver2.github.io/hydroserver/how-to/hydroserverpy/hydroserverpy-examples.html).
26
+
27
+ ## Installation
28
+
29
+ You can install the package via pip:
30
+
31
+ ```bash
32
+ pip install hydroserverpy
33
+ ```
34
+
35
+ ## Connecting to HydroServer
36
+
37
+ To connect to HydroServer, you need to initialize the client with the instance of HydroServer you're using and your user credentials if you want to access and modify your own data. If you don't provide authentication credentials you can read public data, but you will not be able to create or modify any data.
38
+
39
+ ### Example: Anonymous User
40
+
41
+ ```python
42
+ from hydroserverpy import HydroServer
43
+
44
+ # Initialize HydroServer connection.
45
+ hs_api = HydroServer(
46
+ host='https://playground.hydroserver.org'
47
+ )
48
+ ```
49
+
50
+ ### Example: Basic Authentication
51
+
52
+ ```python
53
+ from hydroserverpy import HydroServer
54
+
55
+ # Initialize HydroServer connection with credentials.
56
+ hs_api = HydroServer(
57
+ host='https://playground.hydroserver.org',
58
+ email='user@example.com',
59
+ password='******'
60
+ )
61
+ ```
62
+
63
+ ## Funding and Acknowledgements
64
+
65
+ Funding for this project was provided by the National Oceanic & Atmospheric Administration (NOAA), awarded to the Cooperative Institute for Research to Operations in Hydrology (CIROH) through the NOAA Cooperative Agreement with The University of Alabama (NA22NWS4320003). Utah State University is a founding member of CIROH and receives funding under subaward from the University of Alabama. Additional funding and support have been provided by the State of Utah Division of Water Rights, the World Meteorological Organization, and the Utah Water Research laboratory at Utah State University.
66
+
@@ -1,6 +1,6 @@
1
1
  # HydroServer Python Client
2
2
 
3
- The hydroserverpy Python package provides an interface for managing HydroServer data and metadata, loading observations, and performing data quality control. This guide will go over how to install the package and connect to a HydroServer instance. Full hydroserverpy documentation can be found [here](https://hydroserver2.github.io/hydroserverpy).
3
+ The hydroserverpy Python package provides an interface for managing HydroServer data and metadata, loading observations, and performing data quality control. This guide will go over how to install the package and connect to a HydroServer instance. Full hydroserverpy documentation and examples can be found [here](https://hydroserver2.github.io/hydroserver/how-to/hydroserverpy/hydroserverpy-examples.html).
4
4
 
5
5
  ## Installation
6
6
 
@@ -40,5 +40,5 @@ hs_api = HydroServer(
40
40
 
41
41
  ## Funding and Acknowledgements
42
42
 
43
- Funding for this project was provided by the National Oceanic & Atmospheric Administration (NOAA), awarded to the Cooperative Institute for Research to Operations in Hydrology (CIROH) through the NOAA Cooperative Agreement with The University of Alabama (NA22NWS4320003). Utah State University is a founding member of CIROH and receives funding under subaward from the University of Alabama. Additional funding and support have been provided by the State of Utah Division of Water Rights, the World Meorological Organization, and the Utah Water Research laboratory at Utah State University.
43
+ Funding for this project was provided by the National Oceanic & Atmospheric Administration (NOAA), awarded to the Cooperative Institute for Research to Operations in Hydrology (CIROH) through the NOAA Cooperative Agreement with The University of Alabama (NA22NWS4320003). Utah State University is a founding member of CIROH and receives funding under subaward from the University of Alabama. Additional funding and support have been provided by the State of Utah Division of Water Rights, the World Meteorological Organization, and the Utah Water Research laboratory at Utah State University.
44
44
 
@@ -1,6 +1,9 @@
1
1
  [metadata]
2
2
  name = hydroserverpy
3
- version = 1.4.0b3
3
+ version = 1.5.1
4
+ description = A Python client for managing HydroServer data
5
+ long_description_content_type = text/markdown
6
+ long_description = file: README.md
4
7
 
5
8
  [options]
6
9
  package_dir =
@@ -12,8 +15,8 @@ install_requires =
12
15
  requests >= 2
13
16
  pydantic >= 2.6
14
17
  pydantic[email] >= 2.6
15
- pandas >= 2.2
16
- numpy >= 2.0
18
+ pandas >= 2.1
19
+ numpy >= 1.22.4
17
20
  pyyaml >= 5
18
21
  simplejson >= 3
19
22
  crontab >= 1
@@ -74,7 +74,7 @@ class Timestamp(BaseModel):
74
74
  timezone: Optional[Union[FixedOffsetTimezone, str]] = Field(None, alias="timezone")
75
75
 
76
76
  class Config:
77
- allow_population_by_field_name = True
77
+ populate_by_name = True
78
78
 
79
79
  @field_validator("timezone")
80
80
  def check_timezone(cls, timezone_value, info):
@@ -96,7 +96,7 @@ class RunTimePlaceholder(BaseModel):
96
96
  timestamp: Timestamp
97
97
 
98
98
  class Config:
99
- allow_population_by_field_name = True
99
+ populate_by_name = True
100
100
 
101
101
 
102
102
  PlaceholderVariable = Annotated[
@@ -114,7 +114,7 @@ class BaseExtractor(BaseModel):
114
114
  )
115
115
 
116
116
  class Config:
117
- allow_population_by_field_name = True
117
+ populate_by_name = True
118
118
 
119
119
 
120
120
  class HTTPExtractor(BaseExtractor):
@@ -140,7 +140,7 @@ class JSONTransformer(BaseTransformer):
140
140
  jmespath: str = Field(..., alias="JMESPath")
141
141
 
142
142
  class Config:
143
- allow_population_by_field_name = True
143
+ populate_by_name = True
144
144
 
145
145
 
146
146
  class CSVTransformer(BaseTransformer):
@@ -151,7 +151,7 @@ class CSVTransformer(BaseTransformer):
151
151
  identifier_type: IdentifierType = Field(..., alias="identifierType")
152
152
 
153
153
  class Config:
154
- allow_population_by_field_name = True
154
+ populate_by_name = True
155
155
 
156
156
 
157
157
  TransformerConfig = Union[JSONTransformer, CSVTransformer]
@@ -173,7 +173,7 @@ class ExpressionDataTransformation(BaseModel):
173
173
  expression: str
174
174
 
175
175
  class Config:
176
- allow_population_by_field_name = True
176
+ populate_by_name = True
177
177
 
178
178
 
179
179
  class LookupTableDataTransformation(BaseModel):
@@ -181,7 +181,7 @@ class LookupTableDataTransformation(BaseModel):
181
181
  lookup_table_id: str = Field(..., alias="lookupTableId")
182
182
 
183
183
  class Config:
184
- allow_population_by_field_name = True
184
+ populate_by_name = True
185
185
 
186
186
 
187
187
  DataTransformation = Union[ExpressionDataTransformation, LookupTableDataTransformation]
@@ -194,7 +194,7 @@ class MappingPath(BaseModel):
194
194
  )
195
195
 
196
196
  class Config:
197
- allow_population_by_field_name = True
197
+ populate_by_name = True
198
198
 
199
199
 
200
200
  class SourceTargetMapping(BaseModel):
@@ -202,7 +202,7 @@ class SourceTargetMapping(BaseModel):
202
202
  paths: List[MappingPath] = Field(default_factory=list)
203
203
 
204
204
  class Config:
205
- allow_population_by_field_name = True
205
+ populate_by_name = True
206
206
 
207
207
 
208
208
  class Payload(BaseModel):
@@ -213,7 +213,7 @@ class Payload(BaseModel):
213
213
  )
214
214
 
215
215
  class Config:
216
- allow_population_by_field_name = True
216
+ populate_by_name = True
217
217
 
218
218
 
219
219
  class EtlConfiguration(BaseModel):
@@ -17,11 +17,8 @@ class HTTPExtractor(Extractor):
17
17
  url = self.resolve_placeholder_variables(payload, loader)
18
18
  logging.info(f"Requesting data from → {url}")
19
19
 
20
- try:
21
- response = requests.get(url)
22
- except Exception as e:
23
- logging.error(f"Failed to fetch {url}: {e}")
24
- raise
20
+ response = requests.get(url)
21
+ response.raise_for_status()
25
22
 
26
23
  data = BytesIO()
27
24
  for chunk in response.iter_content(chunk_size=8192):
@@ -7,7 +7,7 @@ class LocalFileExtractor(Extractor):
7
7
  def __init__(self, extractor_config: ExtractorConfig):
8
8
  super().__init__(extractor_config)
9
9
 
10
- def extract(self):
10
+ def extract(self, *args, **kwargs):
11
11
  """
12
12
  Opens the file and returns a file-like object.
13
13
  """
@@ -36,8 +36,6 @@ class HydroServerLoader(Loader):
36
36
  if df.empty:
37
37
  logging.warning(f"No new data for {col}, skipping.")
38
38
  continue
39
- logging.info(f"loading dataframe {df}")
40
- logging.info(f"dtypes: {df.dtypes}")
41
39
 
42
40
  df = df.rename(columns={"timestamp": "phenomenon_time", "value": "result"})
43
41
 
@@ -1,6 +1,6 @@
1
1
  from datetime import datetime
2
2
  from typing import Literal, Optional
3
- from pydantic import BaseModel, Field, EmailStr
3
+ from pydantic import BaseModel, Field
4
4
 
5
5
 
6
6
  class Schedule(BaseModel):
@@ -13,4 +13,4 @@ class Schedule(BaseModel):
13
13
  end_time: Optional[datetime] = Field(None, alias="endTime")
14
14
 
15
15
  class Config:
16
- allow_population_by_field_name = True
16
+ populate_by_name = True
@@ -11,4 +11,4 @@ class Status(BaseModel):
11
11
  next_run: Optional[datetime] = Field(None, alias="nextRun")
12
12
 
13
13
  class Config:
14
- allow_population_by_field_name = True
14
+ populate_by_name = True
@@ -73,11 +73,14 @@ class TimestampParser:
73
73
  return localized.dt.tz_convert(timezone.utc)
74
74
 
75
75
  def parse_series(self, raw_series: pd.Series) -> pd.Series:
76
- s = raw_series.str.strip()
76
+ if pd.api.types.is_datetime64_any_dtype(raw_series):
77
+ s = raw_series # already datetimes
78
+ else:
79
+ s = raw_series.astype("string", copy=False).str.strip()
77
80
  parsed = self._convert_series_to_UTC(s)
78
81
 
79
82
  if parsed.isna().any():
80
- bad_rows = s[parsed.isna()].head(5).tolist()
83
+ bad_rows = s[parsed.isna()].head(2).tolist()
81
84
  logging.warning(
82
85
  f"{parsed.isna().sum()} timestamps failed to parse. "
83
86
  f"Sample bad values: {bad_rows}"
@@ -68,12 +68,19 @@ class Transformer(ABC):
68
68
  def standardize_dataframe(
69
69
  self, df: pd.DataFrame, mappings: List[SourceTargetMapping]
70
70
  ):
71
+ if not df.empty:
72
+ logging.info(f"Read payload into dataframe: {df.iloc[0].to_dict()}")
73
+ else:
74
+ logging.info("Read payload into dataframe: [empty dataframe]")
75
+
71
76
  # 1) Normalize timestamp column
72
77
  df.rename(columns={self.timestamp.key: "timestamp"}, inplace=True)
73
78
  if "timestamp" not in df.columns:
74
79
  msg = f"Timestamp column '{self.timestamp.key}' not found in data."
75
80
  logging.error(msg)
76
81
  raise ValueError(msg)
82
+ logging.info(f"Renamed timestamp column to 'timestamp'")
83
+
77
84
  df["timestamp"] = self.timestamp_parser.parse_series(df["timestamp"])
78
85
  df = df.drop_duplicates(subset=["timestamp"], keep="last")
79
86
 
@@ -50,7 +50,7 @@ class CSVTransformer(Transformer):
50
50
  df = pd.read_csv(
51
51
  clean_file,
52
52
  sep=self.delimiter,
53
- header=self.header_row,
53
+ header=0,
54
54
  skiprows=self._build_skiprows(),
55
55
  usecols=usecols,
56
56
  dtype={self.timestamp.key: "string"},
@@ -24,6 +24,11 @@ class JSONTransformer(Transformer):
24
24
  Returns:
25
25
  pd.DataFrame: pandas DataFrames in the format pd.Timestamp, datastream_id_1, datastream_id_2, ...
26
26
  """
27
+ if data_file is None:
28
+ raise TypeError(
29
+ "JSONTransformer received None; expected file-like, bytes, or str"
30
+ )
31
+
27
32
  json_data = json.load(data_file)
28
33
  data_points = self.extract_data_points(json_data)
29
34
  if not data_points:
@@ -1,7 +1,7 @@
1
1
  from typing import List, Union, Optional, ClassVar, TYPE_CHECKING
2
2
  from uuid import UUID
3
3
  from datetime import datetime
4
- from pydantic import Field, EmailStr
4
+ from pydantic import Field, EmailStr, AliasPath
5
5
  from ..base import HydroServerBaseModel
6
6
 
7
7
  if TYPE_CHECKING:
@@ -28,7 +28,9 @@ class Workspace(HydroServerBaseModel):
28
28
  name: str = Field(..., max_length=255)
29
29
  is_private: bool
30
30
  owner: "Account"
31
- collaborator_role: Optional["Role"] = None
31
+ collaborator_role_id: Optional[Union[UUID, str]] = Field(
32
+ None, validation_alias=AliasPath("collaboratorRole", "id")
33
+ )
32
34
  pending_transfer_to: Optional["Account"] = None
33
35
 
34
36
  _editable_fields: ClassVar[set[str]] = {"name", "is_private"}
@@ -38,6 +40,7 @@ class Workspace(HydroServerBaseModel):
38
40
 
39
41
  self._roles = None
40
42
  self._collaborators = None
43
+ self._collaborator_role = None
41
44
  self._apikeys = None
42
45
  self._things = None
43
46
  self._observedproperties = None
@@ -72,6 +75,15 @@ class Workspace(HydroServerBaseModel):
72
75
 
73
76
  return self._collaborators
74
77
 
78
+ @property
79
+ def collaborator_role(self) -> Optional["Role"]:
80
+ """The user's collaborator role on this workspace."""
81
+
82
+ if self._collaborator_role is None and self.collaborator_role_id is not None:
83
+ self._collaborator_role = self.client.roles.get(uid=self.collaborator_role_id)
84
+
85
+ return self._collaborator_role
86
+
75
87
  @property
76
88
  def apikeys(self) -> List["APIKey"]:
77
89
  """The API keys associated with this workspace."""
@@ -194,6 +194,7 @@ class Datastream(HydroServerBaseModel):
194
194
  order_by: List[str] = ...,
195
195
  phenomenon_time_max: datetime = ...,
196
196
  phenomenon_time_min: datetime = ...,
197
+ result_qualifier_code: str = ...,
197
198
  fetch_all: bool = False,
198
199
  ) -> pd.DataFrame:
199
200
  """Retrieve the observations for this datastream."""
@@ -205,18 +206,21 @@ class Datastream(HydroServerBaseModel):
205
206
  order_by=order_by,
206
207
  phenomenon_time_max=phenomenon_time_max,
207
208
  phenomenon_time_min=phenomenon_time_min,
209
+ result_qualifier_code=result_qualifier_code,
208
210
  fetch_all=fetch_all
209
211
  )
210
212
 
211
213
  def load_observations(
212
214
  self,
213
215
  observations: pd.DataFrame,
216
+ mode: str = "insert"
214
217
  ) -> None:
215
218
  """Load a DataFrame of observations to the datastream."""
216
219
 
217
220
  return self.client.datastreams.load_observations(
218
221
  uid=self.uid,
219
222
  observations=observations,
223
+ mode=mode
220
224
  )
221
225
 
222
226
  def delete_observations(
@@ -38,7 +38,9 @@ class ObservationCollection:
38
38
  data = response.json()
39
39
  self.dataframe = pd.DataFrame({to_snake(k): v for k, v in data.items()})
40
40
  if "phenomenon_time" in self.dataframe.columns:
41
- self.dataframe["phenomenon_time"] = pd.to_datetime(self.dataframe["phenomenon_time"], utc=True)
41
+ self.dataframe["phenomenon_time"] = pd.to_datetime(
42
+ self.dataframe["phenomenon_time"], utc=True, format="ISO8601"
43
+ )
42
44
  else:
43
45
  self.dataframe = pd.DataFrame()
44
46
 
@@ -220,6 +220,7 @@ class DatastreamService(HydroServerBaseService):
220
220
  order_by: List[str] = ...,
221
221
  phenomenon_time_max: datetime = ...,
222
222
  phenomenon_time_min: datetime = ...,
223
+ result_qualifier_code: str = ...,
223
224
  fetch_all: bool = False,
224
225
  ) -> ObservationCollection:
225
226
  """Retrieve observations of a datastream."""
@@ -230,6 +231,7 @@ class DatastreamService(HydroServerBaseService):
230
231
  "order_by": ",".join(order_by) if order_by is not ... else order_by,
231
232
  "phenomenon_time_max": phenomenon_time_max,
232
233
  "phenomenon_time_min": phenomenon_time_min,
234
+ "result_qualifier_code": result_qualifier_code,
233
235
  "format": "column"
234
236
  }
235
237
  params = {
@@ -256,12 +258,13 @@ class DatastreamService(HydroServerBaseService):
256
258
  self,
257
259
  uid: Union[UUID, str],
258
260
  observations: pd.DataFrame,
261
+ mode: str = "insert"
259
262
  ) -> None:
260
263
  """Load observations to a datastream."""
261
264
 
262
265
  path = f"/{self.client.base_route}/{self.model.get_route()}/{str(uid)}/observations/bulk-create"
263
266
  headers = {"Content-type": "application/json"}
264
- params = {"mode": "insert"}
267
+ params = {"mode": mode}
265
268
  body = {
266
269
  "fields": [to_camel(col) for col in observations.columns.tolist()],
267
270
  "data": observations.values.tolist()
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: hydroserverpy
3
+ Version: 1.5.1
4
+ Summary: A Python client for managing HydroServer data
5
+ Requires-Python: <4,>=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: requests>=2
9
+ Requires-Dist: pydantic>=2.6
10
+ Requires-Dist: pydantic[email]>=2.6
11
+ Requires-Dist: pandas>=2.1
12
+ Requires-Dist: numpy>=1.22.4
13
+ Requires-Dist: pyyaml>=5
14
+ Requires-Dist: simplejson>=3
15
+ Requires-Dist: crontab>=1
16
+ Requires-Dist: python-dateutil>=2.8.2
17
+ Requires-Dist: croniter>=2.0.1
18
+ Requires-Dist: jmespath>=1.0.1
19
+ Provides-Extra: docs
20
+ Requires-Dist: sphinx_autodoc_typehints; extra == "docs"
21
+ Dynamic: license-file
22
+
23
+ # HydroServer Python Client
24
+
25
+ The hydroserverpy Python package provides an interface for managing HydroServer data and metadata, loading observations, and performing data quality control. This guide will go over how to install the package and connect to a HydroServer instance. Full hydroserverpy documentation and examples can be found [here](https://hydroserver2.github.io/hydroserver/how-to/hydroserverpy/hydroserverpy-examples.html).
26
+
27
+ ## Installation
28
+
29
+ You can install the package via pip:
30
+
31
+ ```bash
32
+ pip install hydroserverpy
33
+ ```
34
+
35
+ ## Connecting to HydroServer
36
+
37
+ To connect to HydroServer, you need to initialize the client with the instance of HydroServer you're using and your user credentials if you want to access and modify your own data. If you don't provide authentication credentials you can read public data, but you will not be able to create or modify any data.
38
+
39
+ ### Example: Anonymous User
40
+
41
+ ```python
42
+ from hydroserverpy import HydroServer
43
+
44
+ # Initialize HydroServer connection.
45
+ hs_api = HydroServer(
46
+ host='https://playground.hydroserver.org'
47
+ )
48
+ ```
49
+
50
+ ### Example: Basic Authentication
51
+
52
+ ```python
53
+ from hydroserverpy import HydroServer
54
+
55
+ # Initialize HydroServer connection with credentials.
56
+ hs_api = HydroServer(
57
+ host='https://playground.hydroserver.org',
58
+ email='user@example.com',
59
+ password='******'
60
+ )
61
+ ```
62
+
63
+ ## Funding and Acknowledgements
64
+
65
+ Funding for this project was provided by the National Oceanic & Atmospheric Administration (NOAA), awarded to the Cooperative Institute for Research to Operations in Hydrology (CIROH) through the NOAA Cooperative Agreement with The University of Alabama (NA22NWS4320003). Utah State University is a founding member of CIROH and receives funding under subaward from the University of Alabama. Additional funding and support have been provided by the State of Utah Division of Water Rights, the World Meteorological Organization, and the Utah Water Research laboratory at Utah State University.
66
+
@@ -1,8 +1,8 @@
1
1
  requests>=2
2
2
  pydantic>=2.6
3
3
  pydantic[email]>=2.6
4
- pandas>=2.2
5
- numpy>=2.0
4
+ pandas>=2.1
5
+ numpy>=1.22.4
6
6
  pyyaml>=5
7
7
  simplejson>=3
8
8
  crontab>=1
@@ -1,19 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: hydroserverpy
3
- Version: 1.4.0b3
4
- Requires-Python: <4,>=3.9
5
- License-File: LICENSE
6
- Requires-Dist: requests>=2
7
- Requires-Dist: pydantic>=2.6
8
- Requires-Dist: pydantic[email]>=2.6
9
- Requires-Dist: pandas>=2.2
10
- Requires-Dist: numpy>=2.0
11
- Requires-Dist: pyyaml>=5
12
- Requires-Dist: simplejson>=3
13
- Requires-Dist: crontab>=1
14
- Requires-Dist: python-dateutil>=2.8.2
15
- Requires-Dist: croniter>=2.0.1
16
- Requires-Dist: jmespath>=1.0.1
17
- Provides-Extra: docs
18
- Requires-Dist: sphinx_autodoc_typehints; extra == "docs"
19
- Dynamic: license-file
@@ -1,19 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: hydroserverpy
3
- Version: 1.4.0b3
4
- Requires-Python: <4,>=3.9
5
- License-File: LICENSE
6
- Requires-Dist: requests>=2
7
- Requires-Dist: pydantic>=2.6
8
- Requires-Dist: pydantic[email]>=2.6
9
- Requires-Dist: pandas>=2.2
10
- Requires-Dist: numpy>=2.0
11
- Requires-Dist: pyyaml>=5
12
- Requires-Dist: simplejson>=3
13
- Requires-Dist: crontab>=1
14
- Requires-Dist: python-dateutil>=2.8.2
15
- Requires-Dist: croniter>=2.0.1
16
- Requires-Dist: jmespath>=1.0.1
17
- Provides-Extra: docs
18
- Requires-Dist: sphinx_autodoc_typehints; extra == "docs"
19
- Dynamic: license-file
File without changes
File without changes