cloudnet-api-client 0.6.0__tar.gz → 0.7.0__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,11 @@ 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.7.0 – 2025-05-06
9
+
10
+ - Validate checksum optionally
11
+ - Make site optional
12
+
8
13
  ## 0.6.0 – 2025-04-22
9
14
 
10
15
  - Fix datetime parsing for Python 3.10
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnet-api-client
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Cloudnet API client
5
5
  Author-email: Simo Tukiainen <simo.tukiainen@fmi.fi>
6
6
  License-File: LICENSE
@@ -65,7 +65,7 @@ Parameters:
65
65
 
66
66
  | name | type | default | example |
67
67
  | ------------------- | --------------------------- | ------- | ---------------------------------------------------- |
68
- | site_id | `str` | | "hyytiala" |
68
+ | site_id | `str` or `list[str]` | `None` | "hyytiala" |
69
69
  | date | `str` or `date` | `None` | "2024-01-01" |
70
70
  | date_from | `str` or `date` | `None` | "2025-01-01" |
71
71
  | date_to | `str` or `date` | `None` | "2025-01-01" |
@@ -158,6 +158,7 @@ Parameters:
158
158
  | output_directory | `PathLike` or `str` | |
159
159
  | concurrency_limit | `int` | 5 |
160
160
  | progress | `bool` or `None` | `None` |
161
+ | validate_checksum | `bool` | `False` |
161
162
 
162
163
  There's also an asynchronous version of this function:
163
164
  `cloudnet_api_client.adownload`. It's useful for usage inside Jupyter notebook.
@@ -38,7 +38,7 @@ Parameters:
38
38
 
39
39
  | name | type | default | example |
40
40
  | ------------------- | --------------------------- | ------- | ---------------------------------------------------- |
41
- | site_id | `str` | | "hyytiala" |
41
+ | site_id | `str` or `list[str]` | `None` | "hyytiala" |
42
42
  | date | `str` or `date` | `None` | "2024-01-01" |
43
43
  | date_from | `str` or `date` | `None` | "2025-01-01" |
44
44
  | date_to | `str` or `date` | `None` | "2025-01-01" |
@@ -131,6 +131,7 @@ Parameters:
131
131
  | output_directory | `PathLike` or `str` | |
132
132
  | concurrency_limit | `int` | 5 |
133
133
  | progress | `bool` or `None` | `None` |
134
+ | validate_checksum | `bool` | `False` |
134
135
 
135
136
  There's also an asynchronous version of this function:
136
137
  `cloudnet_api_client.adownload`. It's useful for usage inside Jupyter notebook.
@@ -85,7 +85,7 @@ class APIClient:
85
85
 
