cloudnet-api-client 0.3.0__tar.gz → 0.4.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.
@@ -5,6 +5,15 @@ 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.4.1 – 2025-04-03
9
+
10
+ - Fix types
11
+
12
+ ## 0.4.0 – 2025-04-03
13
+
14
+ - Move download function to APIClient class
15
+ - Add status parameter
16
+
8
17
  ## 0.3.0 – 2025-04-02
9
18
 
10
19
  - Move `filename_prefix` and `filename_suffix` parameters
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnet-api-client
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: Cloudnet API client
5
5
  Author-email: Simo Tukiainen <simo.tukiainen@fmi.fi>
6
6
  License-File: LICENSE
@@ -49,10 +49,10 @@ sites = client.sites(type="cloudnet")
49
49
  products = client.products()
50
50
 
51
51
  metadata = client.metadata("hyytiala", "2021-01-01", product=["mwr", "radar"])
52
- cac.download(metadata, "data/")
52
+ client.download(metadata, "data/")
53
53
 
54
54
  raw_metadata = client.raw_metadata("granada", date="2024-01", instrument_id="parsivel")
55
- cac.download(raw_metadata, "data_raw/")
55
+ client.download(raw_metadata, "data_raw/")
56
56
  ```
57
57
 
58
58
  ## Documentation
@@ -78,6 +78,7 @@ Parameters:
78
78
  | show_legacy\* | `bool` | `False` | |
79
79
  | filename_prefix\*\* | `str` or `list[str]` | `None` | "stare" |
80
80
  | filename_suffix\*\* | `str` or `list[str]` | `None` | ".lv1" |
81
+ | status\*\* | `str` or `list[str]` | `None` | "created", "uploaded", "processed" or "invalid" |
81
82
 
82
83
  \* = only in `metadata()`
83
84
 
@@ -110,13 +111,15 @@ Additional filtering of fetched metadata.
110
111
 
111
112
  Parameters:
112
113
 
113
- | name | type | default |
114
- | ------------------ | ---------------------------------------------- | ------- |
115
- | metadata | `list[RawMetadata]` or `list[ProductMetadata]` | |
116
- | include_pattern | `str` | `None` |
117
- | exclude_pattern | `str` | `None` |
118
- | include_tag_subset | `set[str]` | `None` |
119
- | exclude_tag_subset | `set[str]` | `None` |
114
+ | name | type | default |
115
+ | -------------------- | ---------------------------------------------- | ------- |
116
+ | metadata | `list[RawMetadata]` or `list[ProductMetadata]` | |
117
+ | include_pattern | `str` | `None` |
118
+ | exclude_pattern | `str` | `None` |
119
+ | include_tag_subset\* | `set[str]` | `None` |
120
+ | exclude_tag_subset\* | `set[str]` | `None` |
121
+
122
+ \* = only with `RawMetadata`
120
123
 
121
124
  ### `APIClient().sites()` &rarr; `list[Site]`
122
125
 
@@ -142,7 +145,7 @@ Parameters:
142
145
 
143
146
  Fetch cloudnet instruments.
144
147
 
145
- ### `cloudnet_api_client.download(list[Metadata])` &rarr; `list[Path]`
148
+ ### `APIClient().download(list[Metadata])` &rarr; `list[Path]`
146
149
 
147
150
  Download files from the fetched metadata.
148
151
 
@@ -22,10 +22,10 @@ sites = client.sites(type="cloudnet")
22
22
  products = client.products()
23
23
 
24
24
  metadata = client.metadata("hyytiala", "2021-01-01", product=["mwr", "radar"])
25
- cac.download(metadata, "data/")
25
+ client.download(metadata, "data/")
26
26
 
27
27
  raw_metadata = client.raw_metadata("granada", date="2024-01", instrument_id="parsivel")
28
- cac.download(raw_metadata, "data_raw/")
28
+ client.download(raw_metadata, "data_raw/")
29
29
  ```
30
30
 
31
31
  ## Documentation
@@ -51,6 +51,7 @@ Parameters:
51
51
  | show_legacy\* | `bool` | `False` | |
52
52
  | filename_prefix\*\* | `str` or `list[str]` | `None` | "stare" |
53
53
  | filename_suffix\*\* | `str` or `list[str]` | `None` | ".lv1" |
54
+ | status\*\* | `str` or `list[str]` | `None` | "created", "uploaded", "processed" or "invalid" |
54
55
 
