cloudnet-api-client 0.12.3__tar.gz → 0.12.5__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.
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/CHANGELOG.md +12 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/PKG-INFO +9 -5
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/README.md +8 -4
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/cloudnet_api_client/client.py +32 -20
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/cloudnet_api_client/containers.py +25 -4
- cloudnet_api_client-0.12.5/cloudnet_api_client/version.py +1 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/tests/test_client.py +51 -3
- cloudnet_api_client-0.12.3/cloudnet_api_client/version.py +0 -1
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/.github/dataportal.env +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/.github/db.env +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/.github/docker-compose.yml +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/.github/initdb.d/init-dbs.sh +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/.github/ss.env +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/.github/workflows/publish.yml +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/.github/workflows/test.yml +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/.gitignore +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/.pre-commit-config.yaml +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/LICENSE +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/cloudnet_api_client/__init__.py +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/cloudnet_api_client/dl.py +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/cloudnet_api_client/py.typed +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/cloudnet_api_client/utils.py +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/pyproject.toml +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/tests/data/20140205_hyytiala_classification.nc +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/tests/data/20250801_Magurele_CHM170137_000.nc +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/tests/data/20250803_JOYCE_WST_01m.dat +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/tests/data/20250808_Granada_CHM170119_0045_000.nc +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/tests/data/20250808_hyytiala_iwc-Z-T-method.nc +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/tests/data/20250814_bucharest_classification.nc +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/tests/data/20250821_limassol_parsivel_41582c49.nc +0 -0
- {cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/tests/data/20250822_leipzig-lim_ecmwf-open.nc +0 -0
|
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## 0.12.5 – 2025-09-03
|
|
9
|
+
|
|
10
|
+
- Add "mobile" site type
|
|
11
|
+
- Split `Location` to `MeanLocation` and `RawLocation`
|
|
12
|
+
|
|
13
|
+
## 0.12.4 – 2025-08-26
|
|
14
|
+
|
|
15
|
+
- Add `software` attribute to `ProductMetadata`
|
|
16
|
+
- Fix hashability of `Product` in `ProductMetadata`
|
|
17
|
+
- Add possible timeliness values
|
|
18
|
+
- Use aware datetimes
|
|
19
|
+
|
|
8
20
|
## 0.12.3 – 2025-08-26
|
|
9
21
|
|
|
10
22
|
- Fix owners type
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudnet-api-client
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.5
|
|
4
4
|
Summary: Cloudnet API client
|
|
5
5
|
Author-email: Simo Tukiainen <simo.tukiainen@fmi.fi>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -195,21 +195,25 @@ Parameters:
|
|
|
195
195
|
|
|
196
196
|
Fetch all instruments.
|
|
197
197
|
|
|
198
|
+
### `APIClient().instrument_ids()` → `frozenset[str]`
|
|
199
|
+
|
|
200
|
+
Fetch all instrument identifiers.
|
|
201
|
+
|
|
198
202
|
### `APIClient().instrument()` → `ExtendedInstrument`
|
|
199
203
|
|
|
200
204
|
Fetch a single instruments.
|
|
201
205
|
|
|
202
206
|
Parameters:
|
|
203
207
|
|
|
204
|
-
| name | type
|
|
205
|
-
| ---- |
|
|
206
|
-
| uuid | `str` |
|
|
208
|
+
| name | type |
|
|
209
|
+
| ---- | -------------------- |
|
|
210
|
+
| uuid | `str` or `uuid.UUID` |
|
|
207
211
|
|
|
208
212
|
### `APIClient().models()` → `list[Model]`
|
|
209
213
|
|
|
210
214
|
Fetch all models.
|
|
211
215
|
|
|
212
|
-
### `APIClient().
|
|
216
|
+
### `APIClient().model()` → `Model`
|
|
213
217
|
|
|
214
218
|
Fetch a single model.
|
|
215
219
|
|
|
@@ -166,21 +166,25 @@ Parameters:
|
|
|
166
166
|
|
|
167
167
|
Fetch all instruments.
|
|
168
168
|
|
|
169
|
+
### `APIClient().instrument_ids()` → `frozenset[str]`
|
|
170
|
+
|
|
171
|
+
Fetch all instrument identifiers.
|
|
172
|
+
|
|
169
173
|
### `APIClient().instrument()` → `ExtendedInstrument`
|
|
170
174
|
|
|
171
175
|
Fetch a single instruments.
|
|
172
176
|
|
|
173
177
|
Parameters:
|
|
174
178
|
|
|
175
|
-
| name | type
|
|
176
|
-
| ---- |
|
|
177
|
-
| uuid | `str` |
|
|
179
|
+
| name | type |
|
|
180
|
+
| ---- | -------------------- |
|
|
181
|
+
| uuid | `str` or `uuid.UUID` |
|
|
178
182
|
|
|
179
183
|
### `APIClient().models()` → `list[Model]`
|
|
180
184
|
|
|
181
185
|
Fetch all models.
|
|
182
186
|
|
|
183
|
-
### `APIClient().
|
|
187
|
+
### `APIClient().model()` → `Model`
|
|
184
188
|
|
|
185
189
|
Fetch a single model.
|
|
186
190
|
|
|
@@ -22,14 +22,17 @@ from cloudnet_api_client.containers import (
|
|
|
22
22
|
STATUS,
|
|
23
23
|
ExtendedInstrument,
|
|
24
24
|
ExtendedProduct,
|
|
25
|
+
ExtendedProductMetadata,
|
|
25
26
|
Instrument,
|
|
26
|
-
|
|
27
|
+
MeanLocation,
|
|
27
28
|
Model,
|
|
28
29
|
Product,
|
|
29
30
|
ProductMetadata,
|
|
31
|
+
RawLocation,
|
|
30
32
|
RawMetadata,
|
|
31
33
|
RawModelMetadata,
|
|
32
34
|
Site,
|
|
35
|
+
Software,
|
|
33
36
|
VersionMetadata,
|
|
34
37
|
)
|
|
35
38
|
from cloudnet_api_client.dl import download_files
|
|
@@ -60,7 +63,7 @@ class APIClient:
|
|
|
60
63
|
self,
|
|
61
64
|
type: SITE_TYPE | list[SITE_TYPE] | None = None,
|
|
62
65
|
) -> list[Site]:
|
|
63
|
-
type =
|
|
66
|
+
type = _validate_type(type, SITE_TYPE)
|
|
64
67
|
res = self._get("sites", {"type": type})
|
|
65
68
|
return _build_objects(res, Site)
|
|
66
69
|
|
|
@@ -71,7 +74,7 @@ class APIClient:
|
|
|
71
74
|
def products(
|
|
72
75
|
self, type: PRODUCT_TYPE | list[PRODUCT_TYPE] | None = None
|
|
73
76
|
) -> list[Product]:
|
|
74
|
-
type =
|
|
77
|
+
type = _validate_type(type, PRODUCT_TYPE)
|
|
75
78
|
data = self._get("products")
|
|
76
79
|
if type is not None:
|
|
77
80
|
data = [obj for obj in data if any(t in obj["type"] for t in type)]
|
|
@@ -121,14 +124,18 @@ class APIClient:
|
|
|
121
124
|
def file(
|
|
122
125
|
self,
|
|
123
126
|
uuid: str | UUID,
|
|
124
|
-
) ->
|
|
127
|
+
) -> ExtendedProductMetadata:
|
|
125
128
|
file_res = self._get(f"files/{uuid}")[0]
|
|
126
129
|
if file_res.get("instrument") is not None:
|
|
127
130
|
instrument_uuid = file_res["instrument"]["uuid"]
|
|
128
131
|
instrument_res = self._get(f"instrument-pids/{instrument_uuid}")[0]
|
|
129
132
|
else:
|
|
130
133
|
instrument_res = None
|
|
131
|
-
|
|
134
|
+
obj = _build_meta_objects([file_res], instrument_res)[0]
|
|
135
|
+
return ExtendedProductMetadata(
|
|
136
|
+
**_asdict_shallow(obj),
|
|
137
|
+
software=tuple(_build_objects(file_res["software"], Software)),
|
|
138
|
+
)
|
|
132
139
|
|
|
133
140
|
def versions(self, uuid: str | UUID) -> list[VersionMetadata]:
|
|
134
141
|
payload = {"properties": ["pid", "dvasId", "legacy", "size", "checksum"]}
|
|
@@ -271,12 +278,12 @@ class APIClient:
|
|
|
271
278
|
|
|
272
279
|
def moving_site_mean_location(
|
|
273
280
|
self, site_id: str, date: datetime.date | str
|
|
274
|
-
) ->
|
|
281
|
+
) -> MeanLocation:
|
|
275
282
|
if not isinstance(date, datetime.date):
|
|
276
283
|
date = datetime.date.fromisoformat(date)
|
|
277
284
|
payload = {"date": date}
|
|
278
285
|
res = self._get(f"sites/{site_id}/locations", params=payload)[0]
|
|
279
|
-
return
|
|
286
|
+
return MeanLocation(
|
|
280
287
|
time=date,
|
|
281
288
|
latitude=res["latitude"],
|
|
282
289
|
longitude=res["longitude"],
|
|
@@ -284,13 +291,13 @@ class APIClient:
|
|
|
284
291
|
|
|
285
292
|
def moving_site_locations(
|
|
286
293
|
self, site_id: str, date: datetime.date | str
|
|
287
|
-
) -> list[
|
|
294
|
+
) -> list[RawLocation]:
|
|
288
295
|
if not isinstance(date, datetime.date):
|
|
289
296
|
date = datetime.date.fromisoformat(date)
|
|
290
297
|
payload = {"date": date, "raw": "1"}
|
|
291
298
|
locations = self._get(f"sites/{site_id}/locations", params=payload)
|
|
292
299
|
return [
|
|
293
|
-
|
|
300
|
+
RawLocation(
|
|
294
301
|
time=_parse_datetime(location["date"]),
|
|
295
302
|
latitude=location["latitude"],
|
|
296
303
|
longitude=location["longitude"],
|
|
@@ -482,7 +489,9 @@ def _parse_datetime_param(
|
|
|
482
489
|
}
|
|
483
490
|
for fmt, unit in patterns:
|
|
484
491
|
try:
|
|
485
|
-
start_date = datetime.datetime.strptime(dt, fmt)
|
|
492
|
+
start_date = datetime.datetime.strptime(dt, fmt).replace(
|
|
493
|
+
tzinfo=datetime.timezone.utc
|
|
494
|
+
)
|
|
486
495
|
except ValueError:
|
|
487
496
|
continue
|
|
488
497
|
if unit == "years":
|
|
@@ -524,17 +533,12 @@ def _build_meta_objects(
|
|
|
524
533
|
field_names = (
|
|
525
534
|
{f.name for f in fields(ProductMetadata)}
|
|
526
535
|
- CONVERTED
|
|
527
|
-
- {"product", "instrument", "model", "site"}
|
|
536
|
+
- {"product", "instrument", "model", "site", "software"}
|
|
528
537
|
)
|
|
529
538
|
return [
|
|
530
539
|
ProductMetadata(
|
|
531
540
|
**{_to_snake(k): v for k, v in obj.items() if _to_snake(k) in field_names},
|
|
532
|
-
product=Product
|
|
533
|
-
id=obj["product"]["id"],
|
|
534
|
-
human_readable_name=obj["product"]["humanReadableName"],
|
|
535
|
-
type=obj["product"]["type"],
|
|
536
|
-
experimental=obj["product"]["experimental"],
|
|
537
|
-
),
|
|
541
|
+
product=_build_object(obj["product"], Product),
|
|
538
542
|
instrument=_create_instrument_object(instrument_meta or obj["instrument"])
|
|
539
543
|
if instrument_meta or "instrument" in obj and obj["instrument"]
|
|
540
544
|
else None,
|
|
@@ -664,9 +668,13 @@ def _make_session() -> requests.Session:
|
|
|
664
668
|
|
|
665
669
|
def _parse_datetime(dt: str) -> datetime.datetime:
|
|
666
670
|
try:
|
|
667
|
-
return datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
671
|
+
return datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S.%fZ").replace(
|
|
672
|
+
tzinfo=datetime.timezone.utc
|
|
673
|
+
)
|
|
668
674
|
except ValueError:
|
|
669
|
-
return datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%SZ")
|
|
675
|
+
return datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%SZ").replace(
|
|
676
|
+
tzinfo=datetime.timezone.utc
|
|
677
|
+
)
|
|
670
678
|
|
|
671
679
|
|
|
672
680
|
def _check_params(params: dict, ignore: tuple = ()) -> None:
|
|
@@ -674,7 +682,11 @@ def _check_params(params: dict, ignore: tuple = ()) -> None:
|
|
|
674
682
|
raise TypeError("At least one of the parameters must be set.")
|
|
675
683
|
|
|
676
684
|
|
|
677
|
-
def
|
|
685
|
+
def _asdict_shallow(obj) -> dict:
|
|
686
|
+
return dict((field.name, getattr(obj, field.name)) for field in fields(obj))
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def _validate_type(type, values) -> list | None:
|
|
678
690
|
if type is not None:
|
|
679
691
|
if not isinstance(type, str | list):
|
|
680
692
|
raise ValueError(f"Invalid type: {type}")
|
|
@@ -3,14 +3,22 @@ import uuid
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import Literal
|
|
5
5
|
|
|
6
|
-
SITE_TYPE = Literal["cloudnet", "model", "hidden", "campaign"]
|
|
6
|
+
SITE_TYPE = Literal["cloudnet", "model", "hidden", "campaign", "mobile"]
|
|
7
7
|
PRODUCT_TYPE = Literal["instrument", "geophysical", "evaluation", "model"]
|
|
8
8
|
STATUS = Literal["created", "uploaded", "processed", "invalid"]
|
|
9
|
+
TIMELINESS = Literal["rrt", "nrt", "scheduled"]
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@dataclass(frozen=True, slots=True)
|
|
12
|
-
class
|
|
13
|
-
time: datetime.
|
|
13
|
+
class MeanLocation:
|
|
14
|
+
time: datetime.date
|
|
15
|
+
latitude: float
|
|
16
|
+
longitude: float
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class RawLocation:
|
|
21
|
+
time: datetime.datetime
|
|
14
22
|
latitude: float
|
|
15
23
|
longitude: float
|
|
16
24
|
|
|
@@ -74,6 +82,14 @@ class Model:
|
|
|
74
82
|
forecast_end: int | None
|
|
75
83
|
|
|
76
84
|
|
|
85
|
+
@dataclass(frozen=True, slots=True)
|
|
86
|
+
class Software:
|
|
87
|
+
id: str
|
|
88
|
+
version: str
|
|
89
|
+
title: str
|
|
90
|
+
url: str
|
|
91
|
+
|
|
92
|
+
|
|
77
93
|
@dataclass(frozen=True, slots=True)
|
|
78
94
|
class Metadata:
|
|
79
95
|
uuid: uuid.UUID
|
|
@@ -111,12 +127,17 @@ class ProductMetadata(Metadata):
|
|
|
111
127
|
dvas_id: str | None
|
|
112
128
|
error_level: str | None
|
|
113
129
|
coverage: float
|
|
114
|
-
timeliness:
|
|
130
|
+
timeliness: TIMELINESS
|
|
115
131
|
format: str
|
|
116
132
|
start_time: datetime.datetime | None
|
|
117
133
|
stop_time: datetime.datetime | None
|
|
118
134
|
|
|
119
135
|
|
|
136
|
+
@dataclass(frozen=True, slots=True)
|
|
137
|
+
class ExtendedProductMetadata(ProductMetadata):
|
|
138
|
+
software: tuple[Software, ...]
|
|
139
|
+
|
|
140
|
+
|
|
120
141
|
@dataclass(frozen=True, slots=True)
|
|
121
142
|
class VersionMetadata:
|
|
122
143
|
uuid: uuid.UUID
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.12.5"
|
|
@@ -13,10 +13,11 @@ from cloudnet_api_client.containers import (
|
|
|
13
13
|
ExtendedInstrument,
|
|
14
14
|
ExtendedProduct,
|
|
15
15
|
Instrument,
|
|
16
|
-
|
|
16
|
+
MeanLocation,
|
|
17
17
|
Model,
|
|
18
18
|
Product,
|
|
19
19
|
ProductMetadata,
|
|
20
|
+
RawLocation,
|
|
20
21
|
RawMetadata,
|
|
21
22
|
Site,
|
|
22
23
|
VersionMetadata,
|
|
@@ -128,7 +129,7 @@ class TestSites:
|
|
|
128
129
|
|
|
129
130
|
def test_moving_site_mean_location(self, client: APIClient):
|
|
130
131
|
location = client.moving_site_mean_location("boaty", "2022-01-01")
|
|
131
|
-
assert isinstance(location,
|
|
132
|
+
assert isinstance(location, MeanLocation)
|
|
132
133
|
assert location.time == datetime.date(2022, 1, 1)
|
|
133
134
|
assert round(location.latitude) == 60
|
|
134
135
|
assert round(location.longitude) == 25
|
|
@@ -136,12 +137,22 @@ class TestSites:
|
|
|
136
137
|
def test_moving_site_locations(self, client: APIClient):
|
|
137
138
|
locations = client.moving_site_locations("boaty", "2022-01-01")
|
|
138
139
|
assert isinstance(locations, list)
|
|
139
|
-
assert all(isinstance(loc,
|
|
140
|
+
assert all(isinstance(loc, RawLocation) for loc in locations)
|
|
141
|
+
assert all(loc.time.date().isoformat() == "2022-01-01" for loc in locations)
|
|
140
142
|
|
|
141
143
|
def test_invalid_site_id(self, client: APIClient):
|
|
142
144
|
with pytest.raises(CloudnetAPIError):
|
|
143
145
|
client.site("invalid-site-id")
|
|
144
146
|
|
|
147
|
+
def test_sites_are_hashable(self, client: APIClient):
|
|
148
|
+
sites = client.sites()
|
|
149
|
+
for site in sites:
|
|
150
|
+
hash(site)
|
|
151
|
+
|
|
152
|
+
def test_site_is_hashable(self, client: APIClient):
|
|
153
|
+
site = client.site("bucharest")
|
|
154
|
+
hash(site)
|
|
155
|
+
|
|
145
156
|
|
|
146
157
|
class TestProducts:
|
|
147
158
|
def test_products_route(self, client: APIClient):
|
|
@@ -200,6 +211,15 @@ class TestProducts:
|
|
|
200
211
|
with pytest.raises(CloudnetAPIError):
|
|
201
212
|
client.product("invalid-product-id")
|
|
202
213
|
|
|
214
|
+
def test_products_are_hashable(self, client: APIClient):
|
|
215
|
+
products = client.products()
|
|
216
|
+
for product in products:
|
|
217
|
+
hash(product)
|
|
218
|
+
|
|
219
|
+
def test_product_is_hashable(self, client: APIClient):
|
|
220
|
+
product = client.product("categorize")
|
|
221
|
+
hash(product)
|
|
222
|
+
|
|
203
223
|
|
|
204
224
|
class TestModels:
|
|
205
225
|
def test_models_route(self, client: APIClient):
|
|
@@ -216,6 +236,15 @@ class TestModels:
|
|
|
216
236
|
with pytest.raises(CloudnetAPIError):
|
|
217
237
|
client.model("invalid-model-id")
|
|
218
238
|
|
|
239
|
+
def test_models_are_hashable(self, client: APIClient):
|
|
240
|
+
models = client.models()
|
|
241
|
+
for model in models:
|
|
242
|
+
hash(model)
|
|
243
|
+
|
|
244
|
+
def test_model_is_hashable(self, client: APIClient):
|
|
245
|
+
model = client.model("arpege-12-23")
|
|
246
|
+
hash(model)
|
|
247
|
+
|
|
219
248
|
|
|
220
249
|
class TestInstruments:
|
|
221
250
|
def test_instruments_route(self, client: APIClient):
|
|
@@ -241,6 +270,15 @@ class TestInstruments:
|
|
|
241
270
|
with pytest.raises(CloudnetAPIError):
|
|
242
271
|
client.instrument("invalid-instrument-id")
|
|
243
272
|
|
|
273
|
+
def test_instruments_are_hashable(self, client: APIClient):
|
|
274
|
+
instruments = client.instruments()
|
|
275
|
+
for instrument in instruments:
|
|
276
|
+
hash(instrument)
|
|
277
|
+
|
|
278
|
+
def test_instrument_is_hashable(self, client: APIClient):
|
|
279
|
+
instrument = client.instrument("12da536f-0d07-41ea-9ced-f6cdeb97198b")
|
|
280
|
+
hash(instrument)
|
|
281
|
+
|
|
244
282
|
|
|
245
283
|
class TestProductFiles:
|
|
246
284
|
def test_file_route_with_geophysical_product(self, client: APIClient):
|
|
@@ -295,6 +333,11 @@ class TestProductFiles:
|
|
|
295
333
|
with pytest.raises(CloudnetAPIError):
|
|
296
334
|
client.files(site_id="invalid-site")
|
|
297
335
|
|
|
336
|
+
def test_file_is_hashable(self, client: APIClient):
|
|
337
|
+
uuid = "8dcc865c-6920-49ce-a627-de045ec896e8"
|
|
338
|
+
meta = client.file(uuid)
|
|
339
|
+
hash(meta)
|
|
340
|
+
|
|
298
341
|
|
|
299
342
|
class TestRawFiles:
|
|
300
343
|
def test_filter_by_site_and_date(self, client: APIClient):
|
|
@@ -356,6 +399,11 @@ class TestRawFiles:
|
|
|
356
399
|
with pytest.raises(CloudnetAPIError):
|
|
357
400
|
client.raw_files(site_id="invalid-site")
|
|
358
401
|
|
|
402
|
+
def test_raw_files_are_hashable(self, client: APIClient):
|
|
403
|
+
meta = client.raw_files(date_from="2025-08-01", date_to="2025-08-08")
|
|
404
|
+
for m in meta:
|
|
405
|
+
hash(m)
|
|
406
|
+
|
|
359
407
|
|
|
360
408
|
class TestDateParameterHandling:
|
|
361
409
|
"""Test various date parameter formats and edge cases."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.12.3"
|
|
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
|
{cloudnet_api_client-0.12.3 → cloudnet_api_client-0.12.5}/tests/data/20250803_JOYCE_WST_01m.dat
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|