cloudnet-api-client 0.12.10__tar.gz → 0.12.11__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 (34) hide show
  1. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/CHANGELOG.md +4 -0
  2. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/PKG-INFO +1 -1
  3. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/cloudnet_api_client/client.py +2 -2
  4. cloudnet_api_client-0.12.11/cloudnet_api_client/version.py +1 -0
  5. cloudnet_api_client-0.12.11/tests/data/20250711_kenttarova_l3-cf_ecmwf.nc +0 -0
  6. cloudnet_api_client-0.12.11/tests/data/20250711_kenttarova_l3-cf_era5-1-12.nc +0 -0
  7. cloudnet_api_client-0.12.11/tests/data/20250711_kenttarova_l3-cf_era5-7-18.nc +0 -0
  8. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/tests/test_client.py +59 -31
  9. cloudnet_api_client-0.12.10/cloudnet_api_client/version.py +0 -1
  10. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/.github/dataportal.env +0 -0
  11. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/.github/db.env +0 -0
  12. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/.github/docker-compose.yml +0 -0
  13. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/.github/initdb.d/init-dbs.sh +0 -0
  14. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/.github/ss.env +0 -0
  15. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/.github/workflows/publish.yml +0 -0
  16. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/.github/workflows/test.yml +0 -0
  17. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/.gitignore +0 -0
  18. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/.pre-commit-config.yaml +0 -0
  19. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/LICENSE +0 -0
  20. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/README.md +0 -0
  21. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/cloudnet_api_client/__init__.py +0 -0
  22. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/cloudnet_api_client/containers.py +0 -0
  23. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/cloudnet_api_client/dl.py +0 -0
  24. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/cloudnet_api_client/py.typed +0 -0
  25. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/cloudnet_api_client/utils.py +0 -0
  26. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/pyproject.toml +0 -0
  27. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/tests/data/20140205_hyytiala_classification.nc +0 -0
  28. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/tests/data/20250801_Magurele_CHM170137_000.nc +0 -0
  29. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/tests/data/20250803_JOYCE_WST_01m.dat +0 -0
  30. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/tests/data/20250808_Granada_CHM170119_0045_000.nc +0 -0
  31. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/tests/data/20250808_hyytiala_iwc-Z-T-method.nc +0 -0
  32. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/tests/data/20250814_bucharest_classification.nc +0 -0
  33. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/tests/data/20250821_limassol_parsivel_41582c49.nc +0 -0
  34. {cloudnet_api_client-0.12.10 → cloudnet_api_client-0.12.11}/tests/data/20250822_leipzig-lim_ecmwf-open.nc +0 -0
@@ -5,6 +5,10 @@ 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.11 – 2026-06-17
9
+
10
+ - Add model_id filtering to files() query
11
+
8
12
  ## 0.12.10 – 2026-06-09
9
13
 
10
14
  - Update site types
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnet-api-client
3
- Version: 0.12.10
3
+ Version: 0.12.11
4
4
  Summary: Cloudnet API client
5
5
  Author-email: Simo Tukiainen <simo.tukiainen@fmi.fi>
6
6
  License-File: LICENSE
@@ -171,6 +171,7 @@ class APIClient:
171
171
  "instrument": instrument_id,
172
172
  "instrumentPid": instrument_pid,
173
173
  "product": product_id,
174
+ "model": model_id,
174
175
  "showLegacy": show_legacy,
175
176
  }
176
177
  if show_legacy is not True:
@@ -181,7 +182,7 @@ class APIClient:
181
182
  params, date, date_from, date_to, updated_at, updated_at_from, updated_at_to
182
183
  )
183
184
 
184
- _check_params({**params, "model": model_id}, ("showLegacy",))
185
+ _check_params(params, ("showLegacy",))
185
186
 
186
187
  no_instrument = instrument_id is None and instrument_pid is None
187
188
 
@@ -199,7 +200,6 @@ class APIClient:
199
200
  for key in ("showLegacy", "product", "instrument", "instrumentPid"):
200
201
  if key in params:
201
202
  del params[key]
202
- params["model"] = model_id
203
203
  files_res += self._get("model-files", params, expected_code=400)
204
204
 
205
205
  return _build_meta_objects(files_res)
@@ -0,0 +1 @@
1
+ __version__ = "0.12.11"
@@ -24,6 +24,16 @@ from cloudnet_api_client.containers import (
24
24
  )
25
25
  from cloudnet_api_client.utils import CloudnetAPIError, md5sum, sha256sum
26
26
 
27
+ # Product file UUIDs (submitted by the files_product fixture).
28
+ CLASSIFICATION_UUID = "8dcc865c-6920-49ce-a627-de045ec896e8"
29
+ PARSIVEL_UUID = "ab872770-9136-4e61-8958-31e62abdfb1b"
30
+ ECMWF_OPEN_UUID = "277d54f0-d376-4448-a784-f1c8b819b46a"
31
+
32
+ # Raw file instrument PIDs (submitted by the files_raw fixture).
33
+ BUCHAREST_CHM15K_PID = "https://hdl.handle.net/21.12132/3.c60c931fac9d43f0"
34
+ GRANADA_CHM15K_PID = "https://hdl.handle.net/21.12132/3.77a75f3b32294855"
35
+ JUELICH_WST_PID = "https://hdl.handle.net/21.12132/3.726b3b29de1949cc"
36
+
27
37
 