86
86
  def metadata(
87
87
  self,
88
- site_id: str,
88
+ site_id: QueryParam = None,
89
89
  date: DateParam = None,
90
90
  date_from: DateParam = None,
91
91
  date_to: DateParam = None,
@@ -108,6 +108,9 @@ class APIClient:
108
108
  _add_date_params(
109
109
  params, date, date_from, date_to, updated_at, updated_at_from, updated_at_to
110
110
  )
111
+
112
+ _check_params(params, ("showLegacy",))
113
+
111
114
  no_instrument = not instrument_id or instrument_pid
112
115
  if no_instrument and (not product and model_id):
113
116
  files_res = []
@@ -129,7 +132,7 @@ class APIClient:
129
132
 
130
133
  def raw_metadata(
131
134
  self,
132
- site_id: str,
135
+ site_id: QueryParam = None,
133
136
  date: DateParam = None,
134
137
  date_from: DateParam = None,
135
138
  date_to: DateParam = None,
@@ -181,6 +184,9 @@ class APIClient:
181
184
  _add_date_params(
182
185
  params, date, date_from, date_to, updated_at, updated_at_from, updated_at_to
183
186
  )
187
+
188
+ _check_params(params)
189
+
184
190
  res = self._get_response("raw-model-files", params)
185
191
  return _build_raw_model_meta_objects(res)
186
192
 
@@ -190,9 +196,16 @@ class APIClient:
190
196
  output_directory: str | PathLike,
191
197
  concurrency_limit: int = 5,
192
198
  progress: bool | None = None,
199
+ validate_checksum: bool = False,
193
200
  ) -> list[Path]:
194
201
  return asyncio.run(
195
- self.adownload(metadata, output_directory, concurrency_limit, progress)
202
+ self.adownload(
203
+ metadata,
204
+ output_directory,
205
+ concurrency_limit,
206
+ progress,
207
+ validate_checksum,
208
+ )
196
209
  )
197
210
 
198
211
  async def adownload(
@@ -201,6 +214,7 @@ class APIClient:
201
214
  output_directory: str | PathLike,
202
215
  concurrency_limit: int = 5,
203
216
  progress: bool | None = None,
217
+ validate_checksum: bool = False,
204
218
  ) -> list[Path]:
205
219
  disable_progress = not progress if progress is not None else None
206
220
  output_directory = Path(output_directory).resolve()
@@ -211,6 +225,7 @@ class APIClient:
211
225
  output_directory,
212
226
  concurrency_limit,
213
227
  disable_progress,
228
+ validate_checksum,
214
229
  )
215
230
 
216
231
  @staticmethod
@@ -495,3 +510,8 @@ def _make_session() -> requests.Session:
495
510
 
496
511
  def _parse_datetime(dt: str) -> datetime.datetime:
497
512
  return datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S.%fZ")
513
+
514
+
515
+ def _check_params(params: dict, ignore: tuple = ()) -> None:
516
+ if sum(1 for key, value in params.items() if key not in ignore and value) == 0:
517
+ raise TypeError("At least one of the parameters must be set.")
@@ -20,7 +20,9 @@ async def download_files(
20
20
  output_path: Path,
21
21
  concurrency_limit: int,
22
22
  disable_progress: bool | None,
23
+ validate_checksum: bool = False,
23
24
  ) -> list[Path]:
25
+ file_exists = _checksum_matches if validate_checksum else _size_and_name_matches
24
26
  semaphore = asyncio.Semaphore(concurrency_limit)
25
27
  full_paths = []
26
28
  async with aiohttp.ClientSession() as session:
@@ -29,7 +31,7 @@ async def download_files(
29
31
  download_url = f"{base_url}{meta.download_url.split('/api/')[-1]}"
30
32
  destination = output_path / meta.download_url.split("/")[-1]
31
33
  full_paths.append(destination)
32
- if destination.exists() and _file_checksum_matches(meta, destination):
34
+ if destination.exists() and file_exists(meta, destination):
33
35
  logging.debug(f"Already downloaded: {destination}")
34
36
  continue
35
37
  task = asyncio.create_task(
@@ -97,8 +99,17 @@ async def _download_file(
97
99
  logging.debug(f"Downloaded: {destination}")
98
100
 
99
101
 
100
- def _file_checksum_matches(
102
+ def _checksum_matches(
101
103
  meta: ProductMetadata | RawMetadata | RawModelMetadata, destination: Path
102
104
  ) -> bool:
103
105
  fun = utils.sha256sum if isinstance(meta, ProductMetadata) else utils.md5sum
104
106
  return fun(destination) == meta.checksum
107
+
108
+
109
+ def _size_and_name_matches(
110
+ meta: ProductMetadata | RawMetadata | RawModelMetadata, destination: Path
111
+ ) -> bool:
112
+ return (
113
+ destination.stat().st_size == meta.size
114
+ and destination.name == meta.download_url.split("/")[-1]
115
+ )
@@ -0,0 +1 @@
1
+ __version__ = "0.7.0"
@@ -1 +0,0 @@
1
- __version__ = "0.6.0"