datacosmos 0.0.1__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.

@@ -0,0 +1,12 @@
1
+ Copyright (c) 2022, OPEN COSMOS LTD
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+
8
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+
10
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.2
2
+ Name: datacosmos
3
+ Version: 0.0.1
4
+ Summary: A library for interacting with DataCosmos from Python code
5
+ Author-email: Open Cosmos <support@open-cosmos.com>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Operating System :: OS Independent
8
+ Requires-Python: >=3.10
9
+ License-File: LICENSE.md
10
+ Requires-Dist: pydantic_settings>=2.7.0
11
+ Requires-Dist: requests==2.31.0
12
+ Requires-Dist: oauthlib==3.2.0
13
+ Requires-Dist: requests-oauthlib==1.3.1
14
+ Requires-Dist: pydantic==2.10.6
15
+ Requires-Dist: pystac==1.12.1
16
+ Provides-Extra: dev
17
+ Requires-Dist: black==22.3.0; extra == "dev"
18
+ Requires-Dist: ruff==0.9.5; extra == "dev"
19
+ Requires-Dist: pytest==7.2.0; extra == "dev"
20
+ Requires-Dist: bandit[toml]==1.7.4; extra == "dev"
21
+ Requires-Dist: isort==5.11.4; extra == "dev"
22
+ Requires-Dist: pydocstyle==6.1.1; extra == "dev"
@@ -0,0 +1,239 @@
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
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 datacosmos.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.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 pystac import Item, Asset
139
+ from datetime import datetime
140
+
141
+ stac_item = Item(
142
+ id="new-item",
143
+ geometry={"type": "Point", "coordinates": [102.0, 0.5]},
144
+ bbox=[101.0, 0.0, 103.0, 1.0],
145
+ datetime=datetime.utcnow(),
146
+ properties={},
147
+ collection="example-collection"
148
+ )
149
+
150
+ stac_item.add_asset(
151
+ "image",
152
+ Asset(
153
+ href="https://example.com/sample-image.tiff",
154
+ media_type="image/tiff",
155
+ roles=["data"],
156
+ title="Sample Image"
157
+ )
158
+ )
159
+
160
+ stac_client.create_item(collection_id="example-collection", item=stac_item)
161
+ ```
162
+
163
+ #### 5. **Update an Existing STAC Item**
164
+
165
+ ```python
166
+ from datacosmos.stac.models.item_update import ItemUpdate
167
+ from pystac import Asset, Link
168
+
169
+ update_payload = ItemUpdate(
170
+ properties={
171
+ "new_property": "updated_value",
172
+ "datetime": "2024-11-10T14:58:00Z"
173
+ },
174
+ assets={
175
+ "image": Asset(
176
+ href="https://example.com/updated-image.tiff",
177
+ media_type="image/tiff"
178
+ )
179
+ },
180
+ links=[
181
+ Link(rel="self", target="https://example.com/updated-image.tiff")
182
+ ],
183
+ geometry={
184
+ "type": "Point",
185
+ "coordinates": [10, 20]
186
+ },
187
+ bbox=[10.0, 20.0, 30.0, 40.0]
188
+ )
189
+
190
+ stac_client.update_item(item_id="new-item", collection_id="example-collection", update_data=update_payload)
191
+ ```
192
+
193
+ #### 6. **Delete an Item**
194
+
195
+ ```python
196
+ stac_client.delete_item(item_id="new-item", collection_id="example-collection")
197
+ ```
198
+
199
+ ## Configuration Options
200
+
201
+ - **Recommended:** Instantiate `DatacosmosClient` with a `Config` object.
202
+ - Alternatively, use **YAML files** (`config/config.yaml`).
203
+ - Or, use **environment variables**.
204
+
205
+ ## Contributing
206
+
207
+ If you would like to contribute:
208
+
209
+ 1. Fork the repository.
210
+ 2. Create a feature branch.
211
+ 3. Submit a pull request.
212
+
213
+ ### Development Setup
214
+
215
+ If you are developing the SDK, you can use `uv` for dependency management:
216
+
217
+ ```sh
218
+ pip install uv
219
+ uv venv
220
+ uv pip install -r pyproject.toml
221
+ uv pip install -r pyproject.toml .[dev]
222
+ source .venv/bin/activate
223
+ ```
224
+
225
+ Before making changes, ensure that:
226
+
227
+ - The code is formatted using **Black** and **isort**.
228
+ - Static analysis and linting are performed using **ruff** and **pydocstyle**.
229
+ - Security checks are performed using **bandit**.
230
+ - Tests are executed with **pytest**.
231
+
232
+ ```sh
233
+ black .
234
+ isort .
235
+ ruff check . --select C901
236
+ pydocstyle .
237
+ bandit -r -c pyproject.toml . --skip B105,B106,B101
238
+ pytest
239
+ ```
@@ -0,0 +1 @@
1
+ """A library for interacting with DataCosmos from Python code."""
@@ -0,0 +1,120 @@
1
+ """DatacosmosClient handles authenticated interactions with the Datacosmos API.
2
+
3
+ Automatically manages token refreshing and provides HTTP convenience
4
+ methods.
5
+ """
6
+
7
+ from datetime import datetime, timedelta, timezone
8
+ from typing import Any, Optional
9
+
10
+ import requests
11
+ from oauthlib.oauth2 import BackendApplicationClient
12
+ from requests.exceptions import ConnectionError, HTTPError, RequestException, Timeout
13
+ from requests_oauthlib import OAuth2Session
14
+
15
+ from config.config import Config
16
+ from datacosmos.exceptions.datacosmos_exception import DatacosmosException
17
+
18
+
19
+ class DatacosmosClient:
20
+ """Client to interact with the Datacosmos API with authentication and request handling."""
21
+
22
+ def __init__(self, config: Optional[Config] = None):
23
+ """Initialize the DatacosmosClient.
24
+
25
+ Args:
26
+ config (Optional[Config]): Configuration object.
27
+ """
28
+ if config:
29
+ self.config = config
30
+ else:
31
+ try:
32
+ self.config = Config.from_yaml()
33
+ except ValueError:
34
+ self.config = Config.from_env()
35
+
36
+ self.token = None
37
+ self.token_expiry = None
38
+ self._http_client = self._authenticate_and_initialize_client()
39
+
40
+ def _authenticate_and_initialize_client(self) -> requests.Session:
41
+ """Authenticate and initialize the HTTP client with a valid token."""
42
+ try:
43
+ client = BackendApplicationClient(
44
+ client_id=self.config.authentication.client_id
45
+ )
46
+ oauth_session = OAuth2Session(client=client)
47
+
48
+ token_response = oauth_session.fetch_token(
49
+ token_url=self.config.authentication.token_url,
50
+ client_id=self.config.authentication.client_id,
51
+ client_secret=self.config.authentication.client_secret,
52
+ audience=self.config.authentication.audience,
53
+ )
54
+
55
+ self.token = token_response["access_token"]
56
+ self.token_expiry = datetime.now(timezone.utc) + timedelta(
57
+ seconds=token_response.get("expires_in", 3600)
58
+ )
59
+
60
+ http_client = requests.Session()
61
+ http_client.headers.update({"Authorization": f"Bearer {self.token}"})
62
+ return http_client
63
+ except (HTTPError, ConnectionError, Timeout) as e:
64
+ raise DatacosmosException(f"Authentication failed: {str(e)}") from e
65
+ except RequestException as e:
66
+ raise DatacosmosException(
67
+ f"Unexpected request failure during authentication: {str(e)}"
68
+ ) from e
69
+
70
+ def _refresh_token_if_needed(self):
71
+ """Refresh the token if it has expired."""
72
+ if not self.token or self.token_expiry <= datetime.now(timezone.utc):
73
+ self._http_client = self._authenticate_and_initialize_client()
74
+
75
+ def request(
76
+ self, method: str, url: str, *args: Any, **kwargs: Any
77
+ ) -> requests.Response:
78
+ """Send an HTTP request using the authenticated session."""
79
+ self._refresh_token_if_needed()
80
+ try:
81
+ response = self._http_client.request(method, url, *args, **kwargs)
82
+ response.raise_for_status()
83
+ return response
84
+ except HTTPError as e:
85
+ raise DatacosmosException(
86
+ f"HTTP error during {method.upper()} request to {url}",
87
+ response=e.response,
88
+ ) from e
89
+ except ConnectionError as e:
90
+ raise DatacosmosException(
91
+ f"Connection error during {method.upper()} request to {url}: {str(e)}"
92
+ ) from e
93
+ except Timeout as e:
94
+ raise DatacosmosException(
95
+ f"Request timeout during {method.upper()} request to {url}: {str(e)}"
96
+ ) from e
97
+ except RequestException as e:
98
+ raise DatacosmosException(
99
+ f"Unexpected request failure during {method.upper()} request to {url}: {str(e)}"
100
+ ) from e
101
+
102
+ def get(self, url: str, *args: Any, **kwargs: Any) -> requests.Response:
103
+ """Send a GET request using the authenticated session."""
104
+ return self.request("GET", url, *args, **kwargs)
105
+
106
+ def post(self, url: str, *args: Any, **kwargs: Any) -> requests.Response:
107
+ """Send a POST request using the authenticated session."""
108
+ return self.request("POST", url, *args, **kwargs)
109
+
110
+ def put(self, url: str, *args: Any, **kwargs: Any) -> requests.Response:
111
+ """Send a PUT request using the authenticated session."""
112
+ return self.request("PUT", url, *args, **kwargs)
113
+
114
+ def patch(self, url: str, *args: Any, **kwargs: Any) -> requests.Response:
115
+ """Send a PATCH request using the authenticated session."""
116
+ return self.request("PATCH", url, *args, **kwargs)
117
+
118
+ def delete(self, url: str, *args: Any, **kwargs: Any) -> requests.Response:
119
+ """Send a DELETE request using the authenticated session."""
120
+ return self.request("DELETE", url, *args, **kwargs)
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.2
2
+ Name: datacosmos
3
+ Version: 0.0.1
4
+ Summary: A library for interacting with DataCosmos from Python code
5
+ Author-email: Open Cosmos <support@open-cosmos.com>
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Operating System :: OS Independent
8
+ Requires-Python: >=3.10
9
+ License-File: LICENSE.md
10
+ Requires-Dist: pydantic_settings>=2.7.0
11
+ Requires-Dist: requests==2.31.0
12
+ Requires-Dist: oauthlib==3.2.0
13
+ Requires-Dist: requests-oauthlib==1.3.1
14
+ Requires-Dist: pydantic==2.10.6
15
+ Requires-Dist: pystac==1.12.1
16
+ Provides-Extra: dev
17
+ Requires-Dist: black==22.3.0; extra == "dev"
18
+ Requires-Dist: ruff==0.9.5; extra == "dev"
19
+ Requires-Dist: pytest==7.2.0; extra == "dev"
20
+ Requires-Dist: bandit[toml]==1.7.4; extra == "dev"
21
+ Requires-Dist: isort==5.11.4; extra == "dev"
22
+ Requires-Dist: pydocstyle==6.1.1; extra == "dev"
@@ -0,0 +1,11 @@
1
+ LICENSE.md
2
+ README.md
3
+ pyproject.toml
4
+ datacosmos/__init__.py
5
+ datacosmos/datacosmos_client.py
6
+ datacosmos.egg-info/PKG-INFO
7
+ datacosmos.egg-info/SOURCES.txt
8
+ datacosmos.egg-info/dependency_links.txt
9
+ datacosmos.egg-info/requires.txt
10
+ datacosmos.egg-info/top_level.txt
11
+ tests/test_pass.py
@@ -0,0 +1,14 @@
1
+ pydantic_settings>=2.7.0
2
+ requests==2.31.0
3
+ oauthlib==3.2.0
4
+ requests-oauthlib==1.3.1
5
+ pydantic==2.10.6
6
+ pystac==1.12.1
7
+
8
+ [dev]
9
+ black==22.3.0
10
+ ruff==0.9.5
11
+ pytest==7.2.0
12
+ bandit[toml]==1.7.4
13
+ isort==5.11.4
14
+ pydocstyle==6.1.1
@@ -0,0 +1 @@
1
+ datacosmos
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "datacosmos"
7
+ version = "0.0.1"
8
+ authors = [
9
+ { name="Open Cosmos", email="support@open-cosmos.com" },
10
+ ]
11
+ description = "A library for interacting with DataCosmos from Python code"
12
+ requires-python = ">=3.10"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+ dependencies = [
18
+ "pydantic_settings>=2.7.0",
19
+ "requests==2.31.0",
20
+ "oauthlib==3.2.0",
21
+ "requests-oauthlib==1.3.1",
22
+ "pydantic==2.10.6",
23
+ "pystac==1.12.1"
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ dev = [
28
+ "black==22.3.0",
29
+ "ruff==0.9.5",
30
+ "pytest==7.2.0",
31
+ "bandit[toml]==1.7.4",
32
+ "isort==5.11.4",
33
+ "pydocstyle==6.1.1"
34
+ ]
35
+
36
+ [tool.setuptools]
37
+ packages = ["datacosmos"]
38
+
39
+ [tool.bandit]
40
+ exclude_dirs = [".venv"]
41
+
42
+ [tool.pydocstyle]
43
+ convention = "google"
44
+
45
+ [tool.isort]
46
+ profile = "black"
47
+ multi_line_output = 3
48
+ include_trailing_comma = true
49
+ force_grid_wrap = 0
50
+ use_parentheses = true
51
+ line_length = 88
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,9 @@
1
+ """Test suite for basic functionality and CI setup."""
2
+
3
+
4
+ class TestPass:
5
+ """A simple test class to validate the CI pipeline setup."""
6
+
7
+ def test_pass(self):
8
+ """A passing test to ensure the CI pipeline is functional."""
9
+ assert True