cloudnet-api-client 0.9.2__tar.gz → 0.11.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.
Files changed (30) hide show
  1. cloudnet_api_client-0.11.0/.github/dataportal.env +29 -0
  2. cloudnet_api_client-0.11.0/.github/db.env +4 -0
  3. cloudnet_api_client-0.11.0/.github/docker-compose.yml +45 -0
  4. cloudnet_api_client-0.11.0/.github/initdb.d/init-dbs.sh +9 -0
  5. cloudnet_api_client-0.11.0/.github/ss.env +12 -0
  6. cloudnet_api_client-0.11.0/.github/workflows/test.yml +64 -0
  7. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/.gitignore +1 -0
  8. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/.pre-commit-config.yaml +2 -2
  9. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/CHANGELOG.md +10 -0
  10. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/PKG-INFO +26 -3
  11. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/README.md +23 -2
  12. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/cloudnet_api_client/client.py +41 -8
  13. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/cloudnet_api_client/containers.py +22 -1
  14. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/cloudnet_api_client/utils.py +8 -3
  15. cloudnet_api_client-0.11.0/cloudnet_api_client/version.py +1 -0
  16. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/pyproject.toml +4 -1
  17. cloudnet_api_client-0.11.0/tests/data/20140205_hyytiala_classification.nc +0 -0
  18. cloudnet_api_client-0.11.0/tests/data/20250801_Magurele_CHM170137_000.nc +0 -0
  19. cloudnet_api_client-0.11.0/tests/data/20250803_JOYCE_WST_01m.dat +525 -0
  20. cloudnet_api_client-0.11.0/tests/data/20250808_Granada_CHM170119_0045_000.nc +0 -0
  21. cloudnet_api_client-0.11.0/tests/data/20250808_hyytiala_iwc-Z-T-method.nc +0 -0
  22. cloudnet_api_client-0.11.0/tests/data/20250814_bucharest_classification.nc +0 -0
  23. cloudnet_api_client-0.11.0/tests/test_client.py +359 -0
  24. cloudnet_api_client-0.9.2/.github/workflows/test.yml +0 -27
  25. cloudnet_api_client-0.9.2/cloudnet_api_client/version.py +0 -1
  26. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/.github/workflows/publish.yml +0 -0
  27. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/LICENSE +0 -0
  28. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/cloudnet_api_client/__init__.py +0 -0
  29. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/cloudnet_api_client/dl.py +0 -0
  30. {cloudnet_api_client-0.9.2 → cloudnet_api_client-0.11.0}/cloudnet_api_client/py.typed +0 -0
