datacosmos 0.0.19__tar.gz → 0.0.20__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.

Potentially problematic release.


This version of datacosmos might be problematic. Click here for more details.

Files changed (69) hide show
  1. {datacosmos-0.0.19 → datacosmos-0.0.20}/PKG-INFO +1 -1
  2. datacosmos-0.0.20/datacosmos/stac/storage/storage_base.py +77 -0
  3. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/storage/uploader.py +57 -30
  4. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos.egg-info/PKG-INFO +1 -1
  5. {datacosmos-0.0.19 → datacosmos-0.0.20}/pyproject.toml +1 -1
  6. datacosmos-0.0.19/datacosmos/stac/storage/storage_base.py +0 -40
  7. {datacosmos-0.0.19 → datacosmos-0.0.20}/LICENSE.md +0 -0
  8. {datacosmos-0.0.19 → datacosmos-0.0.20}/README.md +0 -0
  9. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/__init__.py +0 -0
  10. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/auth/__init__.py +0 -0
  11. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/auth/base_authenticator.py +0 -0
  12. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/auth/local_authenticator.py +0 -0
  13. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/auth/local_token_fetcher.py +0 -0
  14. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/auth/m2m_authenticator.py +0 -0
  15. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/auth/token.py +0 -0
  16. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/__init__.py +0 -0
  17. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/auth/__init__.py +0 -0
  18. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/auth/factory.py +0 -0
  19. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/config.py +0 -0
  20. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/constants.py +0 -0
  21. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/loaders/yaml_source.py +0 -0
  22. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/models/__init__.py +0 -0
  23. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/models/authentication_config.py +0 -0
  24. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/models/local_user_account_authentication_config.py +0 -0
  25. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/models/m2m_authentication_config.py +0 -0
  26. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/models/no_authentication_config.py +0 -0
  27. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/config/models/url.py +0 -0
  28. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/datacosmos_client.py +0 -0
  29. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/exceptions/__init__.py +0 -0
  30. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/exceptions/datacosmos_error.py +0 -0
  31. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/exceptions/stac_validation_error.py +0 -0
  32. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/__init__.py +0 -0
  33. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/collection/__init__.py +0 -0
  34. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/collection/collection_client.py +0 -0
  35. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/collection/models/__init__.py +0 -0
  36. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/collection/models/collection_update.py +0 -0
  37. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/constants/__init__.py +0 -0
  38. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/constants/satellite_name_mapping.py +0 -0
  39. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/enums/__init__.py +0 -0
  40. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/enums/processing_level.py +0 -0
  41. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/enums/product_type.py +0 -0
  42. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/enums/season.py +0 -0
  43. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/item/__init__.py +0 -0
  44. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/item/item_client.py +0 -0
  45. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/item/models/__init__.py +0 -0
  46. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/item/models/asset.py +0 -0
  47. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/item/models/catalog_search_parameters.py +0 -0
  48. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/item/models/datacosmos_item.py +0 -0
  49. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/item/models/eo_band.py +0 -0
  50. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/item/models/item_update.py +0 -0
  51. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/item/models/raster_band.py +0 -0
  52. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/stac_client.py +0 -0
  53. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/storage/__init__.py +0 -0
  54. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/storage/dataclasses/__init__.py +0 -0
  55. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/storage/dataclasses/upload_path.py +0 -0
  56. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/stac/storage/storage_client.py +0 -0
  57. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/utils/__init__.py +0 -0
  58. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/utils/http_response/__init__.py +0 -0
  59. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/utils/http_response/check_api_response.py +0 -0
  60. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/utils/http_response/models/__init__.py +0 -0
  61. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/utils/http_response/models/datacosmos_error.py +0 -0
  62. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/utils/http_response/models/datacosmos_response.py +0 -0
  63. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos/utils/url.py +0 -0
  64. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos.egg-info/SOURCES.txt +0 -0
  65. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos.egg-info/dependency_links.txt +0 -0
  66. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos.egg-info/requires.txt +0 -0
  67. {datacosmos-0.0.19 → datacosmos-0.0.20}/datacosmos.egg-info/top_level.txt +0 -0
  68. {datacosmos-0.0.19 → datacosmos-0.0.20}/setup.cfg +0 -0
  69. {datacosmos-0.0.19 → datacosmos-0.0.20}/tests/test_pass.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datacosmos
