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.
@@ -1,26 +1,26 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cdsetool
3
- Version: 0.2.11
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/SDFIdk/CDSETool
7
- Project-URL: Bug Tracker, https://github.com/SDFIdk/CDSETool/issues
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.8
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]~=2.8.0
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: black==24.4.2; extra == "test"
20
- Requires-Dist: pylint==3.2.2; extra == "test"
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.11
99
+ pip install cdsetool==0.2.13
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.11
74
+ pip install cdsetool==0.2.13
75
75
  ```
76
76
 
77
77
  ## Usage
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdsetool"
7
- version = "0.2.11"
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.8"
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] ~= 2.8.0",
22
+ "pyjwt[crypto] >= 2.8,< 2.10",
23
23
  "geopandas >= 0.13.2",
24
24
  ]
25
25
  [project.optional-dependencies]
26
26
  test = [
27
- "black==24.4.2",
28
- "pylint==3.2.2",
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/SDFIdk/CDSETool"
37
- "Bug Tracker" = "https://github.com/SDFIdk/CDSETool/issues"
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"
@@ -52,7 +52,6 @@ def _concurrent_process(
52
52
 
53
53
  # Continue until no more futures are queued
54
54
  while futures:
55
-
56
55
  # Wait for the first future(s) to complete
57
56
  done, not_done = wait(futures, return_when=FIRST_COMPLETED)
58
57
  futures = list(not_done)
@@ -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()
@@ -147,7 +147,7 @@ class Credentials: # pylint: disable=too-few-public-methods disable=too-many-in
147
147
  caller=self,
148
148
  authorization=False,
149
149
  max_retries=Retry(
150
- total=2,
150
+ total=15,
151
151
  backoff_factor=0.5,
152
152
  allowed_methods=None,
153
153
  raise_on_status=False,
@@ -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.replace(".SAFE", ".zip")
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 (TokenClientConnectionError, TokenExpiredSignatureError) as e:
62
- log.warning(e)
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
- with session.get(url, stream=True) as response:
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 tempfile.NamedTemporaryFile() as file:
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
- shutil.copy(file.name, result_path)
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
- with session.get(self.next_url) as response:
80
- response.raise_for_status()
81
- res = response.json()
82
- self.features += res.get("features") or []
83
-
84
- total_results = res.get("properties", {}).get("totalResults")
85
- if total_results is not None:
86
- self.total_results = total_results
87
-
88
- self.__set_next_url(res)
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(collection, {"maxRecords": 2000, **search_terms}, proxies)
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 not parameter_node_parent:
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.11
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/SDFIdk/CDSETool
7
- Project-URL: Bug Tracker, https://github.com/SDFIdk/CDSETool/issues
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.8
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]~=2.8.0
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: black==24.4.2; extra == "test"
20
- Requires-Dist: pylint==3.2.2; extra == "test"
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.11
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]~=2.8.0
4
+ pyjwt[crypto]<2.10,>=2.8
5
5
  geopandas>=0.13.2
6
6
 
7
7
  [test]
8
- black==24.4.2
9
- pylint==3.2.2
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