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.
- {datacosmos-0.0.11 → datacosmos-0.0.12}/PKG-INFO +1 -1
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/stac_client.py +2 -1
- datacosmos-0.0.12/datacosmos/stac/storage/dataclasses/upload_path.py +42 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/storage/storage_client.py +2 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/storage/uploader.py +42 -13
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos.egg-info/PKG-INFO +1 -1
- {datacosmos-0.0.11 → datacosmos-0.0.12}/pyproject.toml +1 -1
- datacosmos-0.0.11/datacosmos/stac/storage/dataclasses/upload_path.py +0 -63
- {datacosmos-0.0.11 → datacosmos-0.0.12}/LICENSE.md +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/README.md +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/config.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/authentication_config.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/local_user_account_authentication_config.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/m2m_authentication_config.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/no_authentication_config.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/url.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/datacosmos_client.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/exceptions/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/exceptions/datacosmos_exception.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/collection/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/collection/collection_client.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/collection/models/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/collection/models/collection_update.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/constants/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/constants/satellite_name_mapping.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/enums/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/enums/processing_level.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/enums/product_type.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/enums/season.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/item_client.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/asset.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/catalog_search_parameters.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/datacosmos_item.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/eo_band.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/item_update.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/raster_band.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/storage/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/storage/dataclasses/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/storage/storage_base.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/check_api_response.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/models/__init__.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/models/datacosmos_error.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/models/datacosmos_response.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/url.py +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos.egg-info/SOURCES.txt +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos.egg-info/dependency_links.txt +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos.egg-info/requires.txt +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos.egg-info/top_level.txt +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/setup.cfg +0 -0
- {datacosmos-0.0.11 → datacosmos-0.0.12}/tests/test_pass.py +0 -0
|
@@ -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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
|
|
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 = [
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/m2m_authentication_config.py
RENAMED
|
File without changes
|
{datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/config/models/no_authentication_config.py
RENAMED
|
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
|
{datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/collection/models/collection_update.py
RENAMED
|
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
|
|
File without changes
|
{datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/stac/item/models/catalog_search_parameters.py
RENAMED
|
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
|
{datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/check_api_response.py
RENAMED
|
File without changes
|
|
File without changes
|
{datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/models/datacosmos_error.py
RENAMED
|
File without changes
|
{datacosmos-0.0.11 → datacosmos-0.0.12}/datacosmos/utils/http_response/models/datacosmos_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|