28
38
  class RawFile(NamedTuple):
29
39
  filename: str
@@ -35,8 +45,9 @@ class RawFile(NamedTuple):
35
45
 
36
46
  class File(NamedTuple):
37
47
  filename: str
38
- legacy: bool
39
48
  volatile: bool
49
+ legacy: bool = False
50
+ model: str | None = None
40
51
 
41
52
 
42
53
  @pytest.fixture(scope="session")
@@ -62,21 +73,21 @@ def files_raw() -> list[RawFile]:
62
73
  site="bucharest",
63
74
  instrument="chm15k",
64
75
  date="2025-08-01",
65
- pid="https://hdl.handle.net/21.12132/3.c60c931fac9d43f0",
76
+ pid=BUCHAREST_CHM15K_PID,
66
77
  ),
67
78
  RawFile(
68
79
  filename="20250808_Granada_CHM170119_0045_000.nc",
69
80
  site="granada",
70
81
  instrument="chm15k",
71
82
  date="2025-08-08",
72
- pid="https://hdl.handle.net/21.12132/3.77a75f3b32294855",
83
+ pid=GRANADA_CHM15K_PID,
73
84
  ),
74
85
  RawFile(
75
86
  filename="20250803_JOYCE_WST_01m.dat",
76
87
  site="juelich",
77
88
  instrument="weather-station",
78
89
  date="2025-08-01",
79
- pid="https://hdl.handle.net/21.12132/3.726b3b29de1949cc",
90
+ pid=JUELICH_WST_PID,
80
91
  ),
81
92
  ]
82
93
 
@@ -84,11 +95,18 @@ def files_raw() -> list[RawFile]:
84
95
  @pytest.fixture(scope="session")
85
96
  def files_product() -> list[File]:
86
97
  return [
87
- File("20250814_bucharest_classification.nc", legacy=False, volatile=True),
88
- File("20250808_hyytiala_iwc-Z-T-method.nc", legacy=False, volatile=False),
89
- File("20140205_hyytiala_classification.nc", legacy=True, volatile=False),
90
- File("20250821_limassol_parsivel_41582c49.nc", legacy=False, volatile=False),
91
- File("20250822_leipzig-lim_ecmwf-open.nc", legacy=False, volatile=True),
98
+ File("20250814_bucharest_classification.nc", volatile=True),
99
+ File("20250808_hyytiala_iwc-Z-T-method.nc", volatile=False),
100
+ File("20140205_hyytiala_classification.nc", volatile=False, legacy=True),
101
+ File("20250821_limassol_parsivel_41582c49.nc", volatile=False),
102
+ File("20250822_leipzig-lim_ecmwf-open.nc", volatile=True),
103
+ File("20250711_kenttarova_l3-cf_ecmwf.nc", volatile=True, model="ecmwf"),
104
+ File(
105
+ "20250711_kenttarova_l3-cf_era5-1-12.nc", volatile=True, model="era5-1-12"
106
+ ),
107
+ File(
108
+ "20250711_kenttarova_l3-cf_era5-7-18.nc", volatile=True, model="era5-7-18"
109
+ ),
92
110
  ]
93
111
 
94
112
 
@@ -286,34 +304,30 @@ class TestInstruments:
286
304
 
287
305
  class TestProductFiles:
288
306
  def test_file_route_with_geophysical_product(self, client: APIClient):
289
- uuid = "8dcc865c-6920-49ce-a627-de045ec896e8"
290
- meta = client.file(uuid)
307
+ meta = client.file(CLASSIFICATION_UUID)
291
308
  assert isinstance(meta, ProductMetadata)
292
- assert str(meta.uuid) == uuid
309
+ assert str(meta.uuid) == CLASSIFICATION_UUID
293
310
  assert meta.instrument is None
294
311
  assert meta.model is None
295
312
  assert meta.product.id == "classification"
296
313
 
297
314
  def test_file_route_with_instrument_product(self, client: APIClient):
298
- uuid = "ab872770-9136-4e61-8958-31e62abdfb1b"
299
- meta = client.file(uuid)
315
+ meta = client.file(PARSIVEL_UUID)
300
316
  assert meta.model is None
301
317
  assert isinstance(meta.instrument, Instrument)
302
318
  assert meta.instrument.instrument_id == "parsivel"
303
319
 
304
320
  def test_file_route_with_model_product(self, client: APIClient):
305
- uuid = "277d54f0-d376-4448-a784-f1c8b819b46a"
306
- meta = client.file(uuid)
321
+ meta = client.file(ECMWF_OPEN_UUID)
307
322
  assert meta.instrument is None
308
323
  assert isinstance(meta.model, Model)
309
324
  assert meta.model.id == "ecmwf-open"
310
325
 
311
326
  def test_versions_route(self, client: APIClient):
