sentinelhub 3.10.1__tar.gz → 3.10.3__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.
Files changed (62) hide show
  1. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/PKG-INFO +4 -5
  2. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/pyproject.toml +7 -8
  3. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/_version.py +1 -1
  4. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/base_request.py +7 -5
  5. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/batch/process.py +13 -11
  6. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/batch/utils.py +11 -1
  7. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/byoc.py +7 -5
  8. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/catalog.py +16 -14
  9. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/config.py +3 -1
  10. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/constants.py +4 -4
  11. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/data_collections_bands.py +1 -1
  12. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/download/handlers.py +1 -1
  13. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/download/sentinelhub_client.py +6 -1
  14. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/geometry.py +1 -1
  15. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/.gitignore +0 -0
  16. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/LICENSE.md +0 -0
  17. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/README.md +0 -0
  18. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/.utmzones.geojson +0 -0
  19. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/__init__.py +0 -0
  20. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/__init__.py +0 -0
  21. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/base.py +0 -0
  22. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/batch/__init__.py +0 -0
  23. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/batch/base.py +0 -0
  24. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/batch/statistical.py +0 -0
  25. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/fis.py +0 -0
  26. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/ogc.py +0 -0
  27. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/opensearch.py +0 -0
  28. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/process.py +0 -0
  29. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/statistical.py +0 -0
  30. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/utils.py +0 -0
  31. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/api/wfs.py +0 -0
  32. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/areas.py +0 -0
  33. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/aws/__init__.py +0 -0
  34. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/aws/batch.py +0 -0
  35. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/aws/client.py +0 -0
  36. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/aws/commands.py +0 -0
  37. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/aws/constants.py +0 -0
  38. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/aws/data.py +0 -0
  39. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/aws/data_safe.py +0 -0
  40. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/aws/request.py +0 -0
  41. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/base.py +0 -0
  42. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/commands.py +0 -0
  43. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/data_collections.py +0 -0
  44. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/data_utils.py +0 -0
  45. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/decoding.py +0 -0
  46. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/download/__init__.py +0 -0
  47. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/download/client.py +0 -0
  48. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/download/models.py +0 -0
  49. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/download/rate_limit.py +0 -0
  50. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/download/sentinelhub_statistical_client.py +0 -0
  51. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/download/session.py +0 -0
  52. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/evalscript.py +0 -0
  53. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/exceptions.py +0 -0
  54. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/geo_utils.py +0 -0
  55. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/geopedia/__init__.py +0 -0
  56. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/geopedia/core.py +0 -0
  57. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/geopedia/request.py +0 -0
  58. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/io_utils.py +0 -0
  59. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/py.typed +0 -0
  60. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/testing_utils.py +0 -0
  61. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/time_utils.py +0 -0
  62. {sentinelhub-3.10.1 → sentinelhub-3.10.3}/sentinelhub/types.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: sentinelhub
3
- Version: 3.10.1
3
+ Version: 3.10.3
4
4
  Summary: Python API for Sentinel Hub
5
5
  Project-URL: Homepage, https://github.com/sentinel-hub/sentinelhub-py
6
6
  Project-URL: Documentation, https://sentinelhub-py.readthedocs.io
@@ -73,14 +73,13 @@ Requires-Dist: boto3-stubs>=1.20.0; extra == 'dev'
73
73
  Requires-Dist: build; extra == 'dev'
74
74
  Requires-Dist: click>=8.0.0; extra == 'dev'
75
75
  Requires-Dist: fs; extra == 'dev'
76
- Requires-Dist: moto; extra == 'dev'
76
+ Requires-Dist: moto[s3]>=5.0.0; extra == 'dev'
77
77
  Requires-Dist: mypy>=0.990; extra == 'dev'
78
78
  Requires-Dist: pandas; extra == 'dev'
79
79
  Requires-Dist: pre-commit; extra == 'dev'
80
80
  Requires-Dist: pylint>=2.14.0; extra == 'dev'
81
81
  Requires-Dist: pytest-cov; extra == 'dev'
82
82
  Requires-Dist: pytest-dependency; extra == 'dev'
83
- Requires-Dist: pytest-lazy-fixture; extra == 'dev'
84
83
  Requires-Dist: pytest-mock; extra == 'dev'