3
- Version: 0.0.19
3
+ Version: 0.0.20
4
4
  Summary: A library for interacting with DataCosmos from Python code
5
5
  Author-email: Open Cosmos <support@open-cosmos.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -0,0 +1,77 @@
1
+ """Base class providing common storage helpers (threading, MIME guess, futures)."""
2
+
3
+ import mimetypes
4
+ from concurrent.futures import Future, ThreadPoolExecutor, wait
5
+ from typing import Any, Callable, Dict, Iterable, List, Tuple
6
+
7
+ from datacosmos.datacosmos_client import DatacosmosClient
8
+ from datacosmos.exceptions.datacosmos_error import DatacosmosError
9
+
10
+
11
+ class StorageBase:
12
+ """Base class providing common storage helpers (threading, MIME guess, futures)."""
13
+
14
+ def __init__(self, client: DatacosmosClient):
15
+ """Base class providing common storage helpers (threading, MIME guess, futures)."""
16
+ self.client = client
17
+ self.base_url = client.config.datacosmos_cloud_storage.as_domain_url()
18
+
19
+ def _guess_mime(self, src: str) -> str:
20
+ mime, _ = mimetypes.guess_type(src)
21
+ return mime or "application/octet-stream"
22
+
23
+ def run_in_threads(
24
+ self,
25
+ fn: Callable[..., Any],
26
+ jobs: Iterable[Tuple[Any, ...]],
27
+ max_workers: int,
28
+ timeout: float,
29
+ ) -> Tuple[List[Any], List[Dict[str, Any]]]:
30
+ """Run the callable `fn(*args)` over the iterable of jobs in parallel threads.
31
+
32
+ Collects successes and failures without aborting the batch on individual errors.
33
+
34
+ Args:
35
+ fn: The function to execute.
36
+ jobs: An iterable of tuples, where each tuple is unpacked as fn(*args).
37
+ max_workers: Maximum number of threads to use.
38
+ timeout: Timeout for the entire batch.
39
+
40
+ Returns:
41
+ A tuple containing (successes: List[Any], failures: List[Dict[str, Any]]).
42
+ Failures include the exception and job arguments.
43
+
44
+ Raises:
45
+ DatacosmosError: If the entire batch times out.
46
+ """
47
+ futures: List[Future] = []
48
+
49
+ executor = ThreadPoolExecutor(max_workers=max_workers)
50
+
51
+ try:
52
+ for args in jobs:
53
+ futures.append(executor.submit(fn, *args))
54
+
55
+ # Wait until all futures are done or the timeout is reached
56
+ done, not_done = wait(futures, timeout=timeout)
57
+
58
+ successes = []
59
+ failures = []
60
+
61
+ for future in done:
62
+ try:
63
+ result = future.result()
64
+ except Exception as e:
65
+ failures.append({'error': str(e), 'exception': e})
66
+ else:
67
+ successes.append(result)
68
+
69
+ if not_done:
70
+ # The executor's shutdown wait must be skipped to allow cancellation
71
+ raise DatacosmosError("Batch processing failed: operation timed out.")
72
+
73
+ return successes, failures
74
+ finally:
75
+ # Shutdown without waiting to enable timeout handling
76
+ # The wait call already established which jobs finished
77
+ executor.shutdown(wait=False)
@@ -1,6 +1,7 @@
1
1
  """Handles uploading files to Datacosmos storage and registering STAC items."""
2
2
 
3
3
  from pathlib import Path
4
+ from typing import Any
4
5
 
5
6
  from pydantic import TypeAdapter
6
7
 