312
- uuid = "8dcc865c-6920-49ce-a627-de045ec896e8"
313
- meta = client.versions(uuid)
327
+ meta = client.versions(CLASSIFICATION_UUID)
314
328
  assert len(meta) == 1
315
329
  assert isinstance(meta[0], VersionMetadata)
316
- assert str(meta[0].uuid) == uuid
330
+ assert str(meta[0].uuid) == CLASSIFICATION_UUID
317
331
 
318
332
  def test_product_option(self, client: APIClient):
319
333
  meta = client.files(site_id="hyytiala", product_id="iwc")
@@ -333,13 +347,28 @@ class TestProductFiles:
333
347
  meta = client.files(instrument_id="parsivel")
334
348
  assert len(meta) == 1
335
349
 
350
+ def test_model_filter_on_l3_product(self, client: APIClient):
351
+ models = ("ecmwf", "era5-1-12", "era5-7-18")
352
+ all_l3 = client.files(site_id="kenttarova", product_id="l3-cf")
353
+ assert {m.model.id for m in all_l3 if m.model} == set(models)
354
+ for model_id in models:
355
+ meta = client.files(
356
+ site_id="kenttarova", product_id="l3-cf", model_id=model_id
357
+ )
358
+ assert len(meta) == 1
359
+ assert meta[0].model is not None
360
+ assert meta[0].model.id == model_id
361
+ no_match = client.files(
362
+ site_id="kenttarova", product_id="l3-cf", model_id="ecmwf-open"
363
+ )
364
+ assert len(no_match) == 0
365
+
336
366
  def test_files_route_with_invalid_input(self, client: APIClient):
337
367
  with pytest.raises(CloudnetAPIError):
338
368
  client.files(site_id="invalid-site")
339
369
 
340
370
  def test_file_is_hashable(self, client: APIClient):
341
- uuid = "8dcc865c-6920-49ce-a627-de045ec896e8"
342
- meta = client.file(uuid)
371
+ meta = client.file(CLASSIFICATION_UUID)
343
372
  hash(meta)
344
373
 
345
374
 
@@ -355,13 +384,11 @@ class TestRawFiles:
355
384
  assert len(meta) == 1
356
385
 
357
386
  def test_filter_by_instrument_pid(self, client: APIClient):
358
- pid = "https://hdl.handle.net/21.12132/3.77a75f3b32294855"
359
- meta = client.raw_files(instrument_pid=pid)
387
+ meta = client.raw_files(instrument_pid=GRANADA_CHM15K_PID)
360
388
  assert len(meta) == 1
361
389
 
362
390
  def test_filter_by_instrument_pid_no_match(self, client: APIClient):
363
- pid = "https://hdl.handle.net/21.12132/3.77a75f3b32294855"
364
- meta = client.raw_files(instrument_pid=pid, date="2022-01-01")
391
+ meta = client.raw_files(instrument_pid=GRANADA_CHM15K_PID, date="2022-01-01")
365
392
  assert len(meta) == 0
366
393
 
367
394
  def test_filter_by_date_range_from(self, client: APIClient):
@@ -390,8 +417,7 @@ class TestRawFiles:
390
417
 
391
418
  def test_instrument_id_vs_pid_exclusivity(self, client: APIClient):
392
419
  meta1 = client.raw_files(instrument_id="chm15k")
393
- pid = "https://hdl.handle.net/21.12132/3.c60c931fac9d43f0"
394
- meta2 = client.raw_files(instrument_pid=pid)
420
+ meta2 = client.raw_files(instrument_pid=BUCHAREST_CHM15K_PID)
395
421
  assert len(meta1) > 1 # Multiple chm15k files
396
422
  assert len(meta2) == 1 # Specific PID
397
423
 
@@ -463,8 +489,7 @@ class TestDownloadingFunctionality:
463
489
  assert paths2[0].stat().st_size == original_size
464
490
 
465
491
  def test_downloading_single_metadata(self, client: APIClient, tmp_path: Path):
466
- uuid = "ab872770-9136-4e61-8958-31e62abdfb1b"
467
- meta = client.file(uuid)
492
+ meta = client.file(PARSIVEL_UUID)
468
493
  paths = client.download(meta, output_directory=tmp_path, progress=False)
469
494
  assert len(paths) == 1
470
495
  assert paths[0].exists()
@@ -534,12 +559,15 @@ def _submit_product_file(backend_url: str, data_path: Path, meta: File):
534
559
  "volatile": meta.volatile,
535
560
  "legacy": meta.legacy,
536
561
  "uuid": str(UUID(nc.file_uuid)),
537
- "pid": nc.pid,
562
+ "pid": getattr(nc, "pid", ""),
538
563
  "instrumentPid": getattr(nc, "instrument_pid", None),
539
564
  "s3key": None,
540
565
  **file_info,
541
566
  }
542
- payload["model"] = product if payload["product"] == "model" else None
567
+ if payload["product"] == "model":
568
+ payload["model"] = product
569
+ else:
570
+ payload["model"] = meta.model
543
571
  url = f"{backend_url}/files/{meta.filename}"
544
572
  res = requests.put(url, json=payload)
545
573
  if res.status_code == 403:
@@ -1 +0,0 @@
1
- __version__ = "0.12.10"