datacosmos 0.0.2__tar.gz → 0.0.4__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.4}/PKG-INFO +4 -2
- datacosmos-0.0.4/README.md +368 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/config/config.py +56 -28
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/collection/collection_client.py +6 -2
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/collection/models/collection_update.py +1 -0
- datacosmos-0.0.4/datacosmos/stac/constants/__init__.py +1 -0
- datacosmos-0.0.4/datacosmos/stac/constants/satellite_name_mapping.py +20 -0
- datacosmos-0.0.4/datacosmos/stac/enums/__init__.py +1 -0
- datacosmos-0.0.4/datacosmos/stac/enums/processing_level.py +15 -0
- datacosmos-0.0.4/datacosmos/stac/enums/product_type.py +11 -0
- datacosmos-0.0.4/datacosmos/stac/enums/season.py +14 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/item/item_client.py +11 -4
- datacosmos-0.0.4/datacosmos/stac/item/models/asset.py +23 -0
- datacosmos-0.0.4/datacosmos/stac/item/models/catalog_search_parameters.py +132 -0
- datacosmos-0.0.4/datacosmos/stac/item/models/datacosmos_item.py +55 -0
- datacosmos-0.0.4/datacosmos/stac/item/models/eo_band.py +15 -0
- datacosmos-0.0.4/datacosmos/stac/item/models/raster_band.py +17 -0
- datacosmos-0.0.4/datacosmos/uploader/__init__.py +1 -0
- datacosmos-0.0.4/datacosmos/uploader/dataclasses/__init__.py +1 -0
- datacosmos-0.0.4/datacosmos/uploader/dataclasses/upload_path.py +93 -0
- datacosmos-0.0.4/datacosmos/uploader/datacosmos_uploader.py +106 -0
- datacosmos-0.0.4/datacosmos/utils/constants.py +16 -0
- datacosmos-0.0.4/datacosmos/utils/missions.py +27 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/utils/url.py +23 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4/datacosmos.egg-info}/PKG-INFO +4 -2
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos.egg-info/SOURCES.txt +17 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos.egg-info/requires.txt +1 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/pyproject.toml +3 -2
- datacosmos-0.0.2/README.md +0 -257
- {datacosmos-0.0.2 → datacosmos-0.0.4}/LICENSE.md +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/config/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/config/models/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/config/models/m2m_authentication_config.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/config/models/url.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/datacosmos_client.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/exceptions/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/exceptions/datacosmos_exception.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/collection/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/collection/models/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/item/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/item/models/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/item/models/item_update.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/item/models/search_parameters.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/stac/stac_client.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/utils/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/utils/http_response/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/utils/http_response/check_api_response.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/utils/http_response/models/__init__.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/utils/http_response/models/datacosmos_error.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos/utils/http_response/models/datacosmos_response.py +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos.egg-info/dependency_links.txt +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/datacosmos.egg-info/top_level.txt +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/setup.cfg +0 -0
- {datacosmos-0.0.2 → datacosmos-0.0.4}/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.4
|
|
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
|
|
@@ -13,6 +13,7 @@ Requires-Dist: oauthlib==3.2.0
|
|
|
13
13
|
Requires-Dist: requests-oauthlib==1.3.1
|
|
14
14
|
Requires-Dist: pydantic==2.10.6
|
|
15
15
|
Requires-Dist: pystac==1.12.1
|
|
16
|
+
Requires-Dist: pyyaml==6.0.2
|
|
16
17
|
Provides-Extra: dev
|
|
17
18
|
Requires-Dist: black==22.3.0; extra == "dev"
|
|
18
19
|
Requires-Dist: ruff==0.9.5; extra == "dev"
|
|
@@ -20,3 +21,4 @@ Requires-Dist: pytest==7.2.0; extra == "dev"
|
|
|
20
21
|
Requires-Dist: bandit[toml]==1.7.4; extra == "dev"
|
|
21
22
|
Requires-Dist: isort==5.11.4; extra == "dev"
|
|
22
23
|
Requires-Dist: pydocstyle==6.1.1; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,368 @@
|
|
|
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. **Search Items**
|
|
106
|
+
```python
|
|
107
|
+
from datacosmos.stac.item.models.catalog_search_parameters import CatalogSearchParameters
|
|
108
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
109
|
+
from datacosmos.stac.stac_client import STACClient
|
|
110
|
+
|
|
111
|
+
client = DatacosmosClient()
|
|
112
|
+
stac_client = STACClient(client)
|
|
113
|
+
|
|
114
|
+
params = CatalogSearchParameters(
|
|
115
|
+
start_date="2/9/2025",
|
|
116
|
+
end_date="2/9/2025",
|
|
117
|
+
satellite=["MANTIS"],
|
|
118
|
+
product_type=["Satellite"],
|
|
119
|
+
processing_level=["L1A"]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
items = list(stac_client.search_items(parameters=params, project_id="your-project-id"))
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### 2. **Fetch a Single Item**
|
|
126
|
+
```python
|
|
127
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
128
|
+
from datacosmos.stac.stac_client import STACClient
|
|
129
|
+
|
|
130
|
+
client = DatacosmosClient()
|
|
131
|
+
stac_client = STACClient(client)
|
|
132
|
+
|
|
133
|
+
item = stac_client.fetch_item(item_id="example-item", collection_id="example-collection")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### 3. **Fetch All Items in a Collection**
|
|
137
|
+
```python
|
|
138
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
139
|
+
from datacosmos.stac.stac_client import STACClient
|
|
140
|
+
|
|
141
|
+
client = DatacosmosClient()
|
|
142
|
+
stac_client = STACClient(client)
|
|
143
|
+
|
|
144
|
+
items = stac_client.fetch_collection_items(collection_id="example-collection")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### 4. **Create a New STAC Item**
|
|
148
|
+
```python
|
|
149
|
+
from pystac import Item, Asset
|
|
150
|
+
from datetime import datetime
|
|
151
|
+
|
|
152
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
153
|
+
from datacosmos.stac.stac_client import STACClient
|
|
154
|
+
|
|
155
|
+
client = DatacosmosClient()
|
|
156
|
+
stac_client = STACClient(client)
|
|
157
|
+
|
|
158
|
+
stac_item = Item(
|
|
159
|
+
id="new-item",
|
|
160
|
+
geometry={"type": "Point", "coordinates": [102.0, 0.5]},
|
|
161
|
+
bbox=[101.0, 0.0, 103.0, 1.0],
|
|
162
|
+
datetime=datetime.utcnow(),
|
|
163
|
+
properties={},
|
|
164
|
+
collection="example-collection"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
stac_item.add_asset(
|
|
168
|
+
"image",
|
|
169
|
+
Asset(
|
|
170
|
+
href="https://example.com/sample-image.tiff",
|
|
171
|
+
media_type="image/tiff",
|
|
172
|
+
roles=["data"],
|
|
173
|
+
title="Sample Image"
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
stac_client.create_item(collection_id="example-collection", item=stac_item)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### 5. **Update an Existing STAC Item**
|
|
181
|
+
```python
|
|
182
|
+
from datacosmos.stac.models.item_update import ItemUpdate
|
|
183
|
+
from pystac import Asset, Link
|
|
184
|
+
|
|
185
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
186
|
+
from datacosmos.stac.stac_client import STACClient
|
|
187
|
+
|
|
188
|
+
client = DatacosmosClient()
|
|
189
|
+
stac_client = STACClient(client)
|
|
190
|
+
|
|
191
|
+
update_payload = ItemUpdate(
|
|
192
|
+
properties={
|
|
193
|
+
"new_property": "updated_value",
|
|
194
|
+
"datetime": "2024-11-10T14:58:00Z"
|
|
195
|
+
},
|
|
196
|
+
assets={
|
|
197
|
+
"image": Asset(
|
|
198
|
+
href="https://example.com/updated-image.tiff",
|
|
199
|
+
media_type="image/tiff"
|
|
200
|
+
)
|
|
201
|
+
},
|
|
202
|
+
links=[
|
|
203
|
+
Link(rel="self", target="https://example.com/updated-image.tiff")
|
|
204
|
+
],
|
|
205
|
+
geometry={
|
|
206
|
+
"type": "Point",
|
|
207
|
+
"coordinates": [10, 20]
|
|
208
|
+
},
|
|
209
|
+
bbox=[10.0, 20.0, 30.0, 40.0]
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
stac_client.update_item(item_id="new-item", collection_id="example-collection", update_data=update_payload)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### 6. **Delete an Item**
|
|
216
|
+
```python
|
|
217
|
+
|
|
218
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
219
|
+
from datacosmos.stac.stac_client import STACClient
|
|
220
|
+
|
|
221
|
+
client = DatacosmosClient()
|
|
222
|
+
stac_client = STACClient(client)
|
|
223
|
+
|
|
224
|
+
stac_client.delete_item(item_id="new-item", collection_id="example-collection")
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### 7. Fetch a Collection
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
|
|
231
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
232
|
+
from datacosmos.stac.stac_client import STACClient
|
|
233
|
+
|
|
234
|
+
client = DatacosmosClient()
|
|
235
|
+
stac_client = STACClient(client)
|
|
236
|
+
|
|
237
|
+
collection = stac_client.fetch_collection("test-collection")
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### 8. Fetch All Collections
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
|
|
244
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
245
|
+
from datacosmos.stac.stac_client import STACClient
|
|
246
|
+
|
|
247
|
+
client = DatacosmosClient()
|
|
248
|
+
stac_client = STACClient(client)
|
|
249
|
+
|
|
250
|
+
collections = list(stac_client.fetch_all_collections())
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### 9. Create a Collection
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from pystac import Collection
|
|
257
|
+
|
|
258
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
259
|
+
from datacosmos.stac.stac_client import STACClient
|
|
260
|
+
|
|
261
|
+
client = DatacosmosClient()
|
|
262
|
+
stac_client = STACClient(client)
|
|
263
|
+
|
|
264
|
+
new_collection = Collection(
|
|
265
|
+
id="test-collection",
|
|
266
|
+
title="Test Collection",
|
|
267
|
+
description="This is a test collection",
|
|
268
|
+
license="proprietary",
|
|
269
|
+
extent={
|
|
270
|
+
"spatial": {"bbox": [[-180, -90, 180, 90]]},
|
|
271
|
+
"temporal": {"interval": [["2023-01-01T00:00:00Z", None]]},
|
|
272
|
+
},
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
stac_client.create_collection(new_collection)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### 10. Update a Collection
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
from datacosmos.stac.collection.models.collection_update import CollectionUpdate
|
|
282
|
+
|
|
283
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
284
|
+
from datacosmos.stac.stac_client import STACClient
|
|
285
|
+
|
|
286
|
+
client = DatacosmosClient()
|
|
287
|
+
stac_client = STACClient(client)
|
|
288
|
+
|
|
289
|
+
update_data = CollectionUpdate(
|
|
290
|
+
title="Updated Collection Title",
|
|
291
|
+
description="Updated description",
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
stac_client.update_collection("test-collection", update_data)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### 11. Delete a Collection
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
|
|
301
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
302
|
+
from datacosmos.stac.stac_client import STACClient
|
|
303
|
+
|
|
304
|
+
client = DatacosmosClient()
|
|
305
|
+
stac_client = STACClient(client)
|
|
306
|
+
|
|
307
|
+
stac_client.delete_collection("test-collection")
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Uploading Files and Registering STAC Items
|
|
311
|
+
|
|
312
|
+
You can use the `DatacosmosUploader` class to upload files to the DataCosmos cloud storage and register a STAC item.
|
|
313
|
+
|
|
314
|
+
#### Upload Files and Register STAC Item
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
from datacosmos.uploader.datacosmos_uploader import DatacosmosUploader
|
|
318
|
+
|
|
319
|
+
from datacosmos.datacosmos_client import DatacosmosClient
|
|
320
|
+
|
|
321
|
+
client = DatacosmosClient()
|
|
322
|
+
|
|
323
|
+
uploader = DatacosmosUploader(client)
|
|
324
|
+
item_json_file_path = "/path/to/stac_item.json"
|
|
325
|
+
uploader.upload_and_register_item(item_json_file_path)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Error Handling
|
|
329
|
+
|
|
330
|
+
Use `try-except` blocks to handle API errors gracefully:
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
try:
|
|
334
|
+
data = client.get_data("dataset_id")
|
|
335
|
+
print(data)
|
|
336
|
+
except Exception as e:
|
|
337
|
+
print(f"An error occurred: {e}")
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Contributing
|
|
341
|
+
|
|
342
|
+
To contribute:
|
|
343
|
+
|
|
344
|
+
1. Fork the repository.
|
|
345
|
+
2. Create a feature branch.
|
|
346
|
+
3. Submit a pull request.
|
|
347
|
+
|
|
348
|
+
### Development Setup
|
|
349
|
+
|
|
350
|
+
Use `uv` for dependency management:
|
|
351
|
+
|
|
352
|
+
```sh
|
|
353
|
+
pip install uv
|
|
354
|
+
uv venv
|
|
355
|
+
uv pip install -r pyproject.toml .[dev]
|
|
356
|
+
source .venv/bin/activate
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Before making changes, run:
|
|
360
|
+
|
|
361
|
+
```sh
|
|
362
|
+
black .
|
|
363
|
+
isort .
|
|
364
|
+
ruff check .
|
|
365
|
+
pydocstyle .
|
|
366
|
+
bandit -r -c pyproject.toml .
|
|
367
|
+
pytest
|
|
368
|
+
```
|
|
@@ -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
|
|
@@ -6,6 +6,7 @@ from pystac import Collection, Extent, SpatialExtent, TemporalExtent
|
|
|
6
6
|
from pystac.utils import str_to_datetime
|
|
7
7
|
|
|
8
8
|
from datacosmos.datacosmos_client import DatacosmosClient
|
|
9
|
+
from datacosmos.exceptions.datacosmos_exception import DatacosmosException
|
|
9
10
|
from datacosmos.stac.collection.models.collection_update import CollectionUpdate
|
|
10
11
|
from datacosmos.utils.http_response.check_api_response import check_api_response
|
|
11
12
|
|
|
@@ -145,5 +146,8 @@ class CollectionClient:
|
|
|
145
146
|
"""
|
|
146
147
|
try:
|
|
147
148
|
return next_href.split("?")[1].split("=")[-1]
|
|
148
|
-
except (IndexError, AttributeError):
|
|
149
|
-
raise
|
|
149
|
+
except (IndexError, AttributeError) as e:
|
|
150
|
+
raise DatacosmosException(
|
|
151
|
+
f"Failed to parse pagination token from {next_href}",
|
|
152
|
+
response=e.response,
|
|
153
|
+
) from e
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Constants for STAC."""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Satellite name mapping."""
|
|
2
|
+
|
|
3
|
+
SATELLITE_NAME_MAPPING = {
|
|
4
|
+
"GEOSAT-2": "2014-033D",
|
|
5
|
+
"SUPERVIEW-1-01": "2016-083A",
|
|
6
|
+
"SUPERVIEW-1-02": "2016-083B",
|
|
7
|
+
"SUPERVIEW-1-03": "2018-002A",
|
|
8
|
+
"SUPERVIEW-1-04": "2018-002B",
|
|
9
|
+
"MANTIS": "2023-174B",
|
|
10
|
+
"MENUT": "2023-001B",
|
|
11
|
+
"HAMMER": "2024-043BC",
|
|
12
|
+
"HAMMER-EM": "COSPAR-HAMMER-EM-TBD",
|
|
13
|
+
"Alisio": "2023-185M",
|
|
14
|
+
"Platero": "2023-174G",
|
|
15
|
+
"PHISAT-2": "2024-149C",
|
|
16
|
+
"PHISAT-2 EM": "COSPAR-PHISAT2-EM-TBD",
|
|
17
|
+
"Sentinel-2A": "2015-028A",
|
|
18
|
+
"Sentinel-2B": "2017-013A",
|
|
19
|
+
"Sentinel-2C": "2024-157A",
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Enums for STAC."""
|
|
@@ -9,6 +9,10 @@ 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.catalog_search_parameters import (
|
|
13
|
+
CatalogSearchParameters,
|
|
14
|
+
)
|
|
15
|
+
from datacosmos.stac.item.models.datacosmos_item import DatacosmosItem
|
|
12
16
|
from datacosmos.stac.item.models.item_update import ItemUpdate
|
|
13
17
|
from datacosmos.stac.item.models.search_parameters import SearchParameters
|
|
14
18
|
from datacosmos.utils.http_response.check_api_response import check_api_response
|
|
@@ -58,20 +62,23 @@ class ItemClient:
|
|
|
58
62
|
|
|
59
63
|
return self.search_items(parameters)
|
|
60
64
|
|
|
61
|
-
def search_items(
|
|
65
|
+
def search_items(
|
|
66
|
+
self, parameters: CatalogSearchParameters, project_id: str
|
|
67
|
+
) -> Generator[Item, None, None]:
|
|
62
68
|
"""Query the STAC catalog using the POST endpoint with filtering and pagination.
|
|
63
69
|
|
|
64
70
|
Args:
|
|
65
|
-
parameters (
|
|
71
|
+
parameters (CatalogSearchParameters): The search parameters.
|
|
66
72
|
|
|
67
73
|
Yields:
|
|
68
74
|
Item: Parsed STAC item.
|
|
69
75
|
"""
|
|
70
76
|
url = self.base_url.with_suffix("/search")
|
|
71
|
-
|
|
77
|
+
parameters_query = parameters.to_query()
|
|
78
|
+
body = {"project": project_id, "limit": 50, "query": parameters_query}
|
|
72
79
|
return self._paginate_items(url, body)
|
|
73
80
|
|
|
74
|
-
def create_item(self, collection_id: str, item: Item) -> None:
|
|
81
|
+
def create_item(self, collection_id: str, item: Item | DatacosmosItem) -> None:
|
|
75
82
|
"""Create a new STAC item in a specified collection.
|
|
76
83
|
|
|
77
84
|
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
|