@@ -13,7 +14,7 @@ from datacosmos.stac.storage.storage_base import StorageBase
13
14
 
14
15
 
15
16
  class Uploader(StorageBase):
16
- """Upload a STAC item and its assets to Datacosmos storage, then register the item in the STAC API."""
17
+ """Upload a STAC item and its assets to Datacosmos storage and register the item in the STAC API."""
17
18
 
18
19
  def __init__(self, client: DatacosmosClient):
19
20
  """Initialize the uploader.
@@ -32,17 +33,25 @@ class Uploader(StorageBase):
32
33
  included_assets: list[str] | bool = True,
33
34
  max_workers: int = 4,
34
35
  time_out: float = 60 * 60 * 1,
35
- ) -> DatacosmosItem:
36
- """Upload a STAC item (and optionally its assets) to Datacosmos.
36
+ ) -> tuple[DatacosmosItem, list[str], list[dict[str, Any]]]:
37
+ """Upload a STAC item (and optionally its assets) to Datacosmos in parallel threads.
37
38
 
38
- `item` can be either:
39
- a DatacosmosItem instance, or
40
- the path to an item JSON file on disk.
41
-
42
- If `included_assets` is:
43
- True → upload every asset in the item
44
- list upload only the asset keys in that list
45
- False → upload nothing; just register the item
39
+ Args:
40
+ item (DatacosmosItem | str):
41
+ - a DatacosmosItem instance, or
42
+ - the path to an item JSON file on disk.
43
+ project_id (str): The project ID to upload assets to.
44
+ assets_path (str | None): Base directory where local asset files are located.
45
+ included_assets (list[str] | bool):
46
+ - True → upload every asset in the item.
47
+ - list[str] → upload only the asset keys in that list.
48
+ - False → skip asset upload; just register the item.
49
+ max_workers (int): Maximum number of parallel threads for asset upload.
50
+ time_out (float): Timeout in seconds for the entire asset batch upload.
51
+
52
+ Returns:
53
+ tuple[DatacosmosItem, list[str], list[dict[str, Any]]]:
54
+ The updated DatacosmosItem, a list of asset keys that were uploaded successfully, and a list of upload failures.
46
55
  """
47
56
  if not assets_path and not isinstance(item, str):