55
56
  \* = only in `metadata()`
56
57
 
@@ -83,13 +84,15 @@ Additional filtering of fetched metadata.
83
84
 
84
85
  Parameters:
85
86
 
86
- | name | type | default |
87
- | ------------------ | ---------------------------------------------- | ------- |
88
- | metadata | `list[RawMetadata]` or `list[ProductMetadata]` | |
89
- | include_pattern | `str` | `None` |
90
- | exclude_pattern | `str` | `None` |
91
- | include_tag_subset | `set[str]` | `None` |
92
- | exclude_tag_subset | `set[str]` | `None` |
87
+ | name | type | default |
88
+ | -------------------- | ---------------------------------------------- | ------- |
89
+ | metadata | `list[RawMetadata]` or `list[ProductMetadata]` | |
90
+ | include_pattern | `str` | `None` |
91
+ | exclude_pattern | `str` | `None` |
92
+ | include_tag_subset\* | `set[str]` | `None` |
93
+ | exclude_tag_subset\* | `set[str]` | `None` |
94
+
95
+ \* = only with `RawMetadata`
93
96
 
94
97
  ### `APIClient().sites()` &rarr; `list[Site]`
95
98
 
@@ -115,7 +118,7 @@ Parameters:
115
118
 
116
119
  Fetch cloudnet instruments.
117
120
 
118
- ### `cloudnet_api_client.download(list[Metadata])` &rarr; `list[Path]`
121
+ ### `APIClient().download(list[Metadata])` &rarr; `list[Path]`
119
122
 
120
123
  Download files from the fetched metadata.
121
124
 
@@ -0,0 +1 @@
1
+ from .client import APIClient as APIClient
@@ -1,7 +1,12 @@
1
+ import asyncio
1
2
  import calendar
2
3
  import datetime
4
+ import os
3
5
  import re
6
+ import uuid
4
7
  from dataclasses import fields, is_dataclass
8
+ from os import PathLike
9
+ from pathlib import Path
5
10
  from typing import TypeVar, cast
6
11
  from urllib.parse import urljoin
7
12
 
@@ -12,15 +17,18 @@ from urllib3.util.retry import Retry
12
17
  from cloudnet_api_client.containers import (
13
18
  PRODUCT_TYPE,
14
19
  SITE_TYPE,
20
+ STATUS,
15
21
  Instrument,
16
- Metadata,
17
22
  Product,
18
23
  ProductMetadata,
19
24
  RawMetadata,
20
25
  Site,
21
26
  )
27
+ from cloudnet_api_client.dl import download_files
22
28
 
23
29
  T = TypeVar("T")
30
+ MetadataList = list[ProductMetadata] | list[RawMetadata]
31
+ TMetadata = TypeVar("TMetadata", ProductMetadata, RawMetadata)
24
32
  DateParam = str | datetime.date | None
25
33
  DateTimeParam = str | datetime.datetime | datetime.date | None
26
34
  QueryParam = str | list[str] | None
@@ -58,7 +66,7 @@ class APIClient:
58
66
  instrument_id=obj["instrument"]["id"],
59
67
  model=obj["model"],
60
68
  type=obj["type"],
61
- uuid=obj["uuid"],
69
+ uuid=uuid.UUID(obj["uuid"]),
62
70
  pid=obj["pid"],
63
71
  owners=obj["owners"],
64
72
  serial_number=obj["serialNumber"],
@@ -124,6 +132,7 @@ class APIClient:
124
132
  instrument_pid: QueryParam = None,
125
133
  filename_prefix: QueryParam = None,
126
134
  filename_suffix: QueryParam = None,
135
+ status: STATUS | list[STATUS] | None = None,
127
136
  ) -> list[RawMetadata]:
128
137
  params = {
129
138
  "site": site_id,
@@ -131,6 +140,7 @@ class APIClient:
131
140
  "instrumentPid": instrument_pid,
132
141
  "filenamePrefix": filename_prefix,
133
142
  "filenameSuffix": filename_suffix,
143
+ "status": status,
134
144
  }
