datacosmos 0.0.2__tar.gz → 0.0.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.
Potentially problematic release.
This version of datacosmos might be problematic. Click here for more details.
- {datacosmos-0.0.2/datacosmos.egg-info → datacosmos-0.0.3}/PKG-INFO +3 -2
- datacosmos-0.0.3/README.md +209 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/config/config.py +56 -28
- datacosmos-0.0.3/datacosmos/stac/enums/__init__.py +1 -0
- datacosmos-0.0.3/datacosmos/stac/enums/level.py +15 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/item/item_client.py +2 -1
- datacosmos-0.0.3/datacosmos/stac/item/models/asset.py +23 -0
- datacosmos-0.0.3/datacosmos/stac/item/models/datacosmos_item.py +55 -0
- datacosmos-0.0.3/datacosmos/stac/item/models/eo_band.py +15 -0
- datacosmos-0.0.3/datacosmos/stac/item/models/raster_band.py +17 -0
- datacosmos-0.0.3/datacosmos/uploader/__init__.py +1 -0
- datacosmos-0.0.3/datacosmos/uploader/dataclasses/__init__.py +1 -0
- datacosmos-0.0.3/datacosmos/uploader/dataclasses/upload_path.py +93 -0
- datacosmos-0.0.3/datacosmos/uploader/datacosmos_uploader.py +106 -0
- datacosmos-0.0.3/datacosmos/utils/constants.py +16 -0
- datacosmos-0.0.3/datacosmos/utils/missions.py +27 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/utils/url.py +23 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3/datacosmos.egg-info}/PKG-INFO +3 -2
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos.egg-info/SOURCES.txt +12 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/pyproject.toml +1 -1
- datacosmos-0.0.2/README.md +0 -257
- {datacosmos-0.0.2 → datacosmos-0.0.3}/LICENSE.md +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/config/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/config/models/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/config/models/m2m_authentication_config.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/config/models/url.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/datacosmos_client.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/exceptions/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/exceptions/datacosmos_exception.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/collection/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/collection/collection_client.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/collection/models/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/collection/models/collection_update.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/item/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/item/models/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/item/models/item_update.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/item/models/search_parameters.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/stac/stac_client.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/utils/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/utils/http_response/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/utils/http_response/check_api_response.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/utils/http_response/models/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/utils/http_response/models/datacosmos_error.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/utils/http_response/models/datacosmos_response.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos.egg-info/dependency_links.txt +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos.egg-info/requires.txt +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos.egg-info/top_level.txt +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/setup.cfg +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.3}/tests/test_pass.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: datacosmos
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
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
|
|
@@ -20,3 +20,4 @@ Requires-Dist: pytest==7.2.0; extra == "dev"
|
|
|
20
20
|
Requires-Dist: bandit[toml]==1.7.4; extra == "dev"
|
|
21
21
|
Requires-Dist: isort==5.11.4; extra == "dev"
|
|
22
22
|
Requires-Dist: pydocstyle==6.1.1; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# DataCosmos SDK
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The **DataCosmos SDK** enables Open Cosmos customers to interact with the **DataCosmos APIs** for efficient data management and retrieval. It provides authentication handling, HTTP request utilities, and a client for interacting with the **STAC API** (SpatioTemporal Asset Catalog).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Install the SDK using **pip**:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pip install datacosmos=={version}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Initializing the Client
|
|
16
|
+
|
|
17
|
+
To start using the SDK, initialize the client. The easiest way to do this is by loading the configuration from a YAML file. Alternatively, you can manually instantiate the Config object or use environment variables.
|
|
18
|
+
|
|
19
|
+
### Default Initialization (Recommended)
|
|
20
|
+
|
|
21
|
+
By default, the client loads configuration from a YAML file (`config/config.yaml`).
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
25
|
+
|
|
26
|
+
client = DatacosmosClient()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Loading from YAML (Recommended)
|
|
30
|
+
|
|
31
|
+
Create a YAML file (`config/config.yaml`) with the following content:
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
authentication:
|
|
35
|
+
client_id: {client_id}
|
|
36
|
+
client_secret: {client_secret}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The client will automatically read this file when initialized.
|
|
40
|
+
|
|
41
|
+
### Loading from Environment Variables
|
|
42
|
+
|
|
43
|
+
Set the following environment variables:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
export OC_AUTH_CLIENT_ID={client_id}
|
|
47
|
+
export OC_AUTH_CLIENT_SECRET={client_secret}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The client will automatically read these values when initialized.
|
|
51
|
+
|
|
52
|
+
### Manual Instantiation
|
|
53
|
+
|
|
54
|
+
If manually instantiating `Config`, default values are now applied where possible.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from config.config import Config
|
|
58
|
+
from config.models.m2m_authentication_config import M2MAuthenticationConfig
|
|
59
|
+
from config.models.url import URL
|
|
60
|
+
|
|
61
|
+
config = Config(
|
|
62
|
+
authentication=M2MAuthenticationConfig(
|
|
63
|
+
client_id="your-client-id",
|
|
64
|
+
client_secret="your-client-secret"
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
client = DatacosmosClient(config=config)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Configuration Options and Defaults
|
|
72
|
+
|
|
73
|
+
| Setting | Default Value | Override Method |
|
|
74
|
+
|------------------------------|-------------------------------------------------|----------------|
|
|
75
|
+
| `authentication.type` | `m2m` | YAML / ENV |
|
|
76
|
+
| `authentication.client_id` | _Required in manual instantiation_ | YAML / ENV |
|
|
77
|
+
| `authentication.client_secret` | _Required in manual instantiation_ | YAML / ENV |
|
|
78
|
+
| `stac.protocol` | `https` | YAML / ENV |
|
|
79
|
+
| `stac.host` | `app.open-cosmos.com` | YAML / ENV |
|
|
80
|
+
| `stac.port` | `443` | YAML / ENV |
|
|
81
|
+
| `stac.path` | `/api/data/v0/stac` | YAML / ENV |
|
|
82
|
+
| `datacosmos_cloud_storage.protocol` | `https` | YAML / ENV |
|
|
83
|
+
| `datacosmos_cloud_storage.host` | `app.open-cosmos.com` | YAML / ENV |
|
|
84
|
+
| `datacosmos_cloud_storage.port` | `443` | YAML / ENV |
|
|
85
|
+
| `datacosmos_cloud_storage.path` | `/api/data/v0/storage` | YAML / ENV |
|
|
86
|
+
| `mission_id` | `0` | YAML / ENV |
|
|
87
|
+
| `environment` | `test` | YAML / ENV |
|
|
88
|
+
|
|
89
|
+
## STAC Client
|
|
90
|
+
|
|
91
|
+
The `STACClient` enables interaction with the STAC API, allowing for searching, retrieving, creating, updating, and deleting STAC items and collections.
|
|
92
|
+
|
|
93
|
+
### Initialize STACClient
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
97
|
+
from datacosmos.stac.stac_client import STACClient
|
|
98
|
+
|
|
99
|
+
client = DatacosmosClient()
|
|
100
|
+
stac_client = STACClient(client)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### STACClient Methods
|
|
104
|
+
|
|
105
|
+
#### 1. Fetch a Collection
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
collection = stac_client.fetch_collection("test-collection")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### 2. Fetch All Collections
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
collections = list(stac_client.fetch_all_collections())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### 3. Create a Collection
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from pystac import Collection
|
|
121
|
+
|
|
122
|
+
new_collection = Collection(
|
|
123
|
+
id="test-collection",
|
|
124
|
+
title="Test Collection",
|
|
125
|
+
description="This is a test collection",
|
|
126
|
+
license="proprietary",
|
|
127
|
+
extent={
|
|
128
|
+
"spatial": {"bbox": [[-180, -90, 180, 90]]},
|
|
129
|
+
"temporal": {"interval": [["2023-01-01T00:00:00Z", None]]},
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
stac_client.create_collection(new_collection)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### 4. Update a Collection
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from datacosmos.stac.collection.models.collection_update import CollectionUpdate
|
|
140
|
+
|
|
141
|
+
update_data = CollectionUpdate(
|
|
142
|
+
title="Updated Collection Title",
|
|
143
|
+
description="Updated description",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
stac_client.update_collection("test-collection", update_data)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### 5. Delete a Collection
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
stac_client.delete_collection("test-collection")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Uploading Files and Registering STAC Items
|
|
156
|
+
|
|
157
|
+
You can use the `DatacosmosUploader` class to upload files to the DataCosmos cloud storage and register a STAC item.
|
|
158
|
+
|
|
159
|
+
#### Upload Files and Register STAC Item
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from datacosmos.uploader.datacosmos_uploader import DatacosmosUploader
|
|
163
|
+
|
|
164
|
+
uploader = DatacosmosUploader(client)
|
|
165
|
+
item_json_file_path = "/path/to/stac_item.json"
|
|
166
|
+
uploader.upload_and_register_item(item_json_file_path)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Error Handling
|
|
170
|
+
|
|
171
|
+
Use `try-except` blocks to handle API errors gracefully:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
try:
|
|
175
|
+
data = client.get_data("dataset_id")
|
|
176
|
+
print(data)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
print(f"An error occurred: {e}")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Contributing
|
|
182
|
+
|
|
183
|
+
To contribute:
|
|
184
|
+
|
|
185
|
+
1. Fork the repository.
|
|
186
|
+
2. Create a feature branch.
|
|
187
|
+
3. Submit a pull request.
|
|
188
|
+
|
|
189
|
+
### Development Setup
|
|
190
|
+
|
|
191
|
+
Use `uv` for dependency management:
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
pip install uv
|
|
195
|
+
uv venv
|
|
196
|
+
uv pip install -r pyproject.toml .[dev]
|
|
197
|
+
source .venv/bin/activate
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Before making changes, run:
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
black .
|
|
204
|
+
isort .
|
|
205
|
+
ruff check .
|
|
206
|
+
pydocstyle .
|
|
207
|
+
bandit -r -c pyproject.toml .
|
|
208
|
+
pytest
|
|
209
|
+
```
|
|
@@ -6,7 +6,7 @@ and supports environment variable-based overrides.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
|
-
from typing import ClassVar, Optional
|
|
9
|
+
from typing import ClassVar, Literal, Optional
|
|
10
10
|
|
|
11
11
|
import yaml
|
|
12
12
|
from pydantic import field_validator
|
|
@@ -27,6 +27,9 @@ class Config(BaseSettings):
|
|
|
27
27
|
|
|
28
28
|
authentication: Optional[M2MAuthenticationConfig] = None
|
|
29
29
|
stac: Optional[URL] = None
|
|
30
|
+
datacosmos_cloud_storage: Optional[URL] = None
|
|
31
|
+
mission_id: int = 0
|
|
32
|
+
environment: Literal["local", "test", "prod"] = "test"
|
|
30
33
|
|
|
31
34
|
DEFAULT_AUTH_TYPE: ClassVar[str] = "m2m"
|
|
32
35
|
DEFAULT_AUTH_TOKEN_URL: ClassVar[str] = "https://login.open-cosmos.com/oauth/token"
|
|
@@ -77,7 +80,20 @@ class Config(BaseSettings):
|
|
|
77
80
|
path=os.getenv("OC_STAC_PATH", "/api/data/v0/stac"),
|
|
78
81
|
)
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
datacosmos_cloud_storage_config = URL(
|
|
84
|
+
protocol=os.getenv("DC_CLOUD_STORAGE_PROTOCOL", "https"),
|
|
85
|
+
host=os.getenv("DC_CLOUD_STORAGE_HOST", "app.open-cosmos.com"),
|
|
86
|
+
port=int(os.getenv("DC_CLOUD_STORAGE_PORT", "443")),
|
|
87
|
+
path=os.getenv("DC_CLOUD_STORAGE_PATH", "/api/data/v0/storage"),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return cls(
|
|
91
|
+
authentication=authentication_config,
|
|
92
|
+
stac=stac_config,
|
|
93
|
+
datacosmos_cloud_storage=datacosmos_cloud_storage_config,
|
|
94
|
+
mission_id=int(os.getenv("MISSION_ID", "0")),
|
|
95
|
+
environment=os.getenv("ENVIRONMENT", "test"),
|
|
96
|
+
)
|
|
81
97
|
|
|
82
98
|
@field_validator("authentication", mode="before")
|
|
83
99
|
@classmethod
|
|
@@ -96,7 +112,7 @@ class Config(BaseSettings):
|
|
|
96
112
|
ValueError: If authentication is missing or required fields are not set.
|
|
97
113
|
"""
|
|
98
114
|
if not auth_data:
|
|
99
|
-
cls.
|
|
115
|
+
return cls.apply_auth_defaults(M2MAuthenticationConfig())
|
|
100
116
|
|
|
101
117
|
auth = cls.parse_auth_config(auth_data)
|
|
102
118
|
auth = cls.apply_auth_defaults(auth)
|
|
@@ -105,34 +121,24 @@ class Config(BaseSettings):
|
|
|
105
121
|
return auth
|
|
106
122
|
|
|
107
123
|
@staticmethod
|
|
108
|
-
def
|
|
109
|
-
"""Raise an error when authentication is missing."""
|
|
110
|
-
raise ValueError(
|
|
111
|
-
"M2M authentication is required. Provide it via:\n"
|
|
112
|
-
"1. Explicit instantiation (Config(authentication=...))\n"
|
|
113
|
-
"2. A YAML config file (config.yaml)\n"
|
|
114
|
-
"3. Environment variables (OC_AUTH_CLIENT_ID, OC_AUTH_CLIENT_SECRET, etc.)"
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
@staticmethod
|
|
118
|
-
def parse_auth_config(auth_data: dict) -> M2MAuthenticationConfig:
|
|
119
|
-
"""Convert dictionary input to M2MAuthenticationConfig object."""
|
|
120
|
-
return (
|
|
121
|
-
M2MAuthenticationConfig(**auth_data)
|
|
122
|
-
if isinstance(auth_data, dict)
|
|
123
|
-
else auth_data
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
@classmethod
|
|
127
|
-
def apply_auth_defaults(
|
|
128
|
-
cls, auth: M2MAuthenticationConfig
|
|
129
|
-
) -> M2MAuthenticationConfig:
|
|
124
|
+
def apply_auth_defaults(auth: M2MAuthenticationConfig) -> M2MAuthenticationConfig:
|
|
130
125
|
"""Apply default authentication values if they are missing."""
|
|
131
|
-
auth.type = auth.type or
|
|
132
|
-
auth.token_url = auth.token_url or
|
|
133
|
-
auth.audience = auth.audience or
|
|
126
|
+
auth.type = auth.type or Config.DEFAULT_AUTH_TYPE
|
|
127
|
+
auth.token_url = auth.token_url or Config.DEFAULT_AUTH_TOKEN_URL
|
|
128
|
+
auth.audience = auth.audience or Config.DEFAULT_AUTH_AUDIENCE
|
|
134
129
|
return auth
|
|
135
130
|
|
|
131
|
+
@classmethod
|
|
132
|
+
def parse_auth_config(cls, auth_data: dict) -> M2MAuthenticationConfig:
|
|
133
|
+
"""Parse authentication config from a dictionary."""
|
|
134
|
+
return M2MAuthenticationConfig(
|
|
135
|
+
type=auth_data.get("type", cls.DEFAULT_AUTH_TYPE),
|
|
136
|
+
token_url=auth_data.get("token_url", cls.DEFAULT_AUTH_TOKEN_URL),
|
|
137
|
+
audience=auth_data.get("audience", cls.DEFAULT_AUTH_AUDIENCE),
|
|
138
|
+
client_id=auth_data.get("client_id"),
|
|
139
|
+
client_secret=auth_data.get("client_secret"),
|
|
140
|
+
)
|
|
141
|
+
|
|
136
142
|
@staticmethod
|
|
137
143
|
def check_required_auth_fields(auth: M2MAuthenticationConfig):
|
|
138
144
|
"""Ensure required fields (client_id, client_secret) are provided."""
|
|
@@ -165,3 +171,25 @@ class Config(BaseSettings):
|
|
|
165
171
|
path="/api/data/v0/stac",
|
|
166
172
|
)
|
|
167
173
|
return stac_config
|
|
174
|
+
|
|
175
|
+
@field_validator("datacosmos_cloud_storage", mode="before")
|
|
176
|
+
@classmethod
|
|
177
|
+
def validate_datacosmos_cloud_storage(
|
|
178
|
+
cls, datacosmos_cloud_storage_config: Optional[URL]
|
|
179
|
+
) -> URL:
|
|
180
|
+
"""Ensure datacosmos cloud storage configuration has a default if not explicitly set.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
datacosmos_cloud_storage_config (Optional[URL]): The datacosmos cloud storage config to validate.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
URL: The validated datacosmos cloud storage configuration.
|
|
187
|
+
"""
|
|
188
|
+
if datacosmos_cloud_storage_config is None:
|
|
189
|
+
return URL(
|
|
190
|
+
protocol="https",
|
|
191
|
+
host="app.open-cosmos.com",
|
|
192
|
+
port=443,
|
|
193
|
+
path="/api/data/v0/storage",
|
|
194
|
+
)
|
|
195
|
+
return datacosmos_cloud_storage_config
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Enums for STAC."""
|
|
@@ -9,6 +9,7 @@ from pystac import Item
|
|
|
9
9
|
|
|
10
10
|
from datacosmos.datacosmos_client import DatacosmosClient
|
|
11
11
|
from datacosmos.exceptions.datacosmos_exception import DatacosmosException
|
|
12
|
+
from datacosmos.stac.item.models.datacosmos_item import DatacosmosItem
|
|
12
13
|
from datacosmos.stac.item.models.item_update import ItemUpdate
|
|
13
14
|
from datacosmos.stac.item.models.search_parameters import SearchParameters
|
|
14
15
|
from datacosmos.utils.http_response.check_api_response import check_api_response
|
|
@@ -71,7 +72,7 @@ class ItemClient:
|
|
|
71
72
|
body = parameters.model_dump(by_alias=True, exclude_none=True)
|
|
72
73
|
return self._paginate_items(url, body)
|
|
73
74
|
|
|
74
|
-
def create_item(self, collection_id: str, item: Item) -> None:
|
|
75
|
+
def create_item(self, collection_id: str, item: Item | DatacosmosItem) -> None:
|
|
75
76
|
"""Create a new STAC item in a specified collection.
|
|
76
77
|
|
|
77
78
|
Args:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Model representing a datacosmos item asset."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from datacosmos.stac.item.models.eo_band import EoBand
|
|
6
|
+
from datacosmos.stac.item.models.raster_band import RasterBand
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Asset(BaseModel):
|
|
10
|
+
"""Model representing a datacosmos item asset."""
|
|
11
|
+
|
|
12
|
+
href: str
|
|
13
|
+
title: str
|
|
14
|
+
description: str
|
|
15
|
+
type: str
|
|
16
|
+
roles: list[str] | None
|
|
17
|
+
eo_bands: list[EoBand] | None = Field(default=None, alias="eo:bands")
|
|
18
|
+
raster_bands: list[RasterBand] | None = Field(default=None, alias="raster:bands")
|
|
19
|
+
|
|
20
|
+
class Config:
|
|
21
|
+
"""Pydantic configuration."""
|
|
22
|
+
|
|
23
|
+
populate_by_name = True
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Model representing a datacosmos item."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from datacosmos.stac.enums.level import Level
|
|
8
|
+
from datacosmos.stac.item.models.asset import Asset
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DatacosmosItem(BaseModel):
|
|
12
|
+
"""Model representing a datacosmos item."""
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
type: str
|
|
16
|
+
stac_version: str
|
|
17
|
+
stac_extensions: list | None
|
|
18
|
+
geometry: dict
|
|
19
|
+
properties: dict
|
|
20
|
+
links: list
|
|
21
|
+
assets: dict[str, Asset]
|
|
22
|
+
collection: str
|
|
23
|
+
bbox: tuple[float, float, float, float]
|
|
24
|
+
|
|
25
|
+
def get_property(self, key: str) -> str | None:
|
|
26
|
+
"""Get a property value from the Datacosmos item."""
|
|
27
|
+
return self.properties.get(key)
|
|
28
|
+
|
|
29
|
+
def get_asset(self, key: str) -> Asset | None:
|
|
30
|
+
"""Get an asset from the Datacosmos item."""
|
|
31
|
+
return self.assets.get(key)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def datetime(self) -> datetime:
|
|
35
|
+
"""Get the datetime of the Datacosmos item."""
|
|
36
|
+
return datetime.strptime(self.properties["datetime"], "%Y-%m-%dT%H:%M:%SZ")
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def level(self) -> Level:
|
|
40
|
+
"""Get the processing level of the Datacosmos item."""
|
|
41
|
+
return Level(self.properties["processing:level"].lower())
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def sat_int_designator(self) -> str:
|
|
45
|
+
"""Get the satellite international designator of the Datacosmos item."""
|
|
46
|
+
property = self.get_property("sat:platform_international_designator")
|
|
47
|
+
if property is None:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"sat:platform_international_designator is missing in STAC item"
|
|
50
|
+
)
|
|
51
|
+
return property
|
|
52
|
+
|
|
53
|
+
def to_dict(self) -> dict:
|
|
54
|
+
"""Converts the DatacosmosItem instance to a dictionary."""
|
|
55
|
+
return self.model_dump()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Model representing an EO band."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EoBand(BaseModel):
|
|
9
|
+
"""Model representing an EO band."""
|
|
10
|
+
|
|
11
|
+
name: str
|
|
12
|
+
common_name: str
|
|
13
|
+
center_wavelength: float
|
|
14
|
+
full_width_half_max: float
|
|
15
|
+
solar_illumination: Optional[float] = None
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Model representing a raster band."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RasterBand(BaseModel):
|
|
7
|
+
"""Model representing a raster band."""
|
|
8
|
+
|
|
9
|
+
gain: float = Field(alias="scale")
|
|
10
|
+
bias: float = Field(alias="offset")
|
|
11
|
+
nodata: int
|
|
12
|
+
unit: str
|
|
13
|
+
|
|
14
|
+
class Config:
|
|
15
|
+
"""Pydantic configuration."""
|
|
16
|
+
|
|
17
|
+
populate_by_name = True
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Uploader package for interacting with the Uploader API, providing upload functionalities to the datacosmos cloud storage."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Dataclasses for the uploader module."""
|
|
@@ -0,0 +1,93 @@
|
|
|
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.level import Level
|
|
10
|
+
from datacosmos.stac.item.models.datacosmos_item import DatacosmosItem
|
|
11
|
+
from datacosmos.utils.missions import get_mission_id
|
|
12
|
+
|
|
13
|
+
logger = structlog.get_logger()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class UploadPath:
|
|
18
|
+
"""Dataclass for retrieving the upload path of a file."""
|
|
19
|
+
|
|
20
|
+
mission: str
|
|
21
|
+
level: Level
|
|
22
|
+
day: int
|
|
23
|
+
month: int
|
|
24
|
+
year: int
|
|
25
|
+
id: str
|
|
26
|
+
path: str
|
|
27
|
+
|
|
28
|
+
def __str__(self):
|
|
29
|
+
"""Return a human-readable string representation of the Path."""
|
|
30
|
+
path = f"full/{self.mission.lower()}/{self.level.value.lower()}/{self.year:02}/{self.month:02}/{self.day:02}/{self.id}/{self.path}"
|
|
31
|
+
return path.removesuffix("/")
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_item_path(
|
|
35
|
+
cls, item: DatacosmosItem, mission: str, item_path: str
|
|
36
|
+
) -> "Path":
|
|
37
|
+
"""Create a Path instance from a DatacosmosItem and a path."""
|
|
38
|
+
for asset in item.assets.values():
|
|
39
|
+
if mission == "":
|
|
40
|
+
mission = cls._get_mission_name(asset.href)
|
|
41
|
+
else:
|
|
42
|
+
break
|
|
43
|
+
dt = datetime.strptime(item.properties["datetime"], "%Y-%m-%dT%H:%M:%SZ")
|
|
44
|
+
path = UploadPath(
|
|
45
|
+
mission=mission,
|
|
46
|
+
level=Level(item.properties["processing:level"].lower()),
|
|
47
|
+
day=dt.day,
|
|
48
|
+
month=dt.month,
|
|
49
|
+
year=dt.year,
|
|
50
|
+
id=item.id,
|
|
51
|
+
path=item_path,
|
|
52
|
+
)
|
|
53
|
+
return cls(**path.__dict__)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_path(cls, path: str) -> "Path":
|
|
57
|
+
"""Create a Path instance from a string path."""
|
|
58
|
+
parts = path.split("/")
|
|
59
|
+
if len(parts) < 7:
|
|
60
|
+
raise ValueError(f"Invalid path {path}")
|
|
61
|
+
return cls(
|
|
62
|
+
mission=parts[0],
|
|
63
|
+
level=Level(parts[1]),
|
|
64
|
+
day=int(parts[4]),
|
|
65
|
+
month=int(parts[3]),
|
|
66
|
+
year=int(parts[2]),
|
|
67
|
+
id=parts[5],
|
|
68
|
+
path="/".join(parts[6:]),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def _get_mission_name(cls, href: str) -> str:
|
|
73
|
+
mission = ""
|
|
74
|
+
# bruteforce mission name from asset path
|
|
75
|
+
# traverse the path and check if any part is a mission name (generates a mission id)
|
|
76
|
+
href_parts = href.split("/")
|
|
77
|
+
for idx, part in enumerate(href_parts):
|
|
78
|
+
try:
|
|
79
|
+
# when an id is found, then the mission name is valid
|
|
80
|
+
get_mission_id(
|
|
81
|
+
part, "test"
|
|
82
|
+
) # using test as it is more wide and anything on prod should exists on test
|
|
83
|
+
except KeyError:
|
|
84
|
+
continue
|
|
85
|
+
# validate the mission name by checking if the path is correct
|
|
86
|
+
# using the same logic as the __str__ method
|
|
87
|
+
mission = part.lower()
|
|
88
|
+
h = "/".join(["full", *href_parts[idx:]])
|
|
89
|
+
p = UploadPath.from_path("/".join([mission, *href_parts[idx + 1 :]]))
|
|
90
|
+
if str(p) != h:
|
|
91
|
+
raise ValueError(f"Could not find mission name in asset path {href}")
|
|
92
|
+
break
|
|
93
|
+
return mission
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Module for uploading files to Datacosmos cloud storage and registering STAC items."""
|
|
2
|
+
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from pydantic import TypeAdapter
|
|
7
|
+
|
|
8
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
9
|
+
from datacosmos.stac.item.item_client import ItemClient
|
|
10
|
+
from datacosmos.stac.item.models.datacosmos_item import DatacosmosItem
|
|
11
|
+
from datacosmos.uploader.dataclasses.upload_path import UploadPath
|
|
12
|
+
from datacosmos.utils.missions import get_mission_name
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DatacosmosUploader:
|
|
16
|
+
"""Handles uploading files to Datacosmos storage and registering STAC items."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, client: DatacosmosClient):
|
|
19
|
+
"""Initialize the uploader with DatacosmosClient."""
|
|
20
|
+
mission_id = client.config.mission_id
|
|
21
|
+
environment = client.config.environment
|
|
22
|
+
|
|
23
|
+
self.datacosmos_client = client
|
|
24
|
+
self.item_client = ItemClient(client)
|
|
25
|
+
self.mission_name = (
|
|
26
|
+
get_mission_name(mission_id, environment) if mission_id != 0 else ""
|
|
27
|
+
)
|
|
28
|
+
self.base_url = client.config.datacosmos_cloud_storage.as_domain_url()
|
|
29
|
+
|
|
30
|
+
def upload_and_register_item(self, item_json_file_path: str) -> None:
|
|
31
|
+
"""Uploads files to Datacosmos storage and registers a STAC item.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
item_json_file_path (str): Path to the STAC item JSON file.
|
|
35
|
+
"""
|
|
36
|
+
item = self._load_item(item_json_file_path)
|
|
37
|
+
collection_id, item_id = item.collection, item.id
|
|
38
|
+
dirname = str(Path(item_json_file_path).parent / Path(item_json_file_path).stem)
|
|
39
|
+
|
|
40
|
+
self._delete_existing_item(collection_id, item_id)
|
|
41
|
+
upload_path = self._get_upload_path(item)
|
|
42
|
+
self.upload_from_folder(dirname, upload_path)
|
|
43
|
+
|
|
44
|
+
self._update_item_assets(item)
|
|
45
|
+
|
|
46
|
+
self.item_client.create_item(collection_id, item)
|
|
47
|
+
|
|
48
|
+
def upload_file(self, src: str, dst: str) -> None:
|
|
49
|
+
"""Uploads a single file to the specified destination path."""
|
|
50
|
+
url = self.base_url.with_suffix(str(dst))
|
|
51
|
+
|
|
52
|
+
with open(src, "rb") as f:
|
|
53
|
+
response = self.datacosmos_client.put(url, data=f)
|
|
54
|
+
response.raise_for_status()
|
|
55
|
+
|
|
56
|
+
def upload_from_folder(self, src: str, dst: UploadPath, workers: int = 4) -> None:
|
|
57
|
+
"""Uploads all files from a folder to the destination path in parallel."""
|
|
58
|
+
if Path(dst.path).is_file():
|
|
59
|
+
raise ValueError(f"Destination path should not be a file path {dst}")
|
|
60
|
+
|
|
61
|
+
if Path(src).is_file():
|
|
62
|
+
raise ValueError(f"Source path should not be a file path {src}")
|
|
63
|
+
|
|
64
|
+
with ThreadPoolExecutor(max_workers=workers) as executor:
|
|
65
|
+
futures = []
|
|
66
|
+
for file in Path(src).rglob("*"):
|
|
67
|
+
if file.is_file():
|
|
68
|
+
dst = UploadPath(
|
|
69
|
+
mission=dst.mission,
|
|
70
|
+
level=dst.level,
|
|
71
|
+
day=dst.day,
|
|
72
|
+
month=dst.month,
|
|
73
|
+
year=dst.year,
|
|
74
|
+
id=dst.id,
|
|
75
|
+
path=str(file.relative_to(src)),
|
|
76
|
+
)
|
|
77
|
+
futures.append(executor.submit(self.upload_file, str(file), dst))
|
|
78
|
+
for future in futures:
|
|
79
|
+
future.result()
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _load_item(item_json_file_path: str) -> DatacosmosItem:
|
|
83
|
+
"""Loads and validates the STAC item from a JSON file."""
|
|
84
|
+
with open(item_json_file_path, "rb") as file:
|
|
85
|
+
data = file.read().decode("utf-8")
|
|
86
|
+
return TypeAdapter(DatacosmosItem).validate_json(data)
|
|
87
|
+
|
|
88
|
+
def _delete_existing_item(self, collection_id: str, item_id: str) -> None:
|
|
89
|
+
"""Deletes an existing item if it already exists."""
|
|
90
|
+
try:
|
|
91
|
+
self.item_client.delete_item(item_id, collection_id)
|
|
92
|
+
except Exception: # nosec
|
|
93
|
+
pass # Ignore if item doesn't exist
|
|
94
|
+
|
|
95
|
+
def _get_upload_path(self, item: DatacosmosItem) -> str:
|
|
96
|
+
"""Constructs the storage upload path based on the item and mission name."""
|
|
97
|
+
return UploadPath.from_item_path(item, self.mission_name, "")
|
|
98
|
+
|
|
99
|
+
def _update_item_assets(self, item: DatacosmosItem) -> None:
|
|
100
|
+
"""Updates the item's assets with uploaded file URLs."""
|
|
101
|
+
for asset in item.assets.values():
|
|
102
|
+
try:
|
|
103
|
+
url = self.base_url
|
|
104
|
+
asset.href = url.with_base(asset.href) # type: ignore
|
|
105
|
+
except ValueError:
|
|
106
|
+
pass
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Package for storing constants."""
|
|
2
|
+
|
|
3
|
+
TEST_MISSION_NAMES = {
|
|
4
|
+
55: "MENUT",
|
|
5
|
+
56: "PHISAT-2",
|
|
6
|
+
57: "HAMMER",
|
|
7
|
+
63: "MANTIS",
|
|
8
|
+
64: "PLATERO",
|
|
9
|
+
}
|
|
10
|
+
PROD_MISSION_NAMES = {
|
|
11
|
+
23: "MENUT",
|
|
12
|
+
29: "MANTIS",
|
|
13
|
+
35: "PHISAT-2",
|
|
14
|
+
37: "PLATERO",
|
|
15
|
+
48: "HAMMER",
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Package for storing mission specific information."""
|
|
2
|
+
|
|
3
|
+
from datacosmos.utils.constants import PROD_MISSION_NAMES, TEST_MISSION_NAMES
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_mission_name(mission: int, env: str) -> str:
|
|
7
|
+
"""Get the mission name from the mission number."""
|
|
8
|
+
if env == "test" or env == "local":
|
|
9
|
+
return TEST_MISSION_NAMES[mission]
|
|
10
|
+
elif env == "prod":
|
|
11
|
+
return PROD_MISSION_NAMES[mission]
|
|
12
|
+
else:
|
|
13
|
+
raise ValueError(f"Unsupported environment: {env}")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_mission_id(mission_name: str, env: str) -> int:
|
|
17
|
+
"""Get the mission number from the mission name."""
|
|
18
|
+
if env == "test" or env == "local":
|
|
19
|
+
return {v.upper(): k for k, v in TEST_MISSION_NAMES.items()}[
|
|
20
|
+
mission_name.upper()
|
|
21
|
+
]
|
|
22
|
+
elif env == "prod":
|
|
23
|
+
return {v.upper(): k for k, v in PROD_MISSION_NAMES.items()}[
|
|
24
|
+
mission_name.upper()
|
|
25
|
+
]
|
|
26
|
+
else:
|
|
27
|
+
raise ValueError(f"Unsupported environment: {env}")
|
|
@@ -35,3 +35,26 @@ class URL:
|
|
|
35
35
|
"""
|
|
36
36
|
base = self.string()
|
|
37
37
|
return f"{base.rstrip('/')}/{suffix.lstrip('/')}"
|
|
38
|
+
|
|
39
|
+
def with_base(self, url: str) -> str:
|
|
40
|
+
"""Replaces the base of the url with the base stored in the URL object. (migrates url from one base to another).
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
url (str): url to migrate to the base of the URL object
|
|
44
|
+
|
|
45
|
+
Returns (str):
|
|
46
|
+
url with the base of the URL object
|
|
47
|
+
"""
|
|
48
|
+
split_url = url.split("/")
|
|
49
|
+
if len(split_url) < 3 or url.find("://") == -1:
|
|
50
|
+
raise ValueError(f"URL '{url}' does not meet the minimum requirements")
|
|
51
|
+
# get the whole path
|
|
52
|
+
url_path = "/".join(split_url[3:])
|
|
53
|
+
# simple case, matching self.base at url
|
|
54
|
+
b = self.base.lstrip("/")
|
|
55
|
+
if (base_pos := url_path.find(b)) != -1:
|
|
56
|
+
# remove the base from the url
|
|
57
|
+
url_suffix = url_path[len(b) + base_pos :]
|
|
58
|
+
else:
|
|
59
|
+
url_suffix = url_path
|
|
60
|
+
return self.with_suffix(url_suffix)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: datacosmos
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
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
|
|
@@ -20,3 +20,4 @@ Requires-Dist: pytest==7.2.0; extra == "dev"
|
|
|
20
20
|
Requires-Dist: bandit[toml]==1.7.4; extra == "dev"
|
|
21
21
|
Requires-Dist: isort==5.11.4; extra == "dev"
|
|
22
22
|
Requires-Dist: pydocstyle==6.1.1; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
@@ -21,12 +21,24 @@ datacosmos/stac/collection/__init__.py
|
|
|
21
21
|
datacosmos/stac/collection/collection_client.py
|
|
22
22
|
datacosmos/stac/collection/models/__init__.py
|
|
23
23
|
datacosmos/stac/collection/models/collection_update.py
|
|
24
|
+
datacosmos/stac/enums/__init__.py
|
|
25
|
+
datacosmos/stac/enums/level.py
|
|
24
26
|
datacosmos/stac/item/__init__.py
|
|
25
27
|
datacosmos/stac/item/item_client.py
|
|
26
28
|
datacosmos/stac/item/models/__init__.py
|
|
29
|
+
datacosmos/stac/item/models/asset.py
|
|
30
|
+
datacosmos/stac/item/models/datacosmos_item.py
|
|
31
|
+
datacosmos/stac/item/models/eo_band.py
|
|
27
32
|
datacosmos/stac/item/models/item_update.py
|
|
33
|
+
datacosmos/stac/item/models/raster_band.py
|
|
28
34
|
datacosmos/stac/item/models/search_parameters.py
|
|
35
|
+
datacosmos/uploader/__init__.py
|
|
36
|
+
datacosmos/uploader/datacosmos_uploader.py
|
|
37
|
+
datacosmos/uploader/dataclasses/__init__.py
|
|
38
|
+
datacosmos/uploader/dataclasses/upload_path.py
|
|
29
39
|
datacosmos/utils/__init__.py
|
|
40
|
+
datacosmos/utils/constants.py
|
|
41
|
+
datacosmos/utils/missions.py
|
|
30
42
|
datacosmos/utils/url.py
|
|
31
43
|
datacosmos/utils/http_response/__init__.py
|
|
32
44
|
datacosmos/utils/http_response/check_api_response.py
|
datacosmos-0.0.2/README.md
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
# DataCosmos SDK
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
The **DataCosmos SDK** allows Open Cosmos' customers to interact with the **DataCosmos APIs** for seamless data management and retrieval. It provides authentication handling, HTTP request utilities, and a client for interacting with the **STAC API** (SpatioTemporal Asset Catalog).
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
### Install via PyPI
|
|
10
|
-
|
|
11
|
-
The easiest way to install the SDK is via **pip**:
|
|
12
|
-
|
|
13
|
-
```sh
|
|
14
|
-
pip install datacosmos=={version}
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Getting Started
|
|
18
|
-
|
|
19
|
-
### Initializing the Client
|
|
20
|
-
|
|
21
|
-
The recommended way to initialize the SDK is by passing a `Config` object with authentication credentials:
|
|
22
|
-
|
|
23
|
-
```python
|
|
24
|
-
from datacosmos.datacosmos_client import DatacosmosClient
|
|
25
|
-
from config.config import Config
|
|
26
|
-
|
|
27
|
-
config = Config(
|
|
28
|
-
authentication={
|
|
29
|
-
"client_id": "your_client_id",
|
|
30
|
-
"client_secret": "your_client_secret",
|
|
31
|
-
"token_url": "https://login.open-cosmos.com/oauth/token",
|
|
32
|
-
"audience": "https://beeapp.open-cosmos.com"
|
|
33
|
-
}
|
|
34
|
-
)
|
|
35
|
-
client = DatacosmosClient(config=config)
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Alternatively, the SDK can load configuration automatically from:
|
|
39
|
-
|
|
40
|
-
- A YAML file (`config/config.yaml`)
|
|
41
|
-
- Environment variables
|
|
42
|
-
|
|
43
|
-
### STAC Client
|
|
44
|
-
|
|
45
|
-
The STACClient enables interaction with the STAC API, allowing for searching, retrieving, creating, updating, and deleting STAC items and collections.
|
|
46
|
-
|
|
47
|
-
#### Initialize STACClient
|
|
48
|
-
|
|
49
|
-
```python
|
|
50
|
-
from datacosmos.stac.stac_client import STACClient
|
|
51
|
-
|
|
52
|
-
stac_client = STACClient(client)
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### STACClient Methods
|
|
56
|
-
|
|
57
|
-
#### 1. **Fetch a Collection**
|
|
58
|
-
|
|
59
|
-
```python
|
|
60
|
-
from datacosmos.stac.stac_client import STACClient
|
|
61
|
-
from datacosmos.datacosmos_client import DatacosmosClient
|
|
62
|
-
|
|
63
|
-
datacosmos_client = DatacosmosClient()
|
|
64
|
-
stac_client = STACClient(datacosmos_client)
|
|
65
|
-
|
|
66
|
-
collection = stac_client.fetch_collection("test-collection")
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
#### 2. **Fetch All Collections**
|
|
70
|
-
|
|
71
|
-
```python
|
|
72
|
-
collections = list(stac_client.fetch_all_collections())
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
#### 3. **Create a Collection**
|
|
76
|
-
|
|
77
|
-
```python
|
|
78
|
-
from pystac import Collection
|
|
79
|
-
|
|
80
|
-
new_collection = Collection(
|
|
81
|
-
id="test-collection",
|
|
82
|
-
title="Test Collection",
|
|
83
|
-
description="This is a test collection",
|
|
84
|
-
license="proprietary",
|
|
85
|
-
extent={
|
|
86
|
-
"spatial": {"bbox": [[-180, -90, 180, 90]]},
|
|
87
|
-
"temporal": {"interval": [["2023-01-01T00:00:00Z", None]]},
|
|
88
|
-
},
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
stac_client.create_collection(new_collection)
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
#### 4. **Update a Collection**
|
|
95
|
-
|
|
96
|
-
```python
|
|
97
|
-
from datacosmos.stac.collection.models.collection_update import CollectionUpdate
|
|
98
|
-
|
|
99
|
-
update_data = CollectionUpdate(
|
|
100
|
-
title="Updated Collection Title version 2",
|
|
101
|
-
description="Updated description version 2",
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
stac_client.update_collection("test-collection", update_data)
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
#### 5. **Delete a Collection**
|
|
108
|
-
|
|
109
|
-
```python
|
|
110
|
-
collection_id = "test-collection"
|
|
111
|
-
stac_client.delete_collection(collection_id)
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
#### 1. **Search Items**
|
|
115
|
-
|
|
116
|
-
```python
|
|
117
|
-
from datacosmos.stac.item.models.search_parameters import SearchParameters
|
|
118
|
-
|
|
119
|
-
parameters = SearchParameters(collections=["example-collection"], limit=1)
|
|
120
|
-
items = list(stac_client.search_items(parameters=parameters))
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
#### 2. **Fetch a Single Item**
|
|
124
|
-
|
|
125
|
-
```python
|
|
126
|
-
item = stac_client.fetch_item(item_id="example-item", collection_id="example-collection")
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
#### 3. **Fetch All Items in a Collection**
|
|
130
|
-
|
|
131
|
-
```python
|
|
132
|
-
items = stac_client.fetch_collection_items(collection_id="example-collection")
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
#### 4. **Create a New STAC Item**
|
|
136
|
-
|
|
137
|
-
```python
|
|
138
|
-
from datetime import datetime
|
|
139
|
-
from pystac import Item, Asset
|
|
140
|
-
from pystac.utils import str_to_datetime
|
|
141
|
-
|
|
142
|
-
stac_item = Item(
|
|
143
|
-
id="MENUT_000001418_20240211120920_20240211120932_new_release.tiff",
|
|
144
|
-
geometry={
|
|
145
|
-
"type": "Polygon",
|
|
146
|
-
"coordinates": [
|
|
147
|
-
[
|
|
148
|
-
[-24.937406454761664, 64.5931773445667],
|
|
149
|
-
[-19.6596824245997, 64.5931773445667],
|
|
150
|
-
[-19.6596824245997, 63.117895100111724],
|
|
151
|
-
[-24.937406454761664, 63.117895100111724],
|
|
152
|
-
[-24.937406454761664, 64.5931773445667]
|
|
153
|
-
]
|
|
154
|
-
]
|
|
155
|
-
},
|
|
156
|
-
bbox=[
|
|
157
|
-
-24.937406454761664,
|
|
158
|
-
63.117895100111724,
|
|
159
|
-
-19.6596824245997,
|
|
160
|
-
64.5931773445667
|
|
161
|
-
],
|
|
162
|
-
datetime=str_to_datetime("2024-02-11T12:09:32Z"),
|
|
163
|
-
properties={"processing:level": "L0"},
|
|
164
|
-
collection="menut-l0",
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
stac_item.add_asset(
|
|
168
|
-
"thumbnail",
|
|
169
|
-
Asset(
|
|
170
|
-
href="https://test.app.open-cosmos.com/api/data/v0/storage/full/menut/l0/2024/02/11/MENUT_000001418_20240211120920_20240211120932.tiff/thumbnail.webp",
|
|
171
|
-
media_type="image/webp",
|
|
172
|
-
roles=["thumbnail"],
|
|
173
|
-
title="Thumbnail",
|
|
174
|
-
description="Thumbnail of the image"
|
|
175
|
-
)
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
stac_client.create_item(collection_id="menutl-l0", item=stac_item)
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
#### 5. **Update an Existing STAC Item**
|
|
182
|
-
|
|
183
|
-
```python
|
|
184
|
-
from datacosmos.stac.item.models.item_update import ItemUpdate
|
|
185
|
-
from pystac import Asset, Link
|
|
186
|
-
|
|
187
|
-
update_payload = ItemUpdate(
|
|
188
|
-
properties={
|
|
189
|
-
"new_property": "updated_value",
|
|
190
|
-
"datetime": "2024-11-10T14:58:00Z"
|
|
191
|
-
},
|
|
192
|
-
assets={
|
|
193
|
-
"image": Asset(
|
|
194
|
-
href="https://example.com/updated-image.tiff",
|
|
195
|
-
media_type="image/tiff"
|
|
196
|
-
)
|
|
197
|
-
},
|
|
198
|
-
links=[
|
|
199
|
-
Link(rel="self", target="https://example.com/updated-image.tiff")
|
|
200
|
-
],
|
|
201
|
-
geometry={
|
|
202
|
-
"type": "Point",
|
|
203
|
-
"coordinates": [10, 20]
|
|
204
|
-
},
|
|
205
|
-
bbox=[10.0, 20.0, 30.0, 40.0]
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
stac_client.update_item(item_id="new-item", collection_id="example-collection", update_data=update_payload)
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
#### 6. **Delete an Item**
|
|
212
|
-
|
|
213
|
-
```python
|
|
214
|
-
stac_client.delete_item(item_id="new-item", collection_id="example-collection")
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## Configuration Options
|
|
218
|
-
|
|
219
|
-
- **Recommended:** Instantiate `DatacosmosClient` with a `Config` object.
|
|
220
|
-
- Alternatively, use **YAML files** (`config/config.yaml`).
|
|
221
|
-
- Or, use **environment variables**.
|
|
222
|
-
|
|
223
|
-
## Contributing
|
|
224
|
-
|
|
225
|
-
If you would like to contribute:
|
|
226
|
-
|
|
227
|
-
1. Fork the repository.
|
|
228
|
-
2. Create a feature branch.
|
|
229
|
-
3. Submit a pull request.
|
|
230
|
-
|
|
231
|
-
### Development Setup
|
|
232
|
-
|
|
233
|
-
If you are developing the SDK, you can use `uv` for dependency management:
|
|
234
|
-
|
|
235
|
-
```sh
|
|
236
|
-
pip install uv
|
|
237
|
-
uv venv
|
|
238
|
-
uv pip install -r pyproject.toml
|
|
239
|
-
uv pip install -r pyproject.toml .[dev]
|
|
240
|
-
source .venv/bin/activate
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
Before making changes, ensure that:
|
|
244
|
-
|
|
245
|
-
- The code is formatted using **Black** and **isort**.
|
|
246
|
-
- Static analysis and linting are performed using **ruff** and **pydocstyle**.
|
|
247
|
-
- Security checks are performed using **bandit**.
|
|
248
|
-
- Tests are executed with **pytest**.
|
|
249
|
-
|
|
250
|
-
```sh
|
|
251
|
-
black .
|
|
252
|
-
isort .
|
|
253
|
-
ruff check . --select C901
|
|
254
|
-
pydocstyle .
|
|
255
|
-
bandit -r -c pyproject.toml . --skip B105,B106,B101
|
|
256
|
-
pytest
|
|
257
|
-
```
|
|
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
|
|
File without changes
|
|
File without changes
|
{datacosmos-0.0.2 → datacosmos-0.0.3}/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
|
{datacosmos-0.0.2 → datacosmos-0.0.3}/datacosmos/utils/http_response/models/datacosmos_error.py
RENAMED
|
File without changes
|
{datacosmos-0.0.2 → datacosmos-0.0.3}/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
|