@@ -0,0 +1,29 @@
1
+ NODE_ENV=test
2
+ SS_MODE=remote
3
+ TYPEORM_HOST=db
4
+ TYPEORM_USERNAME=dataportal
5
+ TYPEORM_PASSWORD=dev
6
+ TYPEORM_DATABASE=dataportal
7
+ TYPEORM_PORT=5432
8
+ TYPEORM_SYNCHRONIZE=false
9
+ TYPEORM_MIGRATIONS_RUN=true
10
+ TYPEORM_LOGGING=false
11
+ TYPEORM_ENTITIES=build/entity/*.js
12
+ TYPEORM_MIGRATIONS=build/migration/*.js
13
+ DP_SS_URL=http://storage-service:5900
14
+ DP_SS_USER=test
15
+ DP_SS_PASSWORD=test
16
+ DP_BACKEND_URL=http://localhost:3000/api
17
+ DP_FRONTEND_URL=http://localhost:8080
18
+ GEOLITE2_COUNTRY_PATH=tests/data/GeoLite2-Country-Test.mmdb
19
+ CITATION_SERVICE_URL=http://citation-service
20
+ DATACITE_API_URL=http://localhost:5802
21
+ DATACITE_API_USERNAME=XXX
22
+ DATACITE_API_PASSWORD=XXX
23
+ DATACITE_API_TIMEOUT_MS=2000
24
+ DATACITE_DOI_SERVER=http://handle.datacite.test
25
+ DATACITE_DOI_PREFIX=XXX
26
+ LABELLING_URL=http://localhost:5803
27
+ HANDLE_API_URL=http://localhost:5804
28
+ DVAS_URL=https://dvas.test
29
+ DC_URL=https://dc.test
@@ -0,0 +1,4 @@
1
+ TZ="Europe/Helsinki"
2
+ POSTGRES_USER=admin
3
+ POSTGRES_PASSWORD=admin
4
+ PGUSER=admin
@@ -0,0 +1,45 @@
1
+ services:
2
+ dataportal-backend:
3
+ image: ghcr.io/actris-cloudnet/dataportal-backend
4
+ ports:
5
+ - "3000:3000"
6
+ depends_on:
7
+ db:
8
+ condition: service_healthy
9
+ volumes:
10
+ - ../dataportal-fixtures:/dataportal-fixtures
11
+ - ../backend-fixtures:/backend-fixtures
12
+ env_file:
13
+ - dataportal.env
14
+ command:
15
+ [
16
+ "sh",
17
+ "-c",
18
+ "node build/fixtures.js /backend-fixtures/backend/fixtures TRUNCATE && node build/fixtures.js /dataportal-fixtures APPEND && npm run start",
19
+ ]
20
+ db:
21
+ image: "postgres:16"
22
+ volumes:
23
+ - ./initdb.d:/docker-entrypoint-initdb.d
24
+ ports:
25
+ - "54321:54321"
26
+ env_file:
27
+ - db.env
28
+ healthcheck:
29
+ test: ["CMD", "psql", "-c", "select 1"]
30
+ interval: 1s
31
+ retries: 120
32
+ moto-server:
33
+ image: "motoserver/moto:3.0.1"
34
+ storage-service:
35
+ image: ghcr.io/actris-cloudnet/storage-service
36
+ ports:
37
+ - "5900:5900"
38
+ depends_on:
39
+ db:
40
+ condition: service_healthy
41
+ moto-server:
42
+ condition: service_started
43
+ env_file:
44
+ - ss.env
45
+ command: ["sh", "-c", "node build/init.js && npm start"]
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
5
+ CREATE USER ss WITH PASSWORD 'dev';
6
+ CREATE DATABASE ss OWNER ss;
7
+ CREATE USER dataportal WITH PASSWORD 'dev';
8
+ CREATE DATABASE dataportal OWNER dataportal;
9
+ EOSQL
@@ -0,0 +1,12 @@
1
+ NODE_ENV=test
2
+ SS_USER=test
3
+ SS_PWHASH=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
4
+ SS_MAXOBJECTSPERBUCKET=10000
5
+ PGHOST=db
6
+ PGDATABASE=ss
7
+ PGUSER=ss
8
+ PGPASSWORD=dev
9
+ S3_ENDPOINT=http://moto-server:5000
10
+ S3_ACCESSKEYID=dev
11
+ S3_SECRETACCESSKEY=dev
12
+ S3_FORCE_PATH_STYLE=true
@@ -0,0 +1,64 @@
1
+ name: Test and lint
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ timeout-minutes: 5
8
+ strategy:
9
+ matrix:
10
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Set up Python
14
+ uses: actions/setup-python@v5
15
+ with:
16
+ python-version: ${{ matrix.python-version }}
17
+
18
+ - name: Checkout code
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Cache Python dependencies
22
+ uses: actions/cache@v4
23
+ with:
24
+ path: ~/.cache/pip
25
+ key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/pyproject.toml') }}
26
+ restore-keys: |
27
+ ${{ runner.os }}-pip-${{ matrix.python-version }}-
28
+ ${{ runner.os }}-pip-
29
+
30
+ - name: Checkout fixtures
31
+ uses: actions/checkout@v4
32
+ with:
33
+ repository: actris-cloudnet/dataportal
34
+ sparse-checkout: backend/fixtures/
35
+ sparse-checkout-cone-mode: false
36
+ path: backend-fixtures
37
+
38
+ - name: Checkout production fixtures
39
+ uses: actions/checkout@v4
40
+ with:
41
+ repository: actris-cloudnet/dataportal-fixtures
42
+ path: dataportal-fixtures
43
+
44
+ - name: Set up Docker Buildx
45
+ uses: docker/setup-buildx-action@v3
46
+
47
+ - name: Start dataportal
48
+ run: docker compose -f .github/docker-compose.yml up -d --wait
49
+
50
+ - name: Install dependencies
51
+ run: |
52
+ pip install --upgrade pip
53
+ pip install .[dev,test]
54
+
55
+ - name: Run pre-commit checks
56
+ if: matrix.python-version == '3.13'
57
+ run: pre-commit run --all-files --show-diff-on-failure
58
+
59
+ - name: Run tests
60
+ run: pytest -s -vv
61
+
62
+ - name: Shutdown backend
63
+ if: always()
64
+ run: docker compose -f .github/docker-compose.yml down
@@ -1,3 +1,4 @@
1
1
  __pycache__
2
2
  *~
3
3
  .venv
4
+ *.ipynb*
@@ -1,7 +1,7 @@
1
1
  exclude: ^tests/data/
2
2
  repos:
3
3
  - repo: https://github.com/pre-commit/pre-commit-hooks
4
- rev: v5.0.0
4
+ rev: v6.0.0
5
5
  hooks:
6
6
  - id: check-case-conflict
7
7
  - id: check-executables-have-shebangs
@@ -13,7 +13,7 @@ repos:
13
13
  args: ["--fix", "lf"]
14
14
  - id: trailing-whitespace
15
15
  - repo: https://github.com/astral-sh/ruff-pre-commit
16
- rev: v0.11.0
16
+ rev: v0.12.8
17
17
  hooks:
18
18
  - id: ruff
19
19
  args: ["--fix"]
@@ -5,6 +5,16 @@ 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.11.0 – 2025-08-16
9
+
10
+ - Adjust routes and responses
11
+ - Improve tests
12
+
13
+ ## 0.10.0 – 2025-08-13
14
+
15
+ - Add `volatile` to metadata response
16
+ - Run CI tests against true dataportal backend
17
+
8
18
  ## 0.9.2 – 2025-08-04
9
19
 
10
20
  - Use updated Cloudnet API
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnet-api-client
3
- Version: 0.9.2
3
+ Version: 0.11.0
4
4
  Summary: Cloudnet API client
5
5
  Author-email: Simo Tukiainen <simo.tukiainen@fmi.fi>
6
6
  License-File: LICENSE
@@ -22,7 +22,9 @@ Requires-Dist: types-requests; extra == 'dev'
22
22
  Requires-Dist: types-tqdm; extra == 'dev'
23
23
  Provides-Extra: test
24
24
  Requires-Dist: mypy; extra == 'test'
25
+ Requires-Dist: netcdf4; extra == 'test'
25
26
  Requires-Dist: pytest; extra == 'test'
27
+ Requires-Dist: pytest-asyncio; extra == 'test'
26
28
  Description-Content-Type: text/markdown
27
29
 
28
30
  [![CI](https://github.com/actris-cloudnet/cloudnet-api-client/actions/workflows/test.yml/badge.svg)](https://github.com/actris-cloudnet/cloudnet-api-client/actions/workflows/test.yml)
@@ -80,6 +82,7 @@ Parameters:
80
82
  | updated_at_to | `str`, `date` or `datetime` | `None` | "2025-01-01T12:00:00" |
81
83
  | instrument_id | `str` or `list[str]` | `None` | "rpg-fmcw-94" |
82
84
  | instrument_pid | `str` or `list[str]` | `None` | "https://hdl.handle.net/21.12132/3.191564170f8a4686" |
85
+ | model_id | `str` or `list[str]` | `None` | "gdas1" |
83
86
  | product\* | `str` or `list[str]` | `None` | "classification" |
84
87
  | show_legacy\* | `bool` | `False` | |
85
88
  | filename_prefix\*\* | `str` or `list[str]` | `None` | "stare" |
@@ -127,13 +130,33 @@ Parameters:
127
130
 
128
131
  \* = only with `RawMetadata`
129
132
 
133
+ ### `APIClient().file()` &rarr; `ProductMetadata`
134
+
135
+ Fetch metadata of a single file.
136
+
137
+ Parameters:
138
+
139
+ | name | type |
140
+ | ---- | -------------------- |
141
+ | uuid | `str` or `uuid.UUID` |
142
+
143
+ ### `APIClient().versions()` &rarr; `list[VersionMetadata]`
144
+
145
+ Fetch information of all versions of a file.
146
+
147
+ Parameters:
148
+
149
+ | name | type |
150
+ | ---- | -------------------- |
151
+ | uuid | `str` or `uuid.UUID` |
152
+
130
153
  ### `APIClient().sites()` &rarr; `list[Site]`
131
154
 
132
155
  Fetch cloudnet sites.
133
156
 
134
157
  Parameters:
135
158
 
136
- | name | type | Choices | default |
159
+ | name | type | choices | default |
137
160
  | ------- | -------------------- | ----------------------------------------- | ------- |
138
161
  | site_id | `str` | | `None` |
139
162
  | type | `str` or `list[str]` | "cloudnet", "campaign", "model", "hidden" | `None` |
@@ -144,7 +167,7 @@ Fetch cloudnet products.
144
167
 
145
168
  Parameters:
146
169
 
147
- | name | type | Choices | default |
170
+ | name | type | choices | default |
148
171
  | ---- | -------------------- | ----------------------------------------- | ------- |
149
172
  | type | `str` or `list[str]` | "instrument", "geophysical", "evaluation" | `None` |
150
173
 
@@ -53,6 +53,7 @@ Parameters:
53
53
  | updated_at_to | `str`, `date` or `datetime` | `None` | "2025-01-01T12:00:00" |
54
54
  | instrument_id | `str` or `list[str]` | `None` | "rpg-fmcw-94" |
55
55
  | instrument_pid | `str` or `list[str]` | `None` | "https://hdl.handle.net/21.12132/3.191564170f8a4686" |
56
+ | model_id | `str` or `list[str]` | `None` | "gdas1" |
56
57
  | product\* | `str` or `list[str]` | `None` | "classification" |
57
58
  | show_legacy\* | `bool` | `False` | |
58
59
  | filename_prefix\*\* | `str` or `list[str]` | `None` | "stare" |
@@ -100,13 +101,33 @@ Parameters:
100
101
 
101
102
  \* = only with `RawMetadata`
102
103
 
104
+ ### `APIClient().file()` &rarr; `ProductMetadata`
105
+
106
+ Fetch metadata of a single file.
107
+
108
+ Parameters:
109
+
110
+ | name | type |
111
+ | ---- | -------------------- |
112
+ | uuid | `str` or `uuid.UUID` |
113
+
114
+ ### `APIClient().versions()` &rarr; `list[VersionMetadata]`
115
+
116
+ Fetch information of all versions of a file.
117
+
118
+ Parameters:
119
+
120
+ | name | type |
121
+ | ---- | -------------------- |
122
+ | uuid | `str` or `uuid.UUID` |
123
+
103
124
  ### `APIClient().sites()` &rarr; `list[Site]`
104
125
 
105
126
  Fetch cloudnet sites.
106
127
 
107
128
  Parameters:
108
129
 
109
- | name | type | Choices | default |
130
+ | name | type | choices | default |
110
131
  | ------- | -------------------- | ----------------------------------------- | ------- |
111
132
  | site_id | `str` | | `None` |
112
133
  | type | `str` or `list[str]` | "cloudnet", "campaign", "model", "hidden" | `None` |
@@ -117,7 +138,7 @@ Fetch cloudnet products.
117
138
 
118
139
  Parameters:
119
140
 
120
- | name | type | Choices | default |
141
+ | name | type | choices | default |
121
142
  | ---- | -------------------- | ----------------------------------------- | ------- |
122
143
  | type | `str` or `list[str]` | "instrument", "geophysical", "evaluation" | `None` |
123
144
 
@@ -3,12 +3,12 @@ import calendar
3
3
  import datetime
4
4
  import os
5
5
  import re
6
- import uuid
7
6
  from dataclasses import fields, is_dataclass
8
7
  from os import PathLike
9
8
  from pathlib import Path
10
9
  from typing import TypeVar, cast
11
10
  from urllib.parse import urljoin
11
+ from uuid import UUID
12
12
 
13
13
  import requests
14
14
  from requests.adapters import HTTPAdapter
@@ -25,6 +25,7 @@ from cloudnet_api_client.containers import (
25
25
  RawMetadata,
26
26
  RawModelMetadata,
27
27
  Site,
28
+ VersionMetadata,
28
29
  )
29
30
  from cloudnet_api_client.dl import download_files
30
31
 
@@ -42,6 +43,8 @@ class APIClient:
42
43
  base_url: str = "https://cloudnet.fmi.fi/api/",
43
44
  session: requests.Session | None = None,
44
45
  ) -> None:
46
+ if not base_url.endswith("/"):
47
+ base_url += "/"
45
48
  self.base_url = base_url
46
49
  self.session = session or _make_session()
47
50
 
@@ -74,7 +77,7 @@ class APIClient:
74
77
  instrument_id=obj["instrument"]["id"],
75
78
  model=obj["model"],
76
79
  type=obj["type"],
77
- uuid=uuid.UUID(obj["uuid"]),
80
+ uuid=UUID(obj["uuid"]),
78
81
  pid=obj["pid"],
79
82
  owners=obj["owners"],
80
83
  serial_number=obj["serialNumber"],
@@ -83,6 +86,31 @@ class APIClient:
83
86
  for obj in res
84
87
  ]
85
88
 
89
+ def file(
90
+ self,
91
+ uuid: str | UUID,
92
+ ) -> ProductMetadata:
93
+ res = self._get_response(f"files/{uuid}")
94
+ return _build_meta_objects(res)[0]
95
+
96
+ def versions(self, uuid: str | UUID) -> list[VersionMetadata]:
97
+ res = self._get_response(
98
+ f"files/{uuid}/versions",
99
+ {"properties": ["pid", "dvasId", "legacy", "size", "checksum"]},
100
+ )
101
+ return [
102
+ VersionMetadata(
103
+ uuid=UUID(obj["uuid"]),
104
+ created_at=_parse_datetime(obj["createdAt"]),
105
+ pid=obj["pid"],
106
+ dvas_id=obj["dvasId"],
107
+ legacy=obj["legacy"],
108
+ size=int(obj["size"]),
109
+ checksum=obj["checksum"],
110
+ )
111
+ for obj in res
112
+ ]
113
+
86
114
  def metadata(
87
115
  self,
88
116
  site_id: QueryParam = None,
@@ -105,6 +133,10 @@ class APIClient:
105
133
  "product": product,
106
134
  "showLegacy": show_legacy,
107
135
  }
136
+ if show_legacy is not True:
137
+ # API shows legacy files with any value (even <False>)
138
+ del params["showLegacy"]
139
+
108
140
  _add_date_params(
109
141
  params, date, date_from, date_to, updated_at, updated_at_from, updated_at_to
110
142
  )
@@ -125,7 +157,8 @@ class APIClient:
125
157
  or (model_id is not None and (product is None or "model" in product))
126
158
  ):
127
159
  for key in ("showLegacy", "product", "instrument", "instrumentPid"):
128
- del params[key]
160
+ if key in params:
161
+ del params[key]
129
162
  params["model"] = model_id
130
163
  files_res += self._get_response("model-files", params)
131
164
 
@@ -418,7 +451,7 @@ def _build_meta_objects(res: list[dict]) -> list[ProductMetadata]:
418
451
  created_at=_parse_datetime(obj["createdAt"]),
419
452
  updated_at=_parse_datetime(obj["updatedAt"]),
420
453
  size=int(obj["size"]),
421
- uuid=uuid.UUID(obj["uuid"]),
454
+ uuid=UUID(obj["uuid"]),
422
455
  site=_create_site_object(obj["site"]),
423
456
  )
424
457
  for obj in res
@@ -437,7 +470,7 @@ def _build_raw_meta_objects(res: list[dict]) -> list[RawMetadata]:
437
470
  created_at=_parse_datetime(obj["createdAt"]),
438
471
  updated_at=_parse_datetime(obj["updatedAt"]),
439
472
  size=int(obj["size"]),
440
- uuid=uuid.UUID(obj["uuid"]),
473
+ uuid=UUID(obj["uuid"]),
441
474
  site=_create_site_object(obj["site"]),
442
475
  )
443
476
  for obj in res
@@ -456,7 +489,7 @@ def _build_raw_model_meta_objects(res: list[dict]) -> list[RawModelMetadata]:
456
489
  created_at=_parse_datetime(obj["createdAt"]),
457
490
  updated_at=_parse_datetime(obj["updatedAt"]),
458
491
  size=int(obj["size"]),
459
- uuid=uuid.UUID(obj["uuid"]),
492
+ uuid=UUID(obj["uuid"]),
460
493
  site=_create_site_object(obj["site"]),
461
494
  )
462
495
  for obj in res
@@ -498,10 +531,10 @@ def _create_site_object(metadata: dict) -> Site:
498
531
 
499
532
  def _create_instrument_object(metadata: dict) -> Instrument:
500
533
  return Instrument(
501
- instrument_id=metadata["instrumentId"],
534
+ instrument_id=metadata.get("instrumentId"), # not in api/files/:uuid
502
535
  model=metadata["model"],
503
536
  type=metadata["type"],
504
- uuid=uuid.UUID(metadata["uuid"]),
537
+ uuid=UUID(metadata["uuid"]),
505
538
  pid=metadata["pid"],
506
539
  owners=metadata["owners"],
507
540
  serial_number=metadata["serialNumber"],
@@ -35,7 +35,7 @@ class Product:
35
35
 
36
36
  @dataclass(frozen=True, slots=True)
37
37
  class Instrument:
38
- instrument_id: str # CLU internal identifier, e.g. "rpg-fmcw-94"
38
+ instrument_id: str | None # CLU internal identifier, e.g. "rpg-fmcw-94"
39
39
  model: str # From ACTRIS Vocabulary, e.g. "RPG-FMCW-94 DP"
40
40
  type: str # From ACTRIS Vocabulary, e.g. "Doppler non-scanning cloud radar"
41
41
  name: str # e.g. "FMI RPG-FMCW-94 (Pallas)"
@@ -86,3 +86,24 @@ class ProductMetadata(Metadata):
86
86
  product: Product
87
87
  instrument: Instrument | None
88
88
  model: Model | None
89
+ volatile: bool
90
+ legacy: bool
91
+ pid: str
92
+ dvas_id: str | None
93
+ error_level: str | None
94
+ coverage: float
95
+ timeliness: str
96
+ format: str
97
+ start_time: datetime.datetime | None
98
+ stop_time: datetime.datetime | None
99
+
100
+
101
+ @dataclass(frozen=True, slots=True)
102
+ class VersionMetadata:
103
+ uuid: uuid.UUID
104
+ created_at: datetime.datetime
105
+ pid: str
106
+ checksum: str
107
+ legacy: bool
108
+ size: int
109
+ dvas_id: str | None
@@ -1,3 +1,4 @@
1
+ import base64
1
2
  import hashlib
2
3
  from os import PathLike
3
4
  from typing import Literal
@@ -7,13 +8,17 @@ def sha256sum(filename: str | PathLike) -> str:
7
8
  return _calc_hash_sum(filename, "sha256")
8
9
 
9
10
 
10
- def md5sum(filename: str | PathLike) -> str:
11
- return _calc_hash_sum(filename, "md5")
11
+ def md5sum(filename: str | PathLike, is_base64: bool = False) -> str:
12
+ return _calc_hash_sum(filename, "md5", is_base64)
12
13
 
13
14
 
14
- def _calc_hash_sum(filename: str | PathLike, method: Literal["sha256", "md5"]) -> str:
15
+ def _calc_hash_sum(
16
+ filename: str | PathLike, method: Literal["sha256", "md5"], is_base64: bool = False
17
+ ) -> str:
15
18
  hash_sum = getattr(hashlib, method)()
16
19
  with open(filename, "rb") as f:
17
20
  for byte_block in iter(lambda: f.read(4096), b""):
18
21
  hash_sum.update(byte_block)
22
+ if is_base64:
23
+ return base64.b64encode(hash_sum.digest()).decode("utf-8")
19
24
  return hash_sum.hexdigest()
@@ -0,0 +1 @@
1
+ __version__ = "0.11.0"
@@ -22,12 +22,15 @@ dependencies = ["aiohttp", "numpy", "requests", "tqdm"]
22
22
  dynamic = ["version"]
23
23
 
24
24
  [project.optional-dependencies]
25
- test = ["mypy", "pytest"]
25
+ test = ["mypy", "netCDF4", "pytest", "pytest-asyncio"]
26
26
  dev = ["pre-commit", "release-version", "types-requests", "types-tqdm"]
27
27
 
28
28
  [tool.hatch.version]
29
29
  path = "cloudnet_api_client/version.py"
30
30
 
31
+ [tool.pytest.ini_options]
32
+ asyncio_mode = "auto"
33
+
31
34
  [tool.release-version]
32
35
  filename = "cloudnet_api_client/version.py"
33
36
  pattern = ["__version__ = \"(?P<major>\\d+).(?P<minor>\\d+).(?P<patch>\\d+)\""]