135
145
  _add_date_params(
136
146
  params, date, date_from, date_to, updated_at, updated_at_from, updated_at_to
@@ -138,14 +148,43 @@ class APIClient:
138
148
  res = self._get_response("raw-files", params)
139
149
  return _build_raw_meta_objects(res)
140
150
 
151
+ def download(
152
+ self,
153
+ metadata: MetadataList,
154
+ output_directory: str | PathLike,
155
+ concurrency_limit: int = 5,
156
+ progress: bool | None = None,
157
+ ) -> list[Path]:
158
+ return asyncio.run(
159
+ self.adownload(metadata, output_directory, concurrency_limit, progress)
160
+ )
161
+
162
+ async def adownload(
163
+ self,
164
+ metadata: MetadataList,
165
+ output_directory: str | PathLike,
166
+ concurrency_limit: int = 5,
167
+ progress: bool | None = None,
168
+ ) -> list[Path]:
169
+ disable_progress = not progress if progress is not None else None
170
+ output_directory = Path(output_directory).resolve()
171
+ os.makedirs(output_directory, exist_ok=True)
172
+ return await download_files(
173
+ self.base_url,
174
+ metadata,
175
+ output_directory,
176
+ concurrency_limit,
177
+ disable_progress,
178
+ )
179
+
141
180
  @staticmethod
142
181
  def filter(
143
- metadata: list[Metadata],
182
+ metadata: list[TMetadata],
144
183
  include_pattern: str | None = None,
145
184
  exclude_pattern: str | None = None,
146
185
  include_tag_subset: set[str] | None = None,
147
186
  exclude_tag_subset: set[str] | None = None,
148
- ) -> list[Metadata]:
187
+ ) -> list[TMetadata]:
149
188
  if include_pattern:
150
189
  metadata = [
151
190
  m for m in metadata if re.search(include_pattern, m.filename, re.I)
@@ -294,8 +333,11 @@ def _build_objects(res: list[dict], object_type: type[T]) -> list[T]:
294
333
  return cast(list[T], objects)
295
334
 
296
335
 
336
+ CONVERTED = {"measurement_date", "created_at", "updated_at", "size", "uuid"}
337
+
338
+
297
339
  def _build_meta_objects(res: list[dict]) -> list[ProductMetadata]:
298
- field_names = {f.name for f in fields(ProductMetadata)} - {"product"}
340
+ field_names = {f.name for f in fields(ProductMetadata)} - CONVERTED - {"product"}
299
341
  return [
300
342
  ProductMetadata(
301
343
  **{_to_snake(k): v for k, v in obj.items() if _to_snake(k) in field_names},
@@ -305,13 +347,18 @@ def _build_meta_objects(res: list[dict]) -> list[ProductMetadata]:
305
347
  type=[obj["product"]["type"][1:-1]],
306
348
  experimental=obj["product"]["experimental"],
307
349
  ),
350
+ measurement_date=datetime.date.fromisoformat(obj["measurementDate"]),
351
+ created_at=datetime.datetime.fromisoformat(obj["createdAt"]),
352
+ updated_at=datetime.datetime.fromisoformat(obj["updatedAt"]),
353
+ size=int(obj["size"]),
354
+ uuid=uuid.UUID(obj["uuid"]),
308
355
  )
309
356
  for obj in res
310
357
  ]
311
358
 
312
359
 
313
360
  def _build_raw_meta_objects(res: list[dict]) -> list[RawMetadata]:
314
- field_names = {f.name for f in fields(RawMetadata)} - {"instrument"}
361
+ field_names = {f.name for f in fields(RawMetadata)} - CONVERTED - {"instrument"}
315
362
  return [
316
363
  RawMetadata(
317
364
  **{_to_snake(k): v for k, v in obj.items() if _to_snake(k) in field_names},
@@ -319,12 +366,17 @@ def _build_raw_meta_objects(res: list[dict]) -> list[RawMetadata]:
319
366
  instrument_id=obj["instrumentInfo"]["instrumentId"],
320
367
  model=obj["instrumentInfo"]["model"],
321
368
  type=obj["instrumentInfo"]["type"],
322
- uuid=obj["instrumentInfo"]["uuid"],
369
+ uuid=uuid.UUID(obj["instrumentInfo"]["uuid"]),
323
370
  pid=obj["instrumentInfo"]["pid"],
324
371
  owners=obj["instrumentInfo"]["owners"],
325
372
  serial_number=obj["instrumentInfo"]["serialNumber"],
326
373
  name=obj["instrumentInfo"]["name"],
327
374
  ),
375
+ measurement_date=datetime.date.fromisoformat(obj["measurementDate"]),
376
+ created_at=datetime.datetime.fromisoformat(obj["createdAt"]),
377
+ updated_at=datetime.datetime.fromisoformat(obj["updatedAt"]),
378
+ size=int(obj["size"]),
379
+ uuid=uuid.UUID(obj["uuid"]),
328
380
  )
