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.
- {cdsetool-0.2.9/src/cdsetool.egg-info → cdsetool-0.2.10}/PKG-INFO +4 -4
- {cdsetool-0.2.9 → cdsetool-0.2.10}/README.md +1 -1
- {cdsetool-0.2.9 → cdsetool-0.2.10}/pyproject.toml +3 -3
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool/credentials.py +33 -20
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool/query.py +15 -13
- {cdsetool-0.2.9 → cdsetool-0.2.10/src/cdsetool.egg-info}/PKG-INFO +4 -4
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool.egg-info/requires.txt +2 -2
- {cdsetool-0.2.9 → cdsetool-0.2.10}/LICENSE +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/setup.cfg +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool/__init__.py +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool/_processing.py +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool/cli.py +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool/download.py +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool/logger.py +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool/monitor.py +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool.egg-info/SOURCES.txt +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool.egg-info/dependency_links.txt +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/src/cdsetool.egg-info/entry_points.txt +0 -0
- {cdsetool-0.2.9 → cdsetool-0.2.10}/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.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.
|
|
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.
|
|
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.
|
|
99
|
+
pip install cdsetool==0.2.10
|
|
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.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.
|
|
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.
|
|
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.
|
|
115
|
+
return self.make_session(self, True, self.RETRIES, self.__proxies)
|
|
115
116
|
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|