48
57
  raise ValueError(
@@ -54,23 +63,45 @@ class Uploader(StorageBase):
54
63
  item = self._load_item(item_filename)
55
64
  assets_path = assets_path or str(Path(item_filename).parent)
56
65
 
66
+ if not isinstance(item, DatacosmosItem):
67
+ raise TypeError(f"item must be a DatacosmosItem, got {type(item).__name__}")
68
+
57
69
  assets_path = assets_path or str(Path.cwd())
58
70
 
59
- upload_assets = (
60
- included_assets
61
- if isinstance(included_assets, list)
62
- else item.assets.keys()
63
- if included_assets is True
64
- else []
65
- )
71
+ if included_assets is False:
72
+ upload_assets: list[str] = []
73
+ elif included_assets is True:
74
+ upload_assets = list(item.assets.keys())
75
+ elif isinstance(included_assets, list):
76
+ upload_assets = included_assets
77
+ else:
78
+ upload_assets = []
66
79
 
67
80
  jobs = [
68
81
  (item, asset_key, assets_path, project_id) for asset_key in upload_assets
69
82
  ]
70
- self._run_in_threads(self._upload_asset, jobs, max_workers, time_out)
71
83
 
72
- self.item_client.add_item(item)
73
- return item
84
+ if not jobs:
85
+ self.item_client.add_item(item)
86
+ return item, [], []
87
+
88
+ successes, failures = self.run_in_threads(
89
+ self._upload_asset, jobs, max_workers, time_out
90
+ )
91
+
92
+ # Register the item if the overall process didn't time out
93
+ # and there was at least one successful upload.
94
+ if successes:
95
+ self.item_client.add_item(item)
96
+
97
+ return item, successes, failures
98
+
99
+ @staticmethod
100
+ def _load_item(item_json_file_path: str) -> DatacosmosItem:
101
+ """Load a DatacosmosItem from a JSON file on disk."""
102
+ with open(item_json_file_path, "rb") as file:
103
+ data = file.read().decode("utf-8")
104
+ return TypeAdapter(DatacosmosItem).validate_json(data)
74
105
 
75
106
  def upload_from_file(
76
107
  self, src: str, dst: str, mime_type: str | None = None
@@ -83,19 +114,13 @@ class Uploader(StorageBase):
83
114
  response = self.client.put(url, data=f, headers=headers)
84
115
  response.raise_for_status()
85
116
 
86
- @staticmethod
87
- def _load_item(item_json_file_path: str) -> DatacosmosItem:
88
- """Load a DatacosmosItem from a JSON file on disk."""
89
- with open(item_json_file_path, "rb") as file:
90
- data = file.read().decode("utf-8")
91
- return TypeAdapter(DatacosmosItem).validate_json(data)
92
-
93
117
  def _upload_asset(
94
118
  self, item: DatacosmosItem, asset_key: str, assets_path: str, project_id: str
95
- ) -> None:
119
+ ) -> str:
96
120
  """Upload a single asset file and update its href inside the item object.
97
121
 
98
- Runs in parallel via _run_in_threads().
122
+ Returns:
123
+ str: The asset_key upon successful upload.
99
124
  """
100
125
  asset = item.assets[asset_key]
101
126
 
@@ -117,6 +142,8 @@ class Uploader(StorageBase):
117
142
  self._update_asset_href(asset) # turn href into public URL
118
143
  self.upload_from_file(src, str(upload_path), mime_type=asset.type)
119
144
 
145
+ return asset_key
146
+
120
147
  def _update_asset_href(self, asset: Asset) -> None:
121
148
  """Convert the storage key to a public HTTPS URL."""
122
149
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datacosmos
3
- Version: 0.0.19
3
+ Version: 0.0.20
4
4
  Summary: A library for interacting with DataCosmos from Python code
5
5
  Author-email: Open Cosmos <support@open-cosmos.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "datacosmos"
7
- version = "0.0.19"
7
+ version = "0.0.20"
8
8
  authors = [
9
9
  { name="Open Cosmos", email="support@open-cosmos.com" },
10
10
  ]
@@ -1,40 +0,0 @@
1
- """Base class providing common storage helpers (threading, MIME guess, futures)."""
2
-
3
- import mimetypes
4
- from concurrent.futures import ThreadPoolExecutor, wait
5
-
6
- from datacosmos.datacosmos_client import DatacosmosClient
7
-
8
-
9
- class StorageBase:
10
- """Base class providing common storage helpers (threading, MIME guess, futures)."""
11
-
12
- def __init__(self, client: DatacosmosClient):
13
- """Base class providing common storage helpers (threading, MIME guess, futures)."""
14
- self.client = client
15
- self.base_url = client.config.datacosmos_cloud_storage.as_domain_url()
16
-
17
- def _guess_mime(self, src: str) -> str:
18
- mime, _ = mimetypes.guess_type(src)
19
- return mime or "application/octet-stream"
20
-
21
- def _run_in_threads(self, fn, fn_args, max_workers: int, timeout: float):
22
- """Run the callable `fn(*args)` over the iterable of jobs in parallel threads.
23
-
24
- `jobs` should be a list of tuples, each tuple unpacked as fn(*args).
25
- """
26
- futures = []
27
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
28
- for args in fn_args:
29
- futures.append(executor.submit(fn, *args))
30
- done, not_done = wait(futures, timeout=timeout)
31
- errors = []
32
- for future in done:
33
- try:
34
- future.result()
35
- except Exception as e:
36
- errors.append(e)
37
- for future in not_done:
38
- future.cancel()
39
- if errors:
40
- raise errors[0]
File without changes
File without changes
File without changes