329
381
  for obj in res
330
382
  ]
@@ -1,9 +1,11 @@
1
+ import datetime
1
2
  import uuid
2
3
  from dataclasses import dataclass
3
4
  from typing import Literal
4
5
 
5
6
  SITE_TYPE = Literal["cloudnet", "model", "hidden", "campaign"]
6
7
  PRODUCT_TYPE = Literal["instrument", "geophysical", "evaluation", "model"]
8
+ STATUS = Literal["created", "uploaded", "processed", "invalid"]
7
9
 
8
10
 
9
11
  @dataclass(frozen=True, slots=True)
@@ -50,15 +52,15 @@ class Metadata:
50
52
  checksum: str
51
53
  size: int
52
54
  filename: str
53
- measurement_date: str
54
55
  download_url: str
55
- created_at: str
56
- updated_at: str
56
+ measurement_date: datetime.date
57
+ created_at: datetime.datetime
58
+ updated_at: datetime.datetime
57
59
 
58
60
 
59
61
  @dataclass(frozen=True, slots=True)
60
62
  class RawMetadata(Metadata):
61
- status: Literal["created", "uploaded", "processed", "invalid"]
63
+ status: STATUS
62
64
  instrument: Instrument
63
65
  tags: list[str] | None
64
66
 
@@ -1,7 +1,5 @@
1
1
  import asyncio
2
2
  import logging
3
- import os
4
- from os import PathLike
5
3
  from pathlib import Path
6
4
 
7
5
  import aiohttp
@@ -11,36 +9,10 @@ from tqdm.asyncio import tqdm_asyncio
11
9
  from cloudnet_api_client import utils
12
10
  from cloudnet_api_client.containers import ProductMetadata, RawMetadata
13
11
 
14
- MetadataList = list[ProductMetadata] | list[RawMetadata]
15
12
 
16
-
17
- def download(
18
- metadata: MetadataList,
19
- output_directory: str | PathLike,
20
- concurrency_limit: int = 5,
21
- progress: bool | None = None,
22
- ) -> list[Path]:
23
- return asyncio.run(
24
- adownload(metadata, output_directory, concurrency_limit, progress)
25
- )
26
-
27
-
28
- async def adownload(
29
- metadata: MetadataList,
30
- output_directory: str | PathLike,
31
- concurrency_limit: int = 5,
32
- progress: bool | None = None,
33
- ) -> list[Path]:
34
- disable_progress = not progress if progress is not None else None
35
- output_directory = Path(output_directory).resolve()
36
- os.makedirs(output_directory, exist_ok=True)
37
- return await _download_files(
38
- metadata, output_directory, concurrency_limit, disable_progress
39
- )
40
-
41
-
42
- async def _download_files(
43
- metadata: MetadataList,
13
+ async def download_files(
14
+ base_url: str,
15
+ metadata: list[ProductMetadata] | list[RawMetadata],
44
16
  output_path: Path,
45
17
  concurrency_limit: int,
46
18
  disable_progress: bool | None,
@@ -50,6 +22,7 @@ async def _download_files(
50
22
  async with aiohttp.ClientSession() as session:
51
23
  tasks = []
52
24
  for meta in metadata:
25
+ download_url = f"{base_url}{meta.download_url.split('/api/')[-1]}"
53
26
  destination = output_path / meta.download_url.split("/")[-1]
54
27
  full_paths.append(destination)
55
28
  if destination.exists() and _file_checksum_matches(meta, destination):
@@ -57,7 +30,7 @@ async def _download_files(
57
30
  continue
58
31
  task = asyncio.create_task(
59
32
  _download_file_with_retries(
60
- session, meta.download_url, destination, semaphore, disable_progress
33
+ session, download_url, destination, semaphore, disable_progress
61
34
  )
62
35
  )
63
36
  tasks.append(task)
@@ -0,0 +1 @@
1
+ __version__ = "0.4.1"
@@ -1,3 +0,0 @@
1
- from .client import APIClient as APIClient
2
- from .dl import adownload as adownload
3
- from .dl import download as download
@@ -1 +0,0 @@
1
- __version__ = "0.3.0"