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.
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/CHANGELOG.md +9 -0
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/PKG-INFO +14 -11
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/README.md +13 -10
- cloudnet_api_client-0.4.1/cloudnet_api_client/__init__.py +1 -0
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/cloudnet_api_client/client.py +59 -7
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/cloudnet_api_client/containers.py +6 -4
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/cloudnet_api_client/dl.py +5 -32
- cloudnet_api_client-0.4.1/cloudnet_api_client/version.py +1 -0
- cloudnet_api_client-0.3.0/cloudnet_api_client/__init__.py +0 -3
- cloudnet_api_client-0.3.0/cloudnet_api_client/version.py +0 -1
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/.github/workflows/publish.yml +0 -0
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/.github/workflows/test.yml +0 -0
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/.gitignore +0 -0
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/.pre-commit-config.yaml +0 -0
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/LICENSE +0 -0
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/cloudnet_api_client/py.typed +0 -0
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/cloudnet_api_client/utils.py +0 -0
- {cloudnet_api_client-0.3.0 → cloudnet_api_client-0.4.1}/pyproject.toml +0 -0
|
@@ -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
|
+
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
|
-
|
|
52
|
+
client.download(metadata, "data/")
|
|
53
53
|
|
|
54
54
|
raw_metadata = client.raw_metadata("granada", date="2024-01", instrument_id="parsivel")
|
|
55
|
-
|
|
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
|
|
114
|
-
|
|
|
115
|
-
| metadata
|
|
116
|
-
| include_pattern
|
|
117
|
-
| exclude_pattern
|
|
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()` → `list[Site]`
|
|
122
125
|
|
|
@@ -142,7 +145,7 @@ Parameters:
|
|
|
142
145
|
|
|
143
146
|
Fetch cloudnet instruments.
|
|
144
147
|
|
|
145
|
-
### `
|
|
148
|
+
### `APIClient().download(list[Metadata])` → `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
|
-
|
|
25
|
+
client.download(metadata, "data/")
|
|
26
26
|
|
|
27
27
|
raw_metadata = client.raw_metadata("granada", date="2024-01", instrument_id="parsivel")
|
|
28
|
-
|
|
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
|
|
87
|
-
|
|
|
88
|
-
| metadata
|
|
89
|
-
| include_pattern
|
|
90
|
-
| exclude_pattern
|
|
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()` → `list[Site]`
|
|
95
98
|
|
|
@@ -115,7 +118,7 @@ Parameters:
|
|
|
115
118
|
|
|
116
119
|
Fetch cloudnet instruments.
|
|
117
120
|
|
|
118
|
-
### `
|
|
121
|
+
### `APIClient().download(list[Metadata])` → `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[
|
|
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[
|
|
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
|
-
|
|
56
|
-
|
|
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:
|
|
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
|
-
|
|
18
|
-
metadata:
|
|
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,
|
|
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 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|