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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cdsetool
3
- Version: 0.2.8
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~=0.9.0
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.3.0; extra == "test"
19
+ Requires-Dist: black==24.4.2; extra == "test"
20
20
  Requires-Dist: pylint==3.1.0; extra == "test"
21
- Requires-Dist: pytest==8.1.1; extra == "test"
22
- Requires-Dist: pytest-cov==4.1.0; extra == "test"
23
- Requires-Dist: requests-mock==1.11.0; extra == "test"
24
- Requires-Dist: pytest-mock==3.12.0; extra == "test"
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.8
99
+ pip install cdsetool==0.2.9
100
100
  ```
101
101
 
102
102
  ## Usage
@@ -71,7 +71,7 @@ cdsetool download Sentinel2 PATH/TO/DIR --concurrency 4 --search-term startDate=
71
71
  Install `cdsetool` using pip:
72
72
 
73
73
  ```bash
74
- pip install cdsetool==0.2.8
74
+ pip install cdsetool==0.2.9
75
75
  ```
76
76
 
77
77
  ## Usage
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdsetool"
7
- version = "0.2.8"
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 ~= 0.9.0",
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.3.0",
27
+ "black==24.4.2",
28
28
  "pylint==3.1.0",
29
- "pytest==8.1.1",
30
- "pytest-cov==4.1.0",
31
- "requests-mock==1.11.0",
32
- "pytest-mock==3.12.0",
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=Retry.RETRY_AFTER_STATUS_CODES,
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 = datetime.now()
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=Retry.RETRY_AFTER_STATUS_CODES,
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
- jwt.decode(
179
- self.__access_token,
180
- key=key.key,
181
- algorithms=key._algorithms, # pylint: disable=protected-access
182
- options={"verify_aud": False},
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 Credentials, TokenClientConnectionError
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 < 5:
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 open(fd, "wb") as file:
63
- for chunk in response.iter_content(chunk_size=1024 * 1024 * 5):
64
- file.write(chunk)
65
- status.add_progress(len(chunk))
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.8
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~=0.9.0
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.3.0; extra == "test"
19
+ Requires-Dist: black==24.4.2; extra == "test"
20
20
  Requires-Dist: pylint==3.1.0; extra == "test"
21
- Requires-Dist: pytest==8.1.1; extra == "test"
22
- Requires-Dist: pytest-cov==4.1.0; extra == "test"
23
- Requires-Dist: requests-mock==1.11.0; extra == "test"
24
- Requires-Dist: pytest-mock==3.12.0; extra == "test"
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.8
99
+ pip install cdsetool==0.2.9
100
100
  ```
101
101
 
102
102
  ## Usage
@@ -0,0 +1,13 @@
1
+ typer<0.13,>=0.9
2
+ rich<13.8,>=13.6
3
+ requests<2.32.0,>=2.28.1
4
+ pyjwt[crypto]~=2.8.0
5
+ geopandas>=0.13.2
6
+
7
+ [test]
8
+ black==24.4.2
9
+ pylint==3.1.0
10
+ pytest==8.2.0
11
+ pytest-cov==5.0.0
12
+ requests-mock==1.12.1
13
+ pytest-mock==3.14.0
@@ -1,13 +0,0 @@
1
- typer~=0.9.0
2
- rich<13.8,>=13.6
3
- requests<2.32.0,>=2.28.1
4
- pyjwt[crypto]~=2.8.0
5
- geopandas>=0.13.2
6
-
7
- [test]
8
- black==24.3.0
9
- pylint==3.1.0
10
- pytest==8.1.1
11
- pytest-cov==4.1.0
12
- requests-mock==1.11.0
13
- pytest-mock==3.12.0
File without changes
File without changes
File without changes
File without changes