cdsetool 0.2.8__tar.gz → 0.2.9__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.
- {cdsetool-0.2.8/src/cdsetool.egg-info → cdsetool-0.2.9}/PKG-INFO +8 -8
- {cdsetool-0.2.8 → cdsetool-0.2.9}/README.md +1 -1
- {cdsetool-0.2.8 → cdsetool-0.2.9}/pyproject.toml +7 -7
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool/credentials.py +25 -10
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool/download.py +26 -12
- {cdsetool-0.2.8 → cdsetool-0.2.9/src/cdsetool.egg-info}/PKG-INFO +8 -8
- cdsetool-0.2.9/src/cdsetool.egg-info/requires.txt +13 -0
- cdsetool-0.2.8/src/cdsetool.egg-info/requires.txt +0 -13
- {cdsetool-0.2.8 → cdsetool-0.2.9}/LICENSE +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/setup.cfg +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool/__init__.py +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool/_processing.py +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool/cli.py +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool/logger.py +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool/monitor.py +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool/query.py +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool.egg-info/SOURCES.txt +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool.egg-info/dependency_links.txt +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool.egg-info/entry_points.txt +0 -0
- {cdsetool-0.2.8 → cdsetool-0.2.9}/src/cdsetool.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cdsetool
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: Tools & CLI for interacting with CDSE product APIs
|
|
5
5
|
Author-email: Jacob Vejby <javej@sdfi.dk>
|
|
6
6
|
Project-URL: Homepage, https://github.com/SDFIdk/CDSETool
|
|
@@ -10,18 +10,18 @@ Classifier: Operating System :: OS Independent
|
|
|
10
10
|
Requires-Python: >=3.8
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: typer
|
|
13
|
+
Requires-Dist: typer<0.13,>=0.9
|
|
14
14
|
Requires-Dist: rich<13.8,>=13.6
|
|
15
15
|
Requires-Dist: requests<2.32.0,>=2.28.1
|
|
16
16
|
Requires-Dist: pyjwt[crypto]~=2.8.0
|
|
17
17
|
Requires-Dist: geopandas>=0.13.2
|
|
18
18
|
Provides-Extra: test
|
|
19
|
-
Requires-Dist: black==24.
|
|
19
|
+
Requires-Dist: black==24.4.2; extra == "test"
|
|
20
20
|
Requires-Dist: pylint==3.1.0; extra == "test"
|
|
21
|
-
Requires-Dist: pytest==8.
|
|
22
|
-
Requires-Dist: pytest-cov==
|
|
23
|
-
Requires-Dist: requests-mock==1.
|
|
24
|
-
Requires-Dist: pytest-mock==3.
|
|
21
|
+
Requires-Dist: pytest==8.2.0; extra == "test"
|
|
22
|
+
Requires-Dist: pytest-cov==5.0.0; extra == "test"
|
|
23
|
+
Requires-Dist: requests-mock==1.12.1; extra == "test"
|
|
24
|
+
Requires-Dist: pytest-mock==3.14.0; extra == "test"
|
|
25
25
|
|
|
26
26
|
# CDSETool
|
|
27
27
|
|
|
@@ -96,7 +96,7 @@ cdsetool download Sentinel2 PATH/TO/DIR --concurrency 4 --search-term startDate=
|
|
|
96
96
|
Install `cdsetool` using pip:
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
|
-
pip install cdsetool==0.2.
|
|
99
|
+
pip install cdsetool==0.2.9
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
## Usage
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cdsetool"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.9"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Jacob Vejby", email="javej@sdfi.dk" },
|
|
10
10
|
]
|
|
@@ -16,7 +16,7 @@ classifiers = [
|
|
|
16
16
|
"Operating System :: OS Independent",
|
|
17
17
|
]
|
|
18
18
|
dependencies = [
|
|
19
|
-
"typer
|
|
19
|
+
"typer >= 0.9,< 0.13",
|
|
20
20
|
"rich >= 13.6,< 13.8",
|
|
21
21
|
"requests >= 2.28.1,< 2.32.0",
|
|
22
22
|
"pyjwt[crypto] ~= 2.8.0",
|
|
@@ -24,12 +24,12 @@ dependencies = [
|
|
|
24
24
|
]
|
|
25
25
|
[project.optional-dependencies]
|
|
26
26
|
test = [
|
|
27
|
-
"black==24.
|
|
27
|
+
"black==24.4.2",
|
|
28
28
|
"pylint==3.1.0",
|
|
29
|
-
"pytest==8.
|
|
30
|
-
"pytest-cov==
|
|
31
|
-
"requests-mock==1.
|
|
32
|
-
"pytest-mock==3.
|
|
29
|
+
"pytest==8.2.0",
|
|
30
|
+
"pytest-cov==5.0.0",
|
|
31
|
+
"requests-mock==1.12.1",
|
|
32
|
+
"pytest-mock==3.14.0",
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
[project.urls]
|
|
@@ -56,12 +56,20 @@ class TokenClientConnectionError(Exception):
|
|
|
56
56
|
"""
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
class TokenExpiredSignatureError(Exception):
|
|
60
|
+
"""
|
|
61
|
+
Raised when token signature has expired.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
59
65
|
class Credentials: # pylint: disable=too-few-public-methods disable=too-many-instance-attributes
|
|
60
66
|
"""
|
|
61
67
|
A class for handling credentials for the Copernicus Identity
|
|
62
68
|
and Access Management (IAM) system
|
|
63
69
|
"""
|
|
64
70
|
|
|
71
|
+
RETRY_CODES = frozenset([413, 429, 500, 502, 503])
|
|
72
|
+
|
|
65
73
|
def __init__(
|
|
66
74
|
self,
|
|
67
75
|
username=None,
|
|
@@ -77,7 +85,7 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
|
|
|
77
85
|
total=5,
|
|
78
86
|
backoff_factor=0.5,
|
|
79
87
|
raise_on_status=False,
|
|
80
|
-
status_forcelist=
|
|
88
|
+
status_forcelist=self.RETRY_CODES,
|
|
81
89
|
)
|
|
82
90
|
self.__openid_conf = None
|
|
83
91
|
self.__jwks = None
|
|
@@ -89,8 +97,8 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
|
|
|
89
97
|
|
|
90
98
|
self.__access_token = None
|
|
91
99
|
self.__refresh_token = None
|
|
92
|
-
self.__access_token_expires = datetime.now()
|
|
93
|
-
self.__refresh_token_expires =
|
|
100
|
+
self.__access_token_expires = datetime.now() - timedelta(hours=8)
|
|
101
|
+
self.__refresh_token_expires = self.__access_token_expires
|
|
94
102
|
|
|
95
103
|
self.__lock = threading.Lock()
|
|
96
104
|
|
|
@@ -128,7 +136,7 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
|
|
|
128
136
|
backoff_factor=0.5,
|
|
129
137
|
allowed_methods=None,
|
|
130
138
|
raise_on_status=False,
|
|
131
|
-
status_forcelist=
|
|
139
|
+
status_forcelist=self.RETRY_CODES,
|
|
132
140
|
),
|
|
133
141
|
proxies=self.__proxies,
|
|
134
142
|
)
|
|
@@ -175,12 +183,15 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
|
|
|
175
183
|
key = self.__jwk_client.get_signing_key_from_jwt(self.__access_token)
|
|
176
184
|
except jwt.PyJWKClientConnectionError as e:
|
|
177
185
|
raise TokenClientConnectionError from e
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
try:
|
|
187
|
+
jwt.decode(
|
|
188
|
+
self.__access_token,
|
|
189
|
+
key=key.key,
|
|
190
|
+
algorithms=self.__id_token_signing_algos, # pylint: disable=protected-access
|
|
191
|
+
options={"verify_aud": False},
|
|
192
|
+
)
|
|
193
|
+
except jwt.ExpiredSignatureError as e:
|
|
194
|
+
raise TokenExpiredSignatureError from e
|
|
184
195
|
|
|
185
196
|
def __read_credentials(self):
|
|
186
197
|
try:
|
|
@@ -211,6 +222,10 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
|
|
|
211
222
|
def __jwks_uri(self):
|
|
212
223
|
return self.__openid_configuration["jwks_uri"]
|
|
213
224
|
|
|
225
|
+
@property
|
|
226
|
+
def __id_token_signing_algos(self):
|
|
227
|
+
return self.__openid_configuration["id_token_signing_alg_values_supported"]
|
|
228
|
+
|
|
214
229
|
@property
|
|
215
230
|
def __jwk_client(self):
|
|
216
231
|
if self.__jwks:
|
|
@@ -10,8 +10,14 @@ import random
|
|
|
10
10
|
import tempfile
|
|
11
11
|
import time
|
|
12
12
|
import shutil
|
|
13
|
+
from requests.exceptions import ChunkedEncodingError
|
|
14
|
+
from urllib3.exceptions import ProtocolError
|
|
13
15
|
from cdsetool._processing import _concurrent_process
|
|
14
|
-
from cdsetool.credentials import
|
|
16
|
+
from cdsetool.credentials import (
|
|
17
|
+
Credentials,
|
|
18
|
+
TokenClientConnectionError,
|
|
19
|
+
TokenExpiredSignatureError,
|
|
20
|
+
)
|
|
15
21
|
from cdsetool.logger import NoopLogger
|
|
16
22
|
from cdsetool.monitor import NoopMonitor
|
|
17
23
|
|
|
@@ -39,36 +45,44 @@ def download_feature(feature, path, options=None):
|
|
|
39
45
|
return filename
|
|
40
46
|
|
|
41
47
|
with _get_monitor(options).status() as status:
|
|
42
|
-
(fd, tmp) = tempfile.mkstemp() # pylint: disable=invalid-name
|
|
43
48
|
status.set_filename(filename)
|
|
44
49
|
attempts = 0
|
|
45
|
-
while attempts <
|
|
50
|
+
while attempts < 10:
|
|
51
|
+
attempts += 1
|
|
46
52
|
# Always get a new session, credentials might have expired.
|
|
47
53
|
try:
|
|
48
54
|
session = _get_credentials(options).get_session()
|
|
49
|
-
except TokenClientConnectionError as e:
|
|
55
|
+
except (TokenClientConnectionError, TokenExpiredSignatureError) as e:
|
|
50
56
|
log.warning(e)
|
|
51
57
|
continue
|
|
52
58
|
url = _follow_redirect(url, session)
|
|
53
59
|
with session.get(url, stream=True) as response:
|
|
54
60
|
if response.status_code != 200:
|
|
55
61
|
log.warning(f"Status code {response.status_code}, retrying..")
|
|
56
|
-
attempts += 1
|
|
57
62
|
time.sleep(60 * (1 + (random.random() / 4)))
|
|
58
63
|
continue
|
|
59
64
|
|
|
60
65
|
status.set_filesize(int(response.headers["Content-Length"]))
|
|
61
66
|
|
|
62
|
-
with
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
with tempfile.NamedTemporaryFile() as file:
|
|
68
|
+
# Server might not send all bytes specified by the
|
|
69
|
+
# Content-Length header before closing connection.
|
|
70
|
+
# Log as a warning and try again.
|
|
71
|
+
try:
|
|
72
|
+
for chunk in response.iter_content(chunk_size=1024 * 1024 * 5):
|
|
73
|
+
file.write(chunk)
|
|
74
|
+
status.add_progress(len(chunk))
|
|
75
|
+
except (
|
|
76
|
+
ChunkedEncodingError,
|
|
77
|
+
ConnectionResetError,
|
|
78
|
+
ProtocolError,
|
|
79
|
+
) as e:
|
|
80
|
+
log.warning(e)
|
|
81
|
+
continue
|
|
82
|
+
shutil.copy(file.name, result_path)
|
|
66
83
|
|
|
67
|
-
shutil.move(tmp, result_path)
|
|
68
84
|
return filename
|
|
69
85
|
log.error(f"Failed to download {filename}")
|
|
70
|
-
os.close(fd)
|
|
71
|
-
os.remove(tmp)
|
|
72
86
|
return None
|
|
73
87
|
|
|
74
88
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cdsetool
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: Tools & CLI for interacting with CDSE product APIs
|
|
5
5
|
Author-email: Jacob Vejby <javej@sdfi.dk>
|
|
6
6
|
Project-URL: Homepage, https://github.com/SDFIdk/CDSETool
|
|
@@ -10,18 +10,18 @@ Classifier: Operating System :: OS Independent
|
|
|
10
10
|
Requires-Python: >=3.8
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: typer
|
|
13
|
+
Requires-Dist: typer<0.13,>=0.9
|
|
14
14
|
Requires-Dist: rich<13.8,>=13.6
|
|
15
15
|
Requires-Dist: requests<2.32.0,>=2.28.1
|
|
16
16
|
Requires-Dist: pyjwt[crypto]~=2.8.0
|
|
17
17
|
Requires-Dist: geopandas>=0.13.2
|
|
18
18
|
Provides-Extra: test
|
|
19
|
-
Requires-Dist: black==24.
|
|
19
|
+
Requires-Dist: black==24.4.2; extra == "test"
|
|
20
20
|
Requires-Dist: pylint==3.1.0; extra == "test"
|
|
21
|
-
Requires-Dist: pytest==8.
|
|
22
|
-
Requires-Dist: pytest-cov==
|
|
23
|
-
Requires-Dist: requests-mock==1.
|
|
24
|
-
Requires-Dist: pytest-mock==3.
|
|
21
|
+
Requires-Dist: pytest==8.2.0; extra == "test"
|
|
22
|
+
Requires-Dist: pytest-cov==5.0.0; extra == "test"
|
|
23
|
+
Requires-Dist: requests-mock==1.12.1; extra == "test"
|
|
24
|
+
Requires-Dist: pytest-mock==3.14.0; extra == "test"
|
|
25
25
|
|
|
26
26
|
# CDSETool
|
|
27
27
|
|
|
@@ -96,7 +96,7 @@ cdsetool download Sentinel2 PATH/TO/DIR --concurrency 4 --search-term startDate=
|
|
|
96
96
|
Install `cdsetool` using pip:
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
|
-
pip install cdsetool==0.2.
|
|
99
|
+
pip install cdsetool==0.2.9
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
## Usage
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|