cdsetool 0.2.11__tar.gz → 0.2.13__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.11/src/cdsetool.egg-info → cdsetool-0.2.13}/PKG-INFO +9 -9
- {cdsetool-0.2.11 → cdsetool-0.2.13}/README.md +1 -1
- {cdsetool-0.2.11 → cdsetool-0.2.13}/pyproject.toml +8 -8
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool/_processing.py +0 -1
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool/cli.py +6 -2
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool/credentials.py +1 -1
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool/download.py +22 -8
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool/query.py +35 -12
- {cdsetool-0.2.11 → cdsetool-0.2.13/src/cdsetool.egg-info}/PKG-INFO +9 -9
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool.egg-info/requires.txt +4 -4
- {cdsetool-0.2.11 → cdsetool-0.2.13}/LICENSE +0 -0
- {cdsetool-0.2.11 → cdsetool-0.2.13}/setup.cfg +0 -0
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool/__init__.py +0 -0
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool/logger.py +0 -0
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool/monitor.py +0 -0
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool/py.typed +0 -0
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool.egg-info/SOURCES.txt +0 -0
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool.egg-info/dependency_links.txt +0 -0
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool.egg-info/entry_points.txt +0 -0
- {cdsetool-0.2.11 → cdsetool-0.2.13}/src/cdsetool.egg-info/top_level.txt +0 -0
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cdsetool
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.13
|
|
4
4
|
Summary: Tools & CLI for interacting with CDSE product APIs
|
|
5
5
|
Author-email: Jacob Vejby <javej@sdfi.dk>
|
|
6
|
-
Project-URL: Homepage, https://github.com/
|
|
7
|
-
Project-URL: Bug Tracker, https://github.com/
|
|
6
|
+
Project-URL: Homepage, https://github.com/CDSETool/CDSETool
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/CDSETool/CDSETool/issues
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Requires-Dist: typer<1,>=0.9
|
|
14
14
|
Requires-Dist: rich<14,>=13.6
|
|
15
15
|
Requires-Dist: requests<3,>=2.28.1
|
|
16
|
-
Requires-Dist: pyjwt[crypto]
|
|
16
|
+
Requires-Dist: pyjwt[crypto]<2.10,>=2.8
|
|
17
17
|
Requires-Dist: geopandas>=0.13.2
|
|
18
18
|
Provides-Extra: test
|
|
19
|
-
Requires-Dist:
|
|
20
|
-
Requires-Dist:
|
|
21
|
-
Requires-Dist: pytest==8.2.1; extra == "test"
|
|
19
|
+
Requires-Dist: pylint==3.3.1; extra == "test"
|
|
20
|
+
Requires-Dist: pytest==8.3.3; extra == "test"
|
|
22
21
|
Requires-Dist: pytest-cov==5.0.0; extra == "test"
|
|
23
22
|
Requires-Dist: requests-mock==1.12.1; extra == "test"
|
|
23
|
+
Requires-Dist: ruff==0.6.6; extra == "test"
|
|
24
24
|
Requires-Dist: pytest-mock==3.14.0; extra == "test"
|
|
25
25
|
|
|
26
26
|
# CDSETool
|
|
@@ -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.13
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
## Usage
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cdsetool"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.13"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Jacob Vejby", email="javej@sdfi.dk" },
|
|
10
10
|
]
|
|
11
11
|
description = "Tools & CLI for interacting with CDSE product APIs"
|
|
12
12
|
readme = "README.md"
|
|
13
|
-
requires-python = ">=3.
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
16
|
"Operating System :: OS Independent",
|
|
@@ -19,22 +19,22 @@ dependencies = [
|
|
|
19
19
|
"typer >= 0.9,< 1",
|
|
20
20
|
"rich >= 13.6,< 14",
|
|
21
21
|
"requests >= 2.28.1,< 3",
|
|
22
|
-
"pyjwt[crypto]
|
|
22
|
+
"pyjwt[crypto] >= 2.8,< 2.10",
|
|
23
23
|
"geopandas >= 0.13.2",
|
|
24
24
|
]
|
|
25
25
|
[project.optional-dependencies]
|
|
26
26
|
test = [
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"pytest==8.2.1",
|
|
27
|
+
"pylint==3.3.1",
|
|
28
|
+
"pytest==8.3.3",
|
|
30
29
|
"pytest-cov==5.0.0",
|
|
31
30
|
"requests-mock==1.12.1",
|
|
31
|
+
"ruff==0.6.6",
|
|
32
32
|
"pytest-mock==3.14.0",
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
[project.urls]
|
|
36
|
-
"Homepage" = "https://github.com/
|
|
37
|
-
"Bug Tracker" = "https://github.com/
|
|
36
|
+
"Homepage" = "https://github.com/CDSETool/CDSETool"
|
|
37
|
+
"Bug Tracker" = "https://github.com/CDSETool/CDSETool/issues"
|
|
38
38
|
|
|
39
39
|
[project.scripts]
|
|
40
40
|
cdsetool = "cdsetool:cli.main"
|
|
@@ -12,9 +12,9 @@ from cdsetool.query import describe_collection, query_features
|
|
|
12
12
|
from cdsetool.monitor import StatusMonitor
|
|
13
13
|
from cdsetool.download import download_features
|
|
14
14
|
|
|
15
|
-
app = typer.Typer()
|
|
15
|
+
app = typer.Typer(no_args_is_help=True)
|
|
16
16
|
|
|
17
|
-
query_app = typer.Typer()
|
|
17
|
+
query_app = typer.Typer(no_args_is_help=True)
|
|
18
18
|
app.add_typer(query_app, name="query")
|
|
19
19
|
|
|
20
20
|
|
|
@@ -120,3 +120,7 @@ def _to_dict(term_list: List[str]) -> Dict[str, str]:
|
|
|
120
120
|
key, value = item.split("=")
|
|
121
121
|
search_terms[key] = value
|
|
122
122
|
return search_terms
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
main()
|
|
@@ -38,12 +38,13 @@ def download_feature(
|
|
|
38
38
|
log = _get_logger(options)
|
|
39
39
|
url = _get_feature_url(feature)
|
|
40
40
|
title = feature.get("properties").get("title")
|
|
41
|
+
temp_dir_usr = _get_temp_dir(options)
|
|
41
42
|
|
|
42
43
|
if not url or not title:
|
|
43
44
|
log.debug(f"Bad URL ('{url}') or title ('{title}')")
|
|
44
45
|
return None
|
|
45
46
|
|
|
46
|
-
filename = title
|
|
47
|
+
filename = title + ".zip"
|
|
47
48
|
result_path = os.path.join(path, filename)
|
|
48
49
|
|
|
49
50
|
if not options.get("overwrite_existing", False) and os.path.exists(result_path):
|
|
@@ -58,19 +59,28 @@ def download_feature(
|
|
|
58
59
|
# Always get a new session, credentials might have expired.
|
|
59
60
|
try:
|
|
60
61
|
session = _get_credentials(options).get_session()
|
|
61
|
-
except
|
|
62
|
-
log.warning(
|
|
62
|
+
except TokenClientConnectionError:
|
|
63
|
+
log.warning("Token client connection failed, retrying..")
|
|
64
|
+
continue
|
|
65
|
+
except TokenExpiredSignatureError:
|
|
66
|
+
log.warning("Token signature expired, retrying..")
|
|
63
67
|
continue
|
|
64
68
|
url = _follow_redirect(url, session)
|
|
65
|
-
|
|
69
|
+
name_dir_prefix = filename.replace(".zip", "____")
|
|
70
|
+
with (
|
|
71
|
+
session.get(url, stream=True) as response,
|
|
72
|
+
tempfile.TemporaryDirectory(
|
|
73
|
+
prefix=name_dir_prefix, dir=temp_dir_usr
|
|
74
|
+
) as temp_dir,
|
|
75
|
+
):
|
|
66
76
|
if response.status_code != 200:
|
|
67
77
|
log.warning(f"Status code {response.status_code}, retrying..")
|
|
68
78
|
time.sleep(60 * (1 + (random.random() / 4)))
|
|
69
79
|
continue
|
|
70
80
|
|
|
71
81
|
status.set_filesize(int(response.headers["Content-Length"]))
|
|
72
|
-
|
|
73
|
-
with
|
|
82
|
+
tmp_file = os.path.join(temp_dir, "download.zip")
|
|
83
|
+
with open(tmp_file, "wb") as file:
|
|
74
84
|
# Server might not send all bytes specified by the
|
|
75
85
|
# Content-Length header before closing connection.
|
|
76
86
|
# Log as a warning and try again.
|
|
@@ -85,8 +95,8 @@ def download_feature(
|
|
|
85
95
|
) as e:
|
|
86
96
|
log.warning(e)
|
|
87
97
|
continue
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
# Close file before copy so all buffers are flushed.
|
|
99
|
+
shutil.copy(tmp_file, result_path)
|
|
90
100
|
return filename
|
|
91
101
|
log.error(f"Failed to download {filename}")
|
|
92
102
|
return None
|
|
@@ -143,3 +153,7 @@ def _get_credentials(options: Dict) -> Credentials:
|
|
|
143
153
|
return options.get("credentials") or Credentials(
|
|
144
154
|
proxies=options.get("proxies", None)
|
|
145
155
|
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _get_temp_dir(options: Dict) -> Union[str, None]:
|
|
159
|
+
return options.get("tmpdir") or None
|
|
@@ -9,8 +9,13 @@ from xml.etree import ElementTree
|
|
|
9
9
|
from datetime import datetime, date
|
|
10
10
|
import re
|
|
11
11
|
import json
|
|
12
|
+
from random import random
|
|
13
|
+
from time import sleep
|
|
12
14
|
import geopandas as gpd
|
|
15
|
+
from requests.exceptions import ChunkedEncodingError
|
|
16
|
+
from urllib3.exceptions import ProtocolError
|
|
13
17
|
from cdsetool.credentials import Credentials
|
|
18
|
+
from cdsetool.logger import NoopLogger
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class _FeatureIterator:
|
|
@@ -48,9 +53,11 @@ class FeatureQuery:
|
|
|
48
53
|
collection: str,
|
|
49
54
|
search_terms: Dict[str, Any],
|
|
50
55
|
proxies: Union[Dict[str, str], None] = None,
|
|
56
|
+
options: Union[Dict[str, Any], None] = None,
|
|
51
57
|
) -> None:
|
|
52
58
|
self.features = []
|
|
53
59
|
self.proxies = proxies
|
|
60
|
+
self.log = (options or {}).get("logger") or NoopLogger()
|
|
54
61
|
self.next_url = _query_url(
|
|
55
62
|
collection, {**search_terms, "exactCount": "1"}, proxies=proxies
|
|
56
63
|
)
|
|
@@ -76,16 +83,29 @@ class FeatureQuery:
|
|
|
76
83
|
session = Credentials.make_session(
|
|
77
84
|
None, False, Credentials.RETRIES, self.proxies
|
|
78
85
|
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
attempts = 0
|
|
87
|
+
while attempts < 10:
|
|
88
|
+
attempts += 1
|
|
89
|
+
try:
|
|
90
|
+
with session.get(self.next_url) as response:
|
|
91
|
+
if response.status_code != 200:
|
|
92
|
+
self.log.warning(
|
|
93
|
+
f"Status code {response.status_code}, retrying.."
|
|
94
|
+
)
|
|
95
|
+
sleep(60 * (1 + (random() / 4)))
|
|
96
|
+
continue
|
|
97
|
+
res = response.json()
|
|
98
|
+
self.features += res.get("features") or []
|
|
99
|
+
|
|
100
|
+
total_results = res.get("properties", {}).get("totalResults")
|
|
101
|
+
if total_results is not None:
|
|
102
|
+
self.total_results = total_results
|
|
103
|
+
|
|
104
|
+
self.__set_next_url(res)
|
|
105
|
+
return
|
|
106
|
+
except (ChunkedEncodingError, ConnectionResetError, ProtocolError) as e:
|
|
107
|
+
self.log.warning(e)
|
|
108
|
+
continue
|
|
89
109
|
|
|
90
110
|
def __set_next_url(self, res) -> None:
|
|
91
111
|
links = res.get("properties", {}).get("links") or []
|
|
@@ -101,11 +121,14 @@ def query_features(
|
|
|
101
121
|
collection: str,
|
|
102
122
|
search_terms: Dict[str, Any],
|
|
103
123
|
proxies: Union[Dict[str, str], None] = None,
|
|
124
|
+
options: Union[Dict[str, Any], None] = None,
|
|
104
125
|
) -> FeatureQuery:
|
|
105
126
|
"""
|
|
106
127
|
Returns an iterator over the features matching the search terms
|
|
107
128
|
"""
|
|
108
|
-
return FeatureQuery(
|
|
129
|
+
return FeatureQuery(
|
|
130
|
+
collection, {"maxRecords": 2000, **search_terms}, proxies, options
|
|
131
|
+
)
|
|
109
132
|
|
|
110
133
|
|
|
111
134
|
def shape_to_wkt(shape: str) -> str:
|
|
@@ -156,7 +179,7 @@ def describe_collection(
|
|
|
156
179
|
)
|
|
157
180
|
|
|
158
181
|
parameters = {}
|
|
159
|
-
if
|
|
182
|
+
if parameter_node_parent is None:
|
|
160
183
|
return parameters
|
|
161
184
|
for parameter_node in parameter_node_parent:
|
|
162
185
|
name = parameter_node.attrib.get("name")
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cdsetool
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.13
|
|
4
4
|
Summary: Tools & CLI for interacting with CDSE product APIs
|
|
5
5
|
Author-email: Jacob Vejby <javej@sdfi.dk>
|
|
6
|
-
Project-URL: Homepage, https://github.com/
|
|
7
|
-
Project-URL: Bug Tracker, https://github.com/
|
|
6
|
+
Project-URL: Homepage, https://github.com/CDSETool/CDSETool
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/CDSETool/CDSETool/issues
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Operating System :: OS Independent
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.9
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Requires-Dist: typer<1,>=0.9
|
|
14
14
|
Requires-Dist: rich<14,>=13.6
|
|
15
15
|
Requires-Dist: requests<3,>=2.28.1
|
|
16
|
-
Requires-Dist: pyjwt[crypto]
|
|
16
|
+
Requires-Dist: pyjwt[crypto]<2.10,>=2.8
|
|
17
17
|
Requires-Dist: geopandas>=0.13.2
|
|
18
18
|
Provides-Extra: test
|
|
19
|
-
Requires-Dist:
|
|
20
|
-
Requires-Dist:
|
|
21
|
-
Requires-Dist: pytest==8.2.1; extra == "test"
|
|
19
|
+
Requires-Dist: pylint==3.3.1; extra == "test"
|
|
20
|
+
Requires-Dist: pytest==8.3.3; extra == "test"
|
|
22
21
|
Requires-Dist: pytest-cov==5.0.0; extra == "test"
|
|
23
22
|
Requires-Dist: requests-mock==1.12.1; extra == "test"
|
|
23
|
+
Requires-Dist: ruff==0.6.6; extra == "test"
|
|
24
24
|
Requires-Dist: pytest-mock==3.14.0; extra == "test"
|
|
25
25
|
|
|
26
26
|
# CDSETool
|
|
@@ -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.13
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
## Usage
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
typer<1,>=0.9
|
|
2
2
|
rich<14,>=13.6
|
|
3
3
|
requests<3,>=2.28.1
|
|
4
|
-
pyjwt[crypto]
|
|
4
|
+
pyjwt[crypto]<2.10,>=2.8
|
|
5
5
|
geopandas>=0.13.2
|
|
6
6
|
|
|
7
7
|
[test]
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
pytest==8.2.1
|
|
8
|
+
pylint==3.3.1
|
|
9
|
+
pytest==8.3.3
|
|
11
10
|
pytest-cov==5.0.0
|
|
12
11
|
requests-mock==1.12.1
|
|
12
|
+
ruff==0.6.6
|
|
13
13
|
pytest-mock==3.14.0
|
|
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
|