85
84
  Requires-Dist: pytest>=4.0.0; extra == 'dev'
86
85
  Requires-Dist: ray[default]; extra == 'dev'
@@ -94,7 +93,7 @@ Requires-Dist: docutils; extra == 'docs'
94
93
  Requires-Dist: ipython; extra == 'docs'
95
94
  Requires-Dist: matplotlib; extra == 'docs'
96
95
  Requires-Dist: nbsphinx; extra == 'docs'
97
- Requires-Dist: sphinx-mdinclude; extra == 'docs'
96
+ Requires-Dist: sphinx-mdinclude==0.5.4; extra == 'docs'
98
97
  Requires-Dist: sphinx-rtd-theme==1.3.0; extra == 'docs'
99
98
  Requires-Dist: sphinx==7.1.2; extra == 'docs'
100
99
  Description-Content-Type: text/markdown
@@ -63,7 +63,7 @@ docs = [
63
63
  "matplotlib",
64
64
  "nbsphinx",
65
65
  "sphinx==7.1.2",
66
- "sphinx_mdinclude",
66
+ "sphinx_mdinclude==0.5.4", # version fixed because 0.6.0 didnt work at release time of 3.10.2
67
67
  "sphinx_rtd_theme==1.3.0",
68
68
  ]
69
69
  dev = [
@@ -73,14 +73,13 @@ dev = [
73
73
  "click>=8.0.0",
74
74
  "fs",
75
75
  "mypy>=0.990",
76
- "moto",
76
+ "moto[s3]>=5.0.0",
77
77
  "pandas",
78
78
  "pre-commit",
79
79
  "pylint>=2.14.0",
80
80
  "pytest>=4.0.0",
81
81
  "pytest-cov",
82
82
  "pytest-dependency",
83
- "pytest-lazy-fixture",
84
83
  "pytest-mock",
85
84
  "ray[default]",
86
85
  "requests-mock",
@@ -111,7 +110,7 @@ preview = true
111
110
  [tool.ruff]
112
111
  line-length = 120
113
112
  target-version = "py38"
114
- select = [
113
+ lint.select = [
115
114
  "F", # pyflakes
116
115
  "E", # pycodestyle
117
116
  "W", # pycodestyle
@@ -144,7 +143,7 @@ select = [
144
143
  "RUF", # ruff rules
145
144
  ]
146
145
  fix = true
147
- fixable = [
146
+ lint.fixable = [
148
147
  "I", # sort imports
149
148
  "F401", # remove redundant imports
150
149
  "UP007", # use new-style union type annotations
@@ -152,7 +151,7 @@ fixable = [
152
151
  "UP037", # remove quotes around types when not necessary
153
152
  "FA100", # import future annotations where necessary (not autofixable ATM)
154
153
  ]
155
- ignore = [
154
+ lint.ignore = [
156
155
  "C408", # complains about `dict()` calls, we use them to avoid too many " in the code
157
156
  "SIM108", # tries to aggresively inline `if`, not always readable
158
157
  "COM812", # trailing comma missing, fights with black
@@ -163,11 +162,11 @@ ignore = [
163
162
  "B028", # always demands a stacklevel argument when warning
164
163
  "PT011", # complains for `pytest.raises(ValueError)` but we use it a lot
165
164
  ]
166
- per-file-ignores = { "__init__.py" = ["F401"], "conf.py" = ["FA100"] }
165
+ lint.per-file-ignores = { "__init__.py" = ["F401"], "conf.py" = ["FA100"] }
167
166
  exclude = [".git", "__pycache__", "build", "dist", "sentinelhub/aws/*"]
168
167
 
169
168
 
170
- [tool.ruff.isort]
169
+ [tool.ruff.lint.isort]
171
170
  section-order = [
172
171
  "future",
173
172
  "standard-library",
@@ -1,3 +1,3 @@
1
1
  """Version of the sentinelhub package."""
2
2
 
3
- __version__ = "3.10.1"
3
+ __version__ = "3.10.3"
@@ -134,11 +134,13 @@ class SentinelHubBaseApiRequest(DataRequest, metaclass=ABCMeta):
134
134
  settings from config object. In case different collections have different restrictions then
135
135
  `SHConfig.sh_base_url` breaks the tie in case it matches one of the data collection URLs.
136
136
  """
137
- data_collection_urls = tuple({
138
- input_data_dict.service_url.rstrip("/")
139
- for input_data_dict in self.payload["input"]["data"]
140
- if isinstance(input_data_dict, InputDataDict) and input_data_dict.service_url is not None
141
- })
137
+ data_collection_urls = tuple(
138
+ {
139
+ input_data_dict.service_url.rstrip("/")
140
+ for input_data_dict in self.payload["input"]["data"]
141
+ if isinstance(input_data_dict, InputDataDict) and input_data_dict.service_url is not None
142
+ }
143
+ )
142
144
  config_base_url = self.config.sh_base_url.rstrip("/")
143
145
 
144
146
  if not data_collection_urls:
@@ -150,17 +150,19 @@ class SentinelHubBatch(BaseBatchClient):
150
150
  :param kwargs: Any other arguments to be added to a dictionary of parameters
151
151
  :return: A dictionary of output parameters
152
152
  """
153
- return remove_undefined({
154
- "defaultTilePath": default_tile_path,
155
- "overwrite": overwrite,
156
- "skipExisting": skip_existing,
157
- "cogOutput": cog_output,
158
- "cogParameters": cog_parameters,
159
- "createCollection": create_collection,
160
- "collectionId": collection_id,
161
- "responses": responses,
162
- **kwargs,
163
- })
153
+ return remove_undefined(
154
+ {
155
+ "defaultTilePath": default_tile_path,
156
+ "overwrite": overwrite,
157
+ "skipExisting": skip_existing,
158
+ "cogOutput": cog_output,
159
+ "cogParameters": cog_parameters,
160
+ "createCollection": create_collection,
161
+ "collectionId": collection_id,
162
+ "responses": responses,
163
+ **kwargs,
164
+ }
165
+ )
164
166
 
165
167
  def iter_tiling_grids(self, **kwargs: Any) -> SentinelHubFeatureIterator:
166
168
  """An iterator over tiling grids
@@ -73,9 +73,12 @@ def monitor_batch_job(
73
73
 
74
74
  progress_bar = tqdm(total=batch_request.tile_count, initial=finished_count, desc="Progress rate")
75
75
  success_bar = tqdm(total=finished_count, initial=success_count, desc="Success rate")
76
+
77
+ monitoring_status = [BatchRequestStatus.ANALYSIS_DONE, BatchRequestStatus.PROCESSING]
76
78
  with progress_bar, success_bar:
77
- while finished_count < batch_request.tile_count:
79
+ while finished_count < batch_request.tile_count and batch_request.status in monitoring_status:
78
80
  time.sleep(sleep_time)
81
+ batch_request = batch_client.get_request(batch_request)
79
82
 
80
83
  tiles_per_status = _get_batch_tiles_per_status(batch_request, batch_client)
81
84
  new_success_count = len(tiles_per_status[BatchTileStatus.PROCESSED])
@@ -93,6 +96,13 @@ def monitor_batch_job(
93
96
  failed_tiles_num = finished_count - success_count
94
97
  if failed_tiles_num:
95
98
  LOGGER.info("Batch job failed for %d tiles", failed_tiles_num)
99
+
100
+ while batch_request.status is BatchRequestStatus.PROCESSING:
101
+ LOGGER.info("Waiting on batch job status update.")
102
+ time.sleep(sleep_time)
103
+ batch_request = batch_client.get_request(batch_request)
104
+
105
+ LOGGER.info("Batch job finished with status %s", batch_request.status.value)
96
106
  return tiles_per_status
97
107
 
98
108
 
@@ -220,11 +220,13 @@ class SentinelHubBYOC(SentinelHubService):
220
220
  headers = {"Content-Type": MimeType.JSON.get_string()}
221
221
 
222
222
  _tile = self._to_dict(tile)
223
- updates = remove_undefined({
224
- "path": _tile["path"],
225
- "coverGeometry": _tile.get("coverGeometry"),
226
- "sensingTime": _tile.get("sensingTime"),
227
- })
223
+ updates = remove_undefined(
224
+ {
225
+ "path": _tile["path"],
226
+ "coverGeometry": _tile.get("coverGeometry"),
227
+ "sensingTime": _tile.get("sensingTime"),
228
+ }
229
+ )
228
230
 
229
231
  return self.client.get_json(
230
232
  url=url, request_type=RequestType.PUT, post_values=updates, headers=headers, use_session=True
@@ -140,20 +140,22 @@ class SentinelHubCatalog(SentinelHubService):
140
140
  if geometry and geometry.crs is not CRS.WGS84:
141
141
  geometry = geometry.transform(CRS.WGS84)
142
142
 
143
- payload = remove_undefined({
144
- "collections": [collection_id],
145
- "datetime": f"{start_time}/{end_time}" if time else None,
146
- "bbox": list(bbox) if bbox else None,
147
- "intersects": geometry.get_geojson(with_crs=False) if geometry else None,
148
- "ids": ids,
149
- "filter": self._prepare_filters(filter, collection, filter_lang),
150
- "filter-lang": filter_lang,
151
- "filter-crs": filter_crs,
152
- "fields": fields,
153
- "distinct": distinct,
154
- "limit": limit,
155
- **kwargs,
156
- })
143
+ payload = remove_undefined(
144
+ {
145
+ "collections": [collection_id],
146
+ "datetime": f"{start_time}/{end_time}" if time else None,
147
+ "bbox": list(bbox) if bbox else None,
148
+ "intersects": geometry.get_geojson(with_crs=False) if geometry else None,
149
+ "ids": ids,
150
+ "filter": self._prepare_filters(filter, collection, filter_lang),
151
+ "filter-lang": filter_lang,
152
+ "filter-crs": filter_crs,
153
+ "fields": fields,
154
+ "distinct": distinct,
155
+ "limit": limit,
156
+ **kwargs,
157
+ }
158
+ )
157
159
 
158
160
  return CatalogSearchIterator(self.client, url, payload)
159
161
 
@@ -24,7 +24,7 @@ SH_CLIENT_SECRET_ENV_VAR = "SH_CLIENT_SECRET"
24
24
 
25
25
 
26
26
  @dataclass(repr=False)
27
- class _SHConfig:
27
+ class _SHConfig: # pylint: disable=too-many-instance-attributes
28
28
  instance_id: str = ""
29
29
  sh_client_id: str = ""
30
30
  sh_client_secret: str = ""
@@ -46,6 +46,7 @@ class _SHConfig:
46
46
  download_sleep_time: float = 5.0
47
47
  download_timeout_seconds: float = 120.0
48
48
  number_of_download_processes: int = 1
49
+ max_retries: int | None = None
49
50
 
50
51
  def __post_init__(self) -> None:
51
52
  if self.sh_auth_base_url is not None:
@@ -94,6 +95,7 @@ class SHConfig(_SHConfig):
94
95
  attempt this number exponentially increases with factor `3`.
95
96
  - `download_timeout_seconds`: Maximum number of seconds before download attempt is canceled.
96
97
  - `number_of_download_processes`: Number of download processes, used to calculate rate-limit sleep time.
98
+ - `max_retries`: Maximum number of retries until an exception is raised.
97
99
 
98
100
  The location of `config.toml` for manual modification can be found with `SHConfig.get_config_location()`.
99
101
  """
@@ -81,7 +81,7 @@ class CRSMeta(EnumMeta):
81
81
 
82
82
  return super().__new__(mcs, cls, bases, classdict)
83
83
 
84
- def __call__(cls, crs_value, *args, **kwargs): # type: ignore[no-untyped-def] # noqa: N805
84
+ def __call__(cls, crs_value, *args, **kwargs): # type: ignore[no-untyped-def]
85
85
  """This is executed whenever CRS('something') is called"""
86
86
  # pylint: disable=signature-differs
87
87
  crs_value = cls._parse_crs(crs_value)
@@ -201,7 +201,7 @@ class CRS(Enum, metaclass=CRSMeta):
201
201
  """
202
202
  return self.name.startswith("UTM")
203
203
 
204
- @functools.lru_cache(maxsize=128) # noqa: B019
204
+ @functools.lru_cache(maxsize=128)
205
205
  def projection(self) -> pyproj.Proj:
206
206
  """Returns a projection in form of pyproj class.
207
207
 
@@ -212,7 +212,7 @@ class CRS(Enum, metaclass=CRSMeta):
212
212
  """
213
213
  return pyproj.Proj(self._get_pyproj_projection_def(), preserve_units=True)
214
214
 
215
- @functools.lru_cache(maxsize=128) # noqa: B019
215
+ @functools.lru_cache(maxsize=128)
216
216
  def pyproj_crs(self) -> pyproj.CRS:
217
217
  """Returns a pyproj CRS class.
218
218
 
@@ -223,7 +223,7 @@ class CRS(Enum, metaclass=CRSMeta):
223
223
  """
224
224
  return pyproj.CRS(self._get_pyproj_projection_def())
225
225
 
226
- @functools.lru_cache(maxsize=512) # noqa: B019
226
+ @functools.lru_cache(maxsize=512)
227
227
  def get_transform_function(self, other: CRS, always_xy: bool = True) -> Callable[..., tuple]:
228
228
  """Returns a function for transforming geometrical objects from one CRS to another. The function will support
229
229
  transformations between any objects that pyproj supports.
@@ -1,4 +1,4 @@
1
- """ Contains information about data collections used by SH """
1
+ """Contains information about data collections used by SH"""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -52,7 +52,7 @@ def fail_user_errors(download_func: Callable[[Self, DownloadRequest], T]) -> Cal
52
52
 
53
53
 
54
54
  def retry_temporary_errors(
55
- download_func: Callable[[SelfWithConfig, DownloadRequest], T]
55
+ download_func: Callable[[SelfWithConfig, DownloadRequest], T],
56
56
  ) -> Callable[[SelfWithConfig, DownloadRequest], T]:
57
57
  """Decorator function for handling server and connection errors"""
58
58
  backoff_coefficient = 3
@@ -15,7 +15,7 @@ from requests import Response
15
15
 
16
16
  from ..config import SHConfig
17
17
  from ..constants import SHConstants
18
- from ..exceptions import SHRateLimitWarning, SHRuntimeWarning
18
+ from ..exceptions import OutOfRequestsException, SHRateLimitWarning, SHRuntimeWarning
19
19
  from ..types import JsonDict
20
20
  from .client import DownloadClient
21
21
  from .handlers import fail_user_errors, retry_temporary_errors
@@ -75,10 +75,12 @@ class SentinelHubDownloadClient(DownloadClient):
75
75
  """
76
76
  Executes the download with a single thread and uses a rate limit object, which is shared between all threads
77
77
  """
78
+ download_attempts = 0
78
79
  while True:
79
80
  sleep_time = self._execute_thread_safe(self.rate_limit.register_next)
80
81
 
81
82
  if sleep_time == 0:
83
+ download_attempts += 1
82
84
  LOGGER.debug(
83
85
  "Sending %s request to %s. Hash of sent request is %s",
84
86
  request.request_type.value,
@@ -89,6 +91,9 @@ class SentinelHubDownloadClient(DownloadClient):
89
91
 
90
92
  if response.status_code == requests.status_codes.codes.TOO_MANY_REQUESTS:
91
93
  warnings.warn("Download rate limit hit", category=SHRateLimitWarning)
94
+ if self.config.max_retries is not None and download_attempts >= self.config.max_retries:
95
+ raise OutOfRequestsException("Maximum number of download attempts reached")
96
+
92
97
  self._execute_thread_safe(self.rate_limit.update, response.headers, default=self.default_retry_time)
93
98
  continue
94
99
 
@@ -150,7 +150,7 @@ class BBox(_BaseGeometry):
150
150
 
151
151
  @staticmethod
152
152
  def _tuple_from_list_or_tuple(
153
- bbox: tuple[float, float, float, float] | tuple[tuple[float, float], tuple[float, float]]
153
+ bbox: tuple[float, float, float, float] | tuple[tuple[float, float], tuple[float, float]],
154
154
  ) -> tuple[float, float, float, float]:
155
155
  """Converts a list or tuple representation of a bbox into a flat tuple representation.
156
156
 
File without changes
File without changes
File without changes