datacosmos 0.0.11__tar.gz → 0.0.12__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 (58) hide show
  1. {datacosmos-0.0.11 → datacosmos-0.0.12}/PKG-INFO +1 -1
  2. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/stac_client.py +2 -1
  3. datacosmos-0.0.12/datacosmos/stac/storage/dataclasses/upload_path.py +42 -0
  4. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/storage/storage_client.py +2 -0
  5. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/storage/uploader.py +42 -13
  6. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos.egg-info/PKG-INFO +1 -1
  7. {datacosmos-0.0.11 → datacosmos-0.0.12}/pyproject.toml +1 -1
  8. datacosmos-0.0.11/datacosmos/stac/storage/dataclasses/upload_path.py +0 -63
  9. {datacosmos-0.0.11 → datacosmos-0.0.12}/LICENSE.md +0 -0
  10. {datacosmos-0.0.11 → datacosmos-0.0.12}/README.md +0 -0
  11. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/__init__.py +0 -0
  12. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/__init__.py +0 -0
  13. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/config.py +0 -0
  14. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/__init__.py +0 -0
  15. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/authentication_config.py +0 -0
  16. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/local_user_account_authentication_config.py +0 -0
  17. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/m2m_authentication_config.py +0 -0
  18. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/no_authentication_config.py +0 -0
  19. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/url.py +0 -0
  20. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/datacosmos_client.py +0 -0
  21. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/exceptions/__init__.py +0 -0
  22. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/exceptions/datacosmos_exception.py +0 -0
  23. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/__init__.py +0 -0
  24. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/collection/__init__.py +0 -0
  25. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/collection/collection_client.py +0 -0
  26. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/collection/models/__init__.py +0 -0
  27. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/collection/models/collection_update.py +0 -0
  28. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/constants/__init__.py +0 -0
  29. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/constants/satellite_name_mapping.py +0 -0
  30. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/enums/__init__.py +0 -0
  31. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/enums/processing_level.py +0 -0
  32. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/enums/product_type.py +0 -0
  33. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/enums/season.py +0 -0
  34. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/__init__.py +0 -0
  35. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/item_client.py +0 -0
  36. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/__init__.py +0 -0
  37. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/asset.py +0 -0
  38. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/catalog_search_parameters.py +0 -0
  39. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/datacosmos_item.py +0 -0
  40. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/eo_band.py +0 -0
  41. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/item_update.py +0 -0
  42. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/raster_band.py +0 -0
  43. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/storage/__init__.py +0 -0
  44. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/storage/dataclasses/__init__.py +0 -0
  45. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/storage/storage_base.py +0 -0
  46. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/__init__.py +0 -0
  47. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/__init__.py +0 -0
  48. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/check_api_response.py +0 -0
  49. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/models/__init__.py +0 -0
  50. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/models/datacosmos_error.py +0 -0
  51. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/models/datacosmos_response.py +0 -0
  52. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/url.py +0 -0
  53. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos.egg-info/SOURCES.txt +0 -0
  54. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos.egg-info/dependency_links.txt +0 -0
  55. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos.egg-info/requires.txt +0 -0
  56. {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos.egg-info/top_level.txt +0 -0
  57. {datacosmos-0.0.11 → datacosmos-0.0.12}/setup.cfg +0 -0
  58. {datacosmos-0.0.11 → datacosmos-0.0.12}/tests/test_pass.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datacosmos
3
- Version: 0.0.11
3
+ Version: 0.0.12
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
@@ -1,5 +1,6 @@
1
1
  """Unified interface for STAC API, combining Item & Collection operations."""
2
2
 
3
+ from datacosmos.datacosmos_client import DatacosmosClient
3
4
  from datacosmos.stac.collection.collection_client import CollectionClient
4
5
  from datacosmos.stac.item.item_client import ItemClient
5
6
  from datacosmos.stac.storage.storage_client import StorageClient
@@ -8,7 +9,7 @@ from datacosmos.stac.storage.storage_client import StorageClient
8
9
  class STACClient(ItemClient, CollectionClient, StorageClient):
9
10
  """Unified interface for STAC API, combining Item & Collection operations."""
10
11
 
11
- def __init__(self, client):
12
+ def __init__(self, client: DatacosmosClient):
12
13
  """Initialize the STACClient with a DatacosmosClient."""
13
14
  ItemClient.__init__(self, client)
14
15
  CollectionClient.__init__(self, client)
@@ -0,0 +1,42 @@
1
+ """Dataclass for generating the upload key of an asset."""
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+ from datacosmos.stac.item.models.datacosmos_item import DatacosmosItem
7
+
8
+
9
+ @dataclass
10
+ class UploadPath:
11
+ """Storage key in the form: project/<project-id>/<item-id>/<asset-name>."""
12
+
13
+ project_id: str
14
+ item_id: str
15
+ asset_name: str
16
+
17
+ def __str__(self) -> str:
18
+ """Path in the form: project/<project-id>/<item-id>/<asset-name>."""
19
+ return f"project/{self.project_id}/{self.item_id}/{self.asset_name}".rstrip("/")
20
+
21
+ @classmethod
22
+ def from_item_path(
23
+ cls,
24
+ item: DatacosmosItem,
25
+ project_id: str,
26
+ asset_name: str,
27
+ ) -> "UploadPath":
28
+ """Create an UploadPath for the given item/asset."""
29
+ return cls(project_id=project_id, item_id=item.id, asset_name=asset_name)
30
+
31
+ @classmethod
32
+ def from_path(cls, path: str) -> "UploadPath":
33
+ """Reverse-parse a storage key back into its components."""
34
+ parts = Path(path).parts
35
+ if len(parts) < 4 or parts[0] != "project":
36
+ raise ValueError(f"Invalid path: {path}")
37
+
38
+ project_id, item_id, *rest = parts[1:]
39
+ asset_name = "/".join(rest)
40
+ if not asset_name:
41
+ raise ValueError(f"Asset name is missing in path: {path}")
42
+ return cls(project_id=project_id, item_id=item_id, asset_name=asset_name)
@@ -16,6 +16,7 @@ class StorageClient:
16
16
  def upload_item(
17
17
  self,
18
18
  item: DatacosmosItem,
19
+ project_id: str,
19
20
  assets_path: str | None = None,
20
21
  included_assets: list[str] | bool = True,
21
22
  max_workers: int = 4,
@@ -24,6 +25,7 @@ class StorageClient:
24
25
  """Proxy to Uploader.upload_item, without needing to pass client each call."""
25
26
  return self.uploader.upload_item(
26
27
  item=item,
28
+ project_id=project_id,
27
29
  assets_path=assets_path,
28
30
  included_assets=included_assets,
29
31
  max_workers=max_workers,
@@ -13,22 +13,37 @@ from datacosmos.stac.storage.storage_base import StorageBase
13
13
 
14
14
 
15
15
  class Uploader(StorageBase):
16
- """Handles uploading files to Datacosmos storage and registering STAC items."""
16
+ """Upload a STAC item and its assets to Datacosmos storage, then register the item in the STAC API."""
17
17
 
18
18
  def __init__(self, client: DatacosmosClient):
19
- """Handles uploading files to Datacosmos storage and registering STAC items."""
19
+ """Initialize the uploader.
20
+
21
+ Args:
22
+ client (DatacosmosClient): Pre-configured DatacosmosClient.
23
+ """
20
24
  super().__init__(client)
21
25
  self.item_client = ItemClient(client)
22
26
 
23
27
  def upload_item(
24
28
  self,
25
- item: DatacosmosItem,
29
+ item: DatacosmosItem | str,
30
+ project_id: str,
26
31
  assets_path: str | None = None,
27
32
  included_assets: list[str] | bool = True,
28
33
  max_workers: int = 4,
29
34
  time_out: float = 60 * 60 * 1,
30
35
  ) -> DatacosmosItem:
31
- """Upload a STAC item and its assets to Datacosmos."""
36
+ """Upload a STAC item (and optionally its assets) to Datacosmos.
37
+
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
46
+ """
32
47
  if not assets_path and not isinstance(item, str):
33
48
  raise ValueError(
34
49
  "assets_path must be provided if item is not the path to an item file."
@@ -37,8 +52,7 @@ class Uploader(StorageBase):
37
52
  if isinstance(item, str):
38
53
  item_filename = item
39
54
  item = self._load_item(item_filename)
40
- if not assets_path:
41
- assets_path = str(Path(item_filename).parent)
55
+ assets_path = assets_path or str(Path(item_filename).parent)
42
56
 
43
57
  assets_path = assets_path or str(Path.cwd())
44
58
 
@@ -50,18 +64,18 @@ class Uploader(StorageBase):
50
64
  else []
51
65
  )
52
66
 
53
- jobs = [(item, asset_key, assets_path) for asset_key in upload_assets]
54
-
67
+ jobs = [
68
+ (item, asset_key, assets_path, project_id) for asset_key in upload_assets
69
+ ]
55
70
  self._run_in_threads(self._upload_asset, jobs, max_workers, time_out)
56
71
 
57
72
  self.item_client.add_item(item)
58
-
59
73
  return item
60
74
 
61
75
  def upload_from_file(
62
76
  self, src: str, dst: str, mime_type: str | None = None
63
77
  ) -> None:
64
- """Uploads a single file to the specified destination path."""
78
+ """Upload a single file to the specified destination path in storage."""
65
79
  url = self.base_url.with_suffix(dst)
66
80
  mime = mime_type or self._guess_mime(src)
67
81
  headers = {"Content-Type": mime}
@@ -71,25 +85,40 @@ class Uploader(StorageBase):
71
85
 
72
86
  @staticmethod
73
87
  def _load_item(item_json_file_path: str) -> DatacosmosItem:
88
+ """Load a DatacosmosItem from a JSON file on disk."""
74
89
  with open(item_json_file_path, "rb") as file:
75
90
  data = file.read().decode("utf-8")
76
91
  return TypeAdapter(DatacosmosItem).validate_json(data)
77
92
 
78
93
  def _upload_asset(
79
- self, item: DatacosmosItem, asset_key: str, assets_path: str
94
+ self, item: DatacosmosItem, asset_key: str, assets_path: str, project_id: str
80
95
  ) -> None:
96
+ """Upload a single asset file and update its href inside the item object.
97
+
98
+ Runs in parallel via _run_in_threads().
99
+ """
81
100
  asset = item.assets[asset_key]
82
- upload_path = UploadPath.from_item_path(item, "", Path(asset.href).name)
101
+
102
+ # Build storage key: project/<project_id>/<item_id>/<asset_name>
103
+ upload_path = UploadPath.from_item_path(
104
+ item,
105
+ project_id,
106
+ Path(asset.href).name,
107
+ )
108
+
83
109
  local_src = Path(assets_path) / asset.href
84
110
  if local_src.exists():
85
111
  src = str(local_src)
86
112
  asset.href = f"file:///{upload_path}"
87
113
  else:
114
+ # fallback: try matching just the filename inside assets_path
88
115
  src = str(Path(assets_path) / Path(asset.href).name)
89
- self._update_asset_href(asset)
116
+
117
+ self._update_asset_href(asset) # turn href into public URL
90
118
  self.upload_from_file(src, str(upload_path), mime_type=asset.type)
91
119
 
92
120
  def _update_asset_href(self, asset: Asset) -> None:
121
+ """Convert the storage key to a public HTTPS URL."""
93
122
  try:
94
123
  url = self.client.config.datacosmos_public_cloud_storage.as_domain_url()
95
124
  new_href = url.with_base(asset.href) # type: ignore
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datacosmos
3
- Version: 0.0.11
3
+ Version: 0.0.12
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.11"
7
+ version = "0.0.12"
8
8
  authors = [
9
9
  { name="Open Cosmos", email="support@open-cosmos.com" },
10
10
  ]
@@ -1,63 +0,0 @@
1
- """Dataclass for retrieving the upload path of a file."""
2
-
3
- from dataclasses import dataclass
4
- from datetime import datetime
5
- from pathlib import Path
6
-
7
- import structlog
8
-
9
- from datacosmos.stac.enums.processing_level import ProcessingLevel
10
- from datacosmos.stac.item.models.datacosmos_item import DatacosmosItem
11
-
12
- logger = structlog.get_logger()
13
-
14
-
15
- @dataclass
16
- class UploadPath:
17
- """Dataclass for retrieving the upload path of a file."""
18
-
19
- mission: str
20
- level: ProcessingLevel
21
- day: int
22
- month: int
23
- year: int
24
- id: str
25
- path: str
26
-
27
- def __str__(self):
28
- """Return a human-readable string representation of the Path."""
29
- path = f"full/{self.mission.lower()}/{self.level.value.lower()}/{self.year:02}/{self.month:02}/{self.day:02}/{self.id}/{self.path}"
30
- return path.removesuffix("/")
31
-
32
- @classmethod
33
- def from_item_path(
34
- cls, item: DatacosmosItem, mission: str, item_path: str
35
- ) -> "Path":
36
- """Create a Path instance from a DatacosmosItem and a path."""
37
- dt = datetime.strptime(item.properties["datetime"], "%Y-%m-%dT%H:%M:%SZ")
38
- path = UploadPath(
39
- mission=mission,
40
- level=ProcessingLevel(item.properties["processing:level"]),
41
- day=dt.day,
42
- month=dt.month,
43
- year=dt.year,
44
- id=item.id,
45
- path=item_path,
46
- )
47
- return cls(**path.__dict__)
48
-
49
- @classmethod
50
- def from_path(cls, path: str) -> "Path":
51
- """Create a Path instance from a string path."""
52
- parts = path.split("/")
53
- if len(parts) < 7:
54
- raise ValueError(f"Invalid path {path}")
55
- return cls(
56
- mission=parts[0],
57
- level=ProcessingLevel(parts[1]),
58
- day=int(parts[4]),
59
- month=int(parts[3]),
60
- year=int(parts[2]),
61
- id=parts[5],
62
- path="/".join(parts[6:]),
63
- )
File without changes
File without changes
File without changes