cdsetool 0.2.9__tar.gz → 0.2.10__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.9
3
+ Version: 0.2.10
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
@@ -12,12 +12,12 @@ Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: typer<0.13,>=0.9
14
14
  Requires-Dist: rich<13.8,>=13.6
15
- Requires-Dist: requests<2.32.0,>=2.28.1
15
+ Requires-Dist: requests<2.33.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
19
  Requires-Dist: black==24.4.2; extra == "test"
20
- Requires-Dist: pylint==3.1.0; extra == "test"
20
+ Requires-Dist: pylint==3.2.2; extra == "test"
21
21
  Requires-Dist: pytest==8.2.0; extra == "test"
22
22
  Requires-Dist: pytest-cov==5.0.0; extra == "test"
23
23
  Requires-Dist: requests-mock==1.12.1; extra == "test"
@@ -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.9
99
+ pip install cdsetool==0.2.10
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.9
74
+ pip install cdsetool==0.2.10
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.9"
7
+ version = "0.2.10"
8
8
  authors = [
9
9
  { name="Jacob Vejby", email="javej@sdfi.dk" },
10
10
  ]
@@ -18,14 +18,14 @@ classifiers = [
18
18
  dependencies = [
19
19
  "typer >= 0.9,< 0.13",
20
20
  "rich >= 13.6,< 13.8",
21
- "requests >= 2.28.1,< 2.32.0",
21
+ "requests >= 2.28.1,< 2.33.0",
22
22
  "pyjwt[crypto] ~= 2.8.0",
23
23
  "geopandas >= 0.13.2",
24
24
  ]
25
25
  [project.optional-dependencies]
26
26
  test = [
27
27
  "black==24.4.2",
28
- "pylint==3.1.0",
28
+ "pylint==3.2.2",
29
29
  "pytest==8.2.0",
30
30
  "pytest-cov==5.0.0",
31
31
  "requests-mock==1.12.1",
@@ -70,6 +70,13 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
70
70
 
71
71
  RETRY_CODES = frozenset([413, 429, 500, 502, 503])
72
72
 
73
+ RETRIES = Retry(
74
+ total=5,
75
+ backoff_factor=0.5,
76
+ raise_on_status=False,
77
+ status_forcelist=RETRY_CODES,
78
+ )
79
+
73
80
  def __init__(
74
81
  self,
75
82
  username=None,
@@ -81,12 +88,6 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
81
88
  self.__password = password
82
89
 
83
90
  self.__proxies = proxies
84
- self.__retries = Retry(
85
- total=5,
86
- backoff_factor=0.5,
87
- raise_on_status=False,
88
- status_forcelist=self.RETRY_CODES,
89
- )
90
91
  self.__openid_conf = None
91
92
  self.__jwks = None
92
93
  self.__openid_configuration_endpoint = (
@@ -111,11 +112,16 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
111
112
  """
112
113
  Returns a session with the credentials set as the Authorization header
113
114
  """
114
- return self.__make_session(True, self.__retries, self.__proxies)
115
+ return self.make_session(self, True, self.RETRIES, self.__proxies)
115
116
 
116
- def __make_session(self, authorization, max_retries, proxies):
117
+ @staticmethod
118
+ def make_session(caller, authorization, max_retries, proxies):
119
+ """
120
+ Creates a new session. Authorization is only available from callers
121
+ that are subclasses of Credentials.
122
+ """
117
123
  if authorization:
118
- self.__ensure_tokens()
124
+ caller.__ensure_tokens() # pylint: disable=protected-access
119
125
 
120
126
  session = requests.Session()
121
127
  session.mount("http://", HTTPAdapter(max_retries=max_retries))
@@ -123,13 +129,15 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
123
129
  if proxies is not None:
124
130
  session.proxies.update(proxies)
125
131
  if authorization:
126
- session.headers.update({"Authorization": f"Bearer {self.__access_token}"})
132
+ token = caller.__access_token # pylint: disable=protected-access
133
+ session.headers.update({"Authorization": f"Bearer {token}"})
127
134
  return session
128
135
 
129
136
  def __token_exchange(self, data):
130
137
  # Make a session that will retry post, respecting the retry-after
131
138
  # header when we get a 503 and a few other temporary failures.
132
- session = self.__make_session(
139
+ session = self.make_session(
140
+ caller=self,
133
141
  authorization=False,
134
142
  max_retries=Retry(
135
143
  total=2,
@@ -140,7 +148,6 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
140
148
  ),
141
149
  proxies=self.__proxies,
142
150
  )
143
- now = datetime.now()
144
151
  response = session.post(self.__token_endpoint, data=data, timeout=120)
145
152
 
146
153
  if response.status_code == 401:
@@ -157,13 +164,11 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
157
164
 
158
165
  self.__access_token = response["access_token"]
159
166
  self.__refresh_token = response["refresh_token"]
160
- self.__access_token_expires = now + timedelta(seconds=response["expires_in"])
161
- self.__refresh_token_expires = now + timedelta(
162
- seconds=response["refresh_expires_in"]
163
- )
167
+ return timedelta(seconds=response["refresh_expires_in"])
164
168
 
165
169
  def __ensure_tokens(self):
166
170
  with self.__lock:
171
+ refresh_expire_delta = None
167
172
  if self.__access_token_expires < datetime.now():
168
173
  if self.__refresh_token_expires < datetime.now():
169
174
  data = {
@@ -178,13 +183,13 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
178
183
  "refresh_token": self.__refresh_token,
179
184
  "client_id": "cdse-public",
180
185
  }
181
- self.__token_exchange(data)
186
+ refresh_expire_delta = self.__token_exchange(data)
182
187
  try:
183
188
  key = self.__jwk_client.get_signing_key_from_jwt(self.__access_token)
184
189
  except jwt.PyJWKClientConnectionError as e:
185
190
  raise TokenClientConnectionError from e
186
191
  try:
187
- jwt.decode(
192
+ data = jwt.decode(
188
193
  self.__access_token,
189
194
  key=key.key,
190
195
  algorithms=self.__id_token_signing_algos, # pylint: disable=protected-access
@@ -192,6 +197,11 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
192
197
  )
193
198
  except jwt.ExpiredSignatureError as e:
194
199
  raise TokenExpiredSignatureError from e
200
+ if refresh_expire_delta is not None:
201
+ self.__access_token_expires = datetime.fromtimestamp(data["exp"])
202
+ self.__refresh_token_expires = (
203
+ datetime.fromtimestamp(data["iat"]) + refresh_expire_delta
204
+ )
195
205
 
196
206
  def __read_credentials(self):
197
207
  try:
@@ -206,8 +216,11 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
206
216
  if self.__openid_conf:
207
217
  return self.__openid_conf
208
218
 
209
- session = self.__make_session(
210
- authorization=False, max_retries=self.__retries, proxies=self.__proxies
219
+ session = self.make_session(
220
+ caller=self,
221
+ authorization=False,
222
+ max_retries=self.RETRIES,
223
+ proxies=self.__proxies,
211
224
  )
212
225
  response = session.get(self.__openid_configuration_endpoint, timeout=120)
213
226
  response.raise_for_status()
@@ -8,8 +8,8 @@ from xml.etree import ElementTree
8
8
  from datetime import datetime, date
9
9
  import re
10
10
  import json
11
- import requests
12
11
  import geopandas as gpd
12
+ from cdsetool.credentials import Credentials
13
13
 
14
14
 
15
15
  class _FeatureIterator:
@@ -67,7 +67,10 @@ class FeatureQuery:
67
67
  def __fetch_features(self):
68
68
  if self.next_url is None:
69
69
  return
70
- with requests.get(self.next_url, timeout=120, proxies=self.proxies) as response:
70
+ session = Credentials.make_session(
71
+ None, False, Credentials.RETRIES, self.proxies
72
+ )
73
+ with session.get(self.next_url) as response:
71
74
  response.raise_for_status()
72
75
  res = response.json()
73
76
  self.features += res.get("features") or []
@@ -236,18 +239,17 @@ _describe_docs = {}
236
239
  def _get_describe_doc(collection, proxies=None):
237
240
  if _describe_docs.get(collection):
238
241
  return _describe_docs.get(collection)
239
- res = requests.get(
242
+ session = Credentials.make_session(None, False, Credentials.RETRIES, proxies)
243
+ with session.get(
240
244
  "https://catalogue.dataspace.copernicus.eu"
241
245
  + f"/resto/api/collections/{collection}/describe.xml",
242
- timeout=120,
243
- proxies=proxies,
244
- )
245
- assert res.status_code == 200, (
246
- f"Unable to find collection with name {collection}. Please see "
247
- + "https://documentation.dataspace.copernicus.eu"
248
- + "/APIs/OpenSearch.html#collections "
249
- + "for a list of available collections"
250
- )
246
+ ) as res:
247
+ assert res.status_code == 200, (
248
+ f"Unable to find collection with name {collection}. Please see "
249
+ + "https://documentation.dataspace.copernicus.eu"
250
+ + "/APIs/OpenSearch.html#collections "
251
+ + "for a list of available collections"
252
+ )
251
253
 
252
- _describe_docs[collection] = res.content
254
+ _describe_docs[collection] = res.content
253
255
  return _describe_docs.get(collection)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cdsetool
3
- Version: 0.2.9
3
+ Version: 0.2.10
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
@@ -12,12 +12,12 @@ Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: typer<0.13,>=0.9
14
14
  Requires-Dist: rich<13.8,>=13.6
15
- Requires-Dist: requests<2.32.0,>=2.28.1
15
+ Requires-Dist: requests<2.33.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
19
  Requires-Dist: black==24.4.2; extra == "test"
20
- Requires-Dist: pylint==3.1.0; extra == "test"
20
+ Requires-Dist: pylint==3.2.2; extra == "test"
21
21
  Requires-Dist: pytest==8.2.0; extra == "test"
22
22
  Requires-Dist: pytest-cov==5.0.0; extra == "test"
23
23
  Requires-Dist: requests-mock==1.12.1; extra == "test"
@@ -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.9
99
+ pip install cdsetool==0.2.10
100
100
  ```
101
101
 
102
102
  ## Usage
@@ -1,12 +1,12 @@
1
1
  typer<0.13,>=0.9
2
2
  rich<13.8,>=13.6
3
- requests<2.32.0,>=2.28.1
3
+ requests<2.33.0,>=2.28.1
4
4
  pyjwt[crypto]~=2.8.0
5
5
  geopandas>=0.13.2
6
6
 
7
7
  [test]
8
8
  black==24.4.2
9
- pylint==3.1.0
9
+ pylint==3.2.2
10
10
  pytest==8.2.0
11
11
  pytest-cov==5.0.0
12
12
  requests-mock==1.12.1
File without changes
File without changes
File without changes