From 569ece214598c24b148d3fe80310437280fa8063 Mon Sep 17 00:00:00 2001 From: "tiago.peres.sousa" Date: Tue, 11 Feb 2025 16:40:03 +0000 Subject: [PATCH 1/7] Rename client with datacosmos_client; Move current stac api functionalities to a item dedicated folder --- datacosmos/{client.py => datacosmos_client.py} | 0 datacosmos/stac/item/__init__.py | 4 ++++ .../stac/{stac_client.py => item/item_client.py} | 8 +++++++- datacosmos/stac/{ => item}/models/item_update.py | 0 datacosmos/stac/{ => item}/models/search_parameters.py | 0 .../test_client_authentication.py | 6 +++--- .../test_client_delete_request.py | 6 ++++-- .../test_client_get_request.py | 6 ++++-- .../test_client_patch_request.py | 6 ++++-- .../test_client_post_request.py | 6 ++++-- .../test_client_put_request.py | 6 ++++-- .../test_client_token_refreshing.py | 6 ++++-- .../item_client}/test_create_item.py | 8 ++++---- .../item_client}/test_delete_item.py | 8 ++++---- .../item_client}/test_fetch_collection_items.py | 10 +++++----- .../item_client}/test_fetch_item.py | 8 ++++---- .../item_client}/test_search_items.py | 10 +++++----- .../item_client}/test_update_item.py | 10 +++++----- .../stac/{ => item}/models/test_item_update.py | 2 +- 19 files changed, 66 insertions(+), 44 deletions(-) rename datacosmos/{client.py => datacosmos_client.py} (100%) create mode 100644 datacosmos/stac/item/__init__.py rename datacosmos/stac/{stac_client.py => item/item_client.py} (94%) rename datacosmos/stac/{ => item}/models/item_update.py (100%) rename datacosmos/stac/{ => item}/models/search_parameters.py (100%) rename tests/unit/datacosmos/{client => datacosmos_client}/test_client_authentication.py (93%) rename tests/unit/datacosmos/{client => datacosmos_client}/test_client_delete_request.py (87%) rename tests/unit/datacosmos/{client => datacosmos_client}/test_client_get_request.py (88%) rename tests/unit/datacosmos/{client => datacosmos_client}/test_client_patch_request.py (89%) rename tests/unit/datacosmos/{client => datacosmos_client}/test_client_post_request.py (89%) rename tests/unit/datacosmos/{client => datacosmos_client}/test_client_put_request.py (89%) rename tests/unit/datacosmos/{client => datacosmos_client}/test_client_token_refreshing.py (91%) rename tests/unit/datacosmos/stac/{stac_client => item/item_client}/test_create_item.py (88%) rename tests/unit/datacosmos/stac/{stac_client => item/item_client}/test_delete_item.py (83%) rename tests/unit/datacosmos/stac/{stac_client => item/item_client}/test_fetch_collection_items.py (83%) rename tests/unit/datacosmos/stac/{stac_client => item/item_client}/test_fetch_item.py (88%) rename tests/unit/datacosmos/stac/{stac_client => item/item_client}/test_search_items.py (86%) rename tests/unit/datacosmos/stac/{stac_client => item/item_client}/test_update_item.py (86%) rename tests/unit/datacosmos/stac/{ => item}/models/test_item_update.py (96%) diff --git a/datacosmos/client.py b/datacosmos/datacosmos_client.py similarity index 100% rename from datacosmos/client.py rename to datacosmos/datacosmos_client.py diff --git a/datacosmos/stac/item/__init__.py b/datacosmos/stac/item/__init__.py new file mode 100644 index 0000000..05bd8ad --- /dev/null +++ b/datacosmos/stac/item/__init__.py @@ -0,0 +1,4 @@ +"""STAC package for interacting with items from the STAC API, providing query and fetch functionalities. + +It enables interaction with items from the STAC using an authenticated Datacosmos client. +""" diff --git a/datacosmos/stac/stac_client.py b/datacosmos/stac/item/item_client.py similarity index 94% rename from datacosmos/stac/stac_client.py rename to datacosmos/stac/item/item_client.py index ea7f0f3..a8f8426 100644 --- a/datacosmos/stac/stac_client.py +++ b/datacosmos/stac/item/item_client.py @@ -7,14 +7,20 @@ from pystac import Item +<<<<<<< HEAD:datacosmos/stac/stac_client.py from datacosmos.client import DatacosmosClient from datacosmos.exceptions.datacosmos_exception import DatacosmosException from datacosmos.stac.models.item_update import ItemUpdate from datacosmos.stac.models.search_parameters import SearchParameters from datacosmos.utils.http_response import check_api_response +======= +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.item.models.item_update import ItemUpdate +from datacosmos.stac.item.models.search_parameters import SearchParameters +>>>>>>> 77a15fc (Rename client with datacosmos_client; Move current stac api functionalities to a item dedicated folder):datacosmos/stac/item/item_client.py -class STACClient: +class ItemClient: """Client for interacting with the STAC API.""" def __init__(self, client: DatacosmosClient): diff --git a/datacosmos/stac/models/item_update.py b/datacosmos/stac/item/models/item_update.py similarity index 100% rename from datacosmos/stac/models/item_update.py rename to datacosmos/stac/item/models/item_update.py diff --git a/datacosmos/stac/models/search_parameters.py b/datacosmos/stac/item/models/search_parameters.py similarity index 100% rename from datacosmos/stac/models/search_parameters.py rename to datacosmos/stac/item/models/search_parameters.py diff --git a/tests/unit/datacosmos/client/test_client_authentication.py b/tests/unit/datacosmos/datacosmos_client/test_client_authentication.py similarity index 93% rename from tests/unit/datacosmos/client/test_client_authentication.py rename to tests/unit/datacosmos/datacosmos_client/test_client_authentication.py index 1e536f1..69ea619 100644 --- a/tests/unit/datacosmos/client/test_client_authentication.py +++ b/tests/unit/datacosmos/datacosmos_client/test_client_authentication.py @@ -6,7 +6,7 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient +from datacosmos.datacosmos_client import DatacosmosClient @pytest.mark.usefixtures("mock_fetch_token", "mock_auth_client") @@ -16,7 +16,7 @@ class TestClientAuthentication: @pytest.fixture def mock_fetch_token(self): """Fixture to mock OAuth2 token fetch.""" - with patch("datacosmos.client.OAuth2Session.fetch_token") as mock: + with patch("datacosmos.datacosmos_client.OAuth2Session.fetch_token") as mock: mock.return_value = { "access_token": "mock-access-token", "expires_in": 3600, @@ -27,7 +27,7 @@ def mock_fetch_token(self): def mock_auth_client(self, mock_fetch_token): """Fixture to mock the authentication client initialization.""" with patch( - "datacosmos.client.DatacosmosClient._authenticate_and_initialize_client", + "datacosmos.datacosmos_client.DatacosmosClient._authenticate_and_initialize_client", autospec=True, ) as mock: diff --git a/tests/unit/datacosmos/client/test_client_delete_request.py b/tests/unit/datacosmos/datacosmos_client/test_client_delete_request.py similarity index 87% rename from tests/unit/datacosmos/client/test_client_delete_request.py rename to tests/unit/datacosmos/datacosmos_client/test_client_delete_request.py index 6b61f0b..8d61786 100644 --- a/tests/unit/datacosmos/client/test_client_delete_request.py +++ b/tests/unit/datacosmos/datacosmos_client/test_client_delete_request.py @@ -2,10 +2,12 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient +from datacosmos.datacosmos_client import DatacosmosClient -@patch("datacosmos.client.DatacosmosClient._authenticate_and_initialize_client") +@patch( + "datacosmos.datacosmos_client.DatacosmosClient._authenticate_and_initialize_client" +) def test_delete_request(mock_auth_client): """Test that the client performs a DELETE request correctly.""" # Mock the HTTP client diff --git a/tests/unit/datacosmos/client/test_client_get_request.py b/tests/unit/datacosmos/datacosmos_client/test_client_get_request.py similarity index 88% rename from tests/unit/datacosmos/client/test_client_get_request.py rename to tests/unit/datacosmos/datacosmos_client/test_client_get_request.py index 4963756..50e22f9 100644 --- a/tests/unit/datacosmos/client/test_client_get_request.py +++ b/tests/unit/datacosmos/datacosmos_client/test_client_get_request.py @@ -2,10 +2,12 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient +from datacosmos.datacosmos_client import DatacosmosClient -@patch("datacosmos.client.DatacosmosClient._authenticate_and_initialize_client") +@patch( + "datacosmos.datacosmos_client.DatacosmosClient._authenticate_and_initialize_client" +) def test_client_get_request(mock_auth_client): """Test that the client performs a GET request correctly.""" # Mock the HTTP client diff --git a/tests/unit/datacosmos/client/test_client_patch_request.py b/tests/unit/datacosmos/datacosmos_client/test_client_patch_request.py similarity index 89% rename from tests/unit/datacosmos/client/test_client_patch_request.py rename to tests/unit/datacosmos/datacosmos_client/test_client_patch_request.py index fb92c52..3a0d528 100644 --- a/tests/unit/datacosmos/client/test_client_patch_request.py +++ b/tests/unit/datacosmos/datacosmos_client/test_client_patch_request.py @@ -2,10 +2,12 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient +from datacosmos.datacosmos_client import DatacosmosClient -@patch("datacosmos.client.DatacosmosClient._authenticate_and_initialize_client") +@patch( + "datacosmos.datacosmos_client.DatacosmosClient._authenticate_and_initialize_client" +) def test_patch_request(mock_auth_client): """Test that the client performs a PATCH request correctly.""" # Mock the HTTP client diff --git a/tests/unit/datacosmos/client/test_client_post_request.py b/tests/unit/datacosmos/datacosmos_client/test_client_post_request.py similarity index 89% rename from tests/unit/datacosmos/client/test_client_post_request.py rename to tests/unit/datacosmos/datacosmos_client/test_client_post_request.py index 5023602..3235eb1 100644 --- a/tests/unit/datacosmos/client/test_client_post_request.py +++ b/tests/unit/datacosmos/datacosmos_client/test_client_post_request.py @@ -2,10 +2,12 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient +from datacosmos.datacosmos_client import DatacosmosClient -@patch("datacosmos.client.DatacosmosClient._authenticate_and_initialize_client") +@patch( + "datacosmos.datacosmos_client.DatacosmosClient._authenticate_and_initialize_client" +) def test_post_request(mock_auth_client): """Test that the client performs a POST request correctly.""" # Mock the HTTP client diff --git a/tests/unit/datacosmos/client/test_client_put_request.py b/tests/unit/datacosmos/datacosmos_client/test_client_put_request.py similarity index 89% rename from tests/unit/datacosmos/client/test_client_put_request.py rename to tests/unit/datacosmos/datacosmos_client/test_client_put_request.py index c894fc0..09e0335 100644 --- a/tests/unit/datacosmos/client/test_client_put_request.py +++ b/tests/unit/datacosmos/datacosmos_client/test_client_put_request.py @@ -2,10 +2,12 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient +from datacosmos.datacosmos_client import DatacosmosClient -@patch("datacosmos.client.DatacosmosClient._authenticate_and_initialize_client") +@patch( + "datacosmos.datacosmos_client.DatacosmosClient._authenticate_and_initialize_client" +) def test_put_request(mock_auth_client): """Test that the client performs a PUT request correctly.""" # Mock the HTTP client diff --git a/tests/unit/datacosmos/client/test_client_token_refreshing.py b/tests/unit/datacosmos/datacosmos_client/test_client_token_refreshing.py similarity index 91% rename from tests/unit/datacosmos/client/test_client_token_refreshing.py rename to tests/unit/datacosmos/datacosmos_client/test_client_token_refreshing.py index ad44784..0585ff7 100644 --- a/tests/unit/datacosmos/client/test_client_token_refreshing.py +++ b/tests/unit/datacosmos/datacosmos_client/test_client_token_refreshing.py @@ -3,10 +3,12 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient +from datacosmos.datacosmos_client import DatacosmosClient -@patch("datacosmos.client.DatacosmosClient._authenticate_and_initialize_client") +@patch( + "datacosmos.datacosmos_client.DatacosmosClient._authenticate_and_initialize_client" +) def test_client_token_refreshing(mock_auth_client): """Test that the client refreshes the token when it expires.""" # Mock the HTTP client returned by _authenticate_and_initialize_client diff --git a/tests/unit/datacosmos/stac/stac_client/test_create_item.py b/tests/unit/datacosmos/stac/item/item_client/test_create_item.py similarity index 88% rename from tests/unit/datacosmos/stac/stac_client/test_create_item.py rename to tests/unit/datacosmos/stac/item/item_client/test_create_item.py index 16ac088..c90992e 100644 --- a/tests/unit/datacosmos/stac/stac_client/test_create_item.py +++ b/tests/unit/datacosmos/stac/item/item_client/test_create_item.py @@ -4,13 +4,13 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient -from datacosmos.stac.stac_client import STACClient +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.item.item_client import ItemClient @patch("requests_oauthlib.OAuth2Session.fetch_token") @patch.object(DatacosmosClient, "post") -@patch("datacosmos.stac.stac_client.check_api_response") +@patch("datacosmos.stac.item.item_client.check_api_response") def test_create_item(mock_check_api_response, mock_post, mock_fetch_token): """Test creating a new STAC item.""" mock_fetch_token.return_value = {"access_token": "mock-token", "expires_in": 3600} @@ -38,7 +38,7 @@ def test_create_item(mock_check_api_response, mock_post, mock_fetch_token): ) ) client = DatacosmosClient(config=config) - stac_client = STACClient(client) + stac_client = ItemClient(client) item = Item.from_dict(mock_response.json()) diff --git a/tests/unit/datacosmos/stac/stac_client/test_delete_item.py b/tests/unit/datacosmos/stac/item/item_client/test_delete_item.py similarity index 83% rename from tests/unit/datacosmos/stac/stac_client/test_delete_item.py rename to tests/unit/datacosmos/stac/item/item_client/test_delete_item.py index d014220..d75240a 100644 --- a/tests/unit/datacosmos/stac/stac_client/test_delete_item.py +++ b/tests/unit/datacosmos/stac/item/item_client/test_delete_item.py @@ -2,13 +2,13 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient -from datacosmos.stac.stac_client import STACClient +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.item.item_client import ItemClient @patch("requests_oauthlib.OAuth2Session.fetch_token") @patch.object(DatacosmosClient, "delete") -@patch("datacosmos.stac.stac_client.check_api_response") +@patch("datacosmos.stac.item.item_client.check_api_response") def test_delete_item(mock_check_api_response, mock_delete, mock_fetch_token): """Test deleting a STAC item.""" mock_fetch_token.return_value = {"access_token": "mock-token", "expires_in": 3600} @@ -28,7 +28,7 @@ def test_delete_item(mock_check_api_response, mock_delete, mock_fetch_token): ) client = DatacosmosClient(config=config) - stac_client = STACClient(client) + stac_client = ItemClient(client) stac_client.delete_item("item-1", "test-collection") diff --git a/tests/unit/datacosmos/stac/stac_client/test_fetch_collection_items.py b/tests/unit/datacosmos/stac/item/item_client/test_fetch_collection_items.py similarity index 83% rename from tests/unit/datacosmos/stac/stac_client/test_fetch_collection_items.py rename to tests/unit/datacosmos/stac/item/item_client/test_fetch_collection_items.py index 11b8bc3..b81f43b 100644 --- a/tests/unit/datacosmos/stac/stac_client/test_fetch_collection_items.py +++ b/tests/unit/datacosmos/stac/item/item_client/test_fetch_collection_items.py @@ -2,13 +2,13 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient -from datacosmos.stac.stac_client import STACClient +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.item.item_client import ItemClient @patch("requests_oauthlib.OAuth2Session.fetch_token") -@patch("datacosmos.stac.stac_client.check_api_response") -@patch.object(STACClient, "search_items") +@patch("datacosmos.stac.item.item_client.check_api_response") +@patch.object(ItemClient, "search_items") def test_fetch_collection_items( mock_search_items, mock_check_api_response, mock_fetch_token ): @@ -33,7 +33,7 @@ def test_fetch_collection_items( ) client = DatacosmosClient(config=config) - stac_client = STACClient(client) + stac_client = ItemClient(client) results = list(stac_client.fetch_collection_items("test-collection")) diff --git a/tests/unit/datacosmos/stac/stac_client/test_fetch_item.py b/tests/unit/datacosmos/stac/item/item_client/test_fetch_item.py similarity index 88% rename from tests/unit/datacosmos/stac/stac_client/test_fetch_item.py rename to tests/unit/datacosmos/stac/item/item_client/test_fetch_item.py index 35c2cbb..fa9edeb 100644 --- a/tests/unit/datacosmos/stac/stac_client/test_fetch_item.py +++ b/tests/unit/datacosmos/stac/item/item_client/test_fetch_item.py @@ -2,12 +2,12 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient -from datacosmos.stac.stac_client import STACClient +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.item.item_client import ItemClient @patch("requests_oauthlib.OAuth2Session.fetch_token") -@patch("datacosmos.stac.stac_client.check_api_response") +@patch("datacosmos.stac.item.item_client.check_api_response") @patch.object(DatacosmosClient, "get") def test_fetch_item(mock_get, mock_check_api_response, mock_fetch_token): """Test fetching a single STAC item by ID.""" @@ -40,7 +40,7 @@ def test_fetch_item(mock_get, mock_check_api_response, mock_fetch_token): ) client = DatacosmosClient(config=config) - stac_client = STACClient(client) + stac_client = ItemClient(client) item = stac_client.fetch_item("item-1", "test-collection") diff --git a/tests/unit/datacosmos/stac/stac_client/test_search_items.py b/tests/unit/datacosmos/stac/item/item_client/test_search_items.py similarity index 86% rename from tests/unit/datacosmos/stac/stac_client/test_search_items.py rename to tests/unit/datacosmos/stac/item/item_client/test_search_items.py index 572a2cf..15b16c9 100644 --- a/tests/unit/datacosmos/stac/stac_client/test_search_items.py +++ b/tests/unit/datacosmos/stac/item/item_client/test_search_items.py @@ -2,13 +2,13 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient -from datacosmos.stac.models.search_parameters import SearchParameters -from datacosmos.stac.stac_client import STACClient +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.item.item_client import ItemClient +from datacosmos.stac.item.models.search_parameters import SearchParameters @patch("requests_oauthlib.OAuth2Session.fetch_token") -@patch("datacosmos.stac.stac_client.check_api_response") +@patch("datacosmos.stac.item.item_client.check_api_response") @patch.object(DatacosmosClient, "post") def test_search_items(mock_post, mock_check_api_response, mock_fetch_token): """Test searching STAC items with filters and pagination.""" @@ -46,7 +46,7 @@ def test_search_items(mock_post, mock_check_api_response, mock_fetch_token): ) client = DatacosmosClient(config=config) - stac_client = STACClient(client) + stac_client = ItemClient(client) parameters = SearchParameters(collections=["test-collection"]) results = list(stac_client.search_items(parameters)) diff --git a/tests/unit/datacosmos/stac/stac_client/test_update_item.py b/tests/unit/datacosmos/stac/item/item_client/test_update_item.py similarity index 86% rename from tests/unit/datacosmos/stac/stac_client/test_update_item.py rename to tests/unit/datacosmos/stac/item/item_client/test_update_item.py index de466c1..e845183 100644 --- a/tests/unit/datacosmos/stac/stac_client/test_update_item.py +++ b/tests/unit/datacosmos/stac/item/item_client/test_update_item.py @@ -2,14 +2,14 @@ from config.config import Config from config.models.m2m_authentication_config import M2MAuthenticationConfig -from datacosmos.client import DatacosmosClient -from datacosmos.stac.models.item_update import ItemUpdate -from datacosmos.stac.stac_client import STACClient +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.item.item_client import ItemClient +from datacosmos.stac.item.models.item_update import ItemUpdate @patch("requests_oauthlib.OAuth2Session.fetch_token") @patch.object(DatacosmosClient, "patch") -@patch("datacosmos.stac.stac_client.check_api_response") +@patch("datacosmos.stac.item.item_client.check_api_response") def test_update_item(mock_check_api_response, mock_patch, mock_fetch_token): """Test updating an existing STAC item.""" mock_fetch_token.return_value = {"access_token": "mock-token", "expires_in": 3600} @@ -37,7 +37,7 @@ def test_update_item(mock_check_api_response, mock_patch, mock_fetch_token): ) ) client = DatacosmosClient(config=config) - stac_client = STACClient(client) + stac_client = ItemClient(client) update_data = ItemUpdate( properties={"new_property": "value", "datetime": "2023-12-01T12:00:00Z"} diff --git a/tests/unit/datacosmos/stac/models/test_item_update.py b/tests/unit/datacosmos/stac/item/models/test_item_update.py similarity index 96% rename from tests/unit/datacosmos/stac/models/test_item_update.py rename to tests/unit/datacosmos/stac/item/models/test_item_update.py index d1ff225..6338ea7 100644 --- a/tests/unit/datacosmos/stac/models/test_item_update.py +++ b/tests/unit/datacosmos/stac/item/models/test_item_update.py @@ -1,6 +1,6 @@ import pytest -from datacosmos.stac.models.item_update import ItemUpdate +from datacosmos.stac.item.models.item_update import ItemUpdate class TestItemUpdate: From 5b0e4f64b88a3e84815dbbc6398bb66cb69b4dde Mon Sep 17 00:00:00 2001 From: "tiago.peres.sousa" Date: Wed, 12 Feb 2025 10:50:30 +0000 Subject: [PATCH 2/7] Add client for interacting with collections from the STAC API --- datacosmos/stac/collection/__init__.py | 4 + .../stac/collection/collection_client.py | 149 ++++++++++++++++++ datacosmos/stac/collection/models/__init__.py | 1 + .../collection/models/collection_update.py | 46 ++++++ datacosmos/stac/item/item_client.py | 2 +- datacosmos/stac/item/models/__init__.py | 1 + datacosmos/stac/stac_client.py | 12 ++ 7 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 datacosmos/stac/collection/__init__.py create mode 100644 datacosmos/stac/collection/collection_client.py create mode 100644 datacosmos/stac/collection/models/__init__.py create mode 100644 datacosmos/stac/collection/models/collection_update.py create mode 100644 datacosmos/stac/item/models/__init__.py create mode 100644 datacosmos/stac/stac_client.py diff --git a/datacosmos/stac/collection/__init__.py b/datacosmos/stac/collection/__init__.py new file mode 100644 index 0000000..f6642d5 --- /dev/null +++ b/datacosmos/stac/collection/__init__.py @@ -0,0 +1,4 @@ +"""STAC package for interacting with collections from the STAC API, providing query and fetch functionalities. + +It enables interaction with collections from the STAC using an authenticated Datacosmos client. +""" diff --git a/datacosmos/stac/collection/collection_client.py b/datacosmos/stac/collection/collection_client.py new file mode 100644 index 0000000..e9396e0 --- /dev/null +++ b/datacosmos/stac/collection/collection_client.py @@ -0,0 +1,149 @@ +"""Handles operations related to STAC collections.""" + +from typing import Generator, Optional + +from common.sdk.http_response import check_api_response +from pystac import Collection, Extent, SpatialExtent, TemporalExtent +from pystac.utils import str_to_datetime + +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.collection.models.collection_update import CollectionUpdate + + +class CollectionClient: + """Handles operations related to STAC collections.""" + + def __init__(self, client: DatacosmosClient): + """Initialize the CollectionClient with a DatacosmosClient.""" + self.client = client + self.base_url = client.config.stac.as_domain_url() + + def fetch_collection(self, collection_id: str) -> Collection: + """Fetch details of an existing STAC collection.""" + url = self.base_url.with_suffix(f"/collections/{collection_id}") + response = self.client.get(url) + check_api_response(response) + return Collection.from_dict(response.json()) + + def create_collection(self, collection: Collection) -> None: + """Create a new STAC collection. + + Args: + collection (Collection): The STAC collection to create. + + Raises: + InvalidRequest: If the collection data is malformed. + """ + if isinstance(collection.extent, dict): + spatial_data = collection.extent.get("spatial", {}).get("bbox", [[]]) + temporal_data = collection.extent.get("temporal", {}).get("interval", [[]]) + + # Convert string timestamps to datetime objects + parsed_temporal = [] + for interval in temporal_data: + start = str_to_datetime(interval[0]) if interval[0] else None + end = ( + str_to_datetime(interval[1]) + if len(interval) > 1 and interval[1] + else None + ) + parsed_temporal.append([start, end]) + + collection.extent = Extent( + spatial=SpatialExtent(spatial_data), + temporal=TemporalExtent(parsed_temporal), + ) + + url = self.base_url.with_suffix("/collections") + response = self.client.post(url, json=collection.to_dict()) + check_api_response(response) + + def update_collection( + self, collection_id: str, update_data: CollectionUpdate + ) -> None: + """Update an existing STAC collection.""" + url = self.base_url.with_suffix(f"/collections/{collection_id}") + response = self.client.patch( + url, json=update_data.model_dump(by_alias=True, exclude_none=True) + ) + check_api_response(response) + + def delete_collection(self, collection_id: str) -> None: + """Delete a STAC collection by its ID.""" + url = self.base_url.with_suffix(f"/collections/{collection_id}") + response = self.client.delete(url) + check_api_response(response) + + def fetch_all_collections(self) -> Generator[Collection, None, None]: + """Fetch all STAC collections with pagination support.""" + url = self.base_url.with_suffix("/collections") + params = {"limit": 10} + + while True: + data = self._fetch_collections_page(url, params) + yield from self._parse_collections(data) + + next_cursor = self._get_next_pagination_cursor(data) + if not next_cursor: + break + + params["cursor"] = next_cursor + + def _fetch_collections_page(self, url: str, params: dict) -> dict: + """Fetch a single page of collections from the API.""" + response = self.client.get(url, params=params) + check_api_response(response) + + data = response.json() + + if isinstance(data, list): + return {"collections": data} + + return data + + def _parse_collections(self, data: dict) -> Generator[Collection, None, None]: + """Convert API response data to STAC Collection objects, ensuring required fields exist.""" + return ( + Collection.from_dict( + { + **collection, + "type": collection.get("type", "Collection"), + "id": collection.get("id", ""), + "stac_version": collection.get("stac_version", "1.0.0"), + "extent": collection.get( + "extent", + {"spatial": {"bbox": []}, "temporal": {"interval": []}}, + ), + "links": collection.get("links", []) or [], + "properties": collection.get("properties", {}), + } + ) + for collection in data.get("collections", []) + if collection.get("type") == "Collection" + ) + + def _get_next_pagination_cursor(self, data: dict) -> Optional[str]: + """Extract the next pagination token from the response.""" + next_href = self._get_next_link(data) + return self._extract_pagination_token(next_href) if next_href else None + + def _get_next_link(self, data: dict) -> Optional[str]: + """Extract the next page link from the response.""" + next_link = next( + (link for link in data.get("links", []) if link.get("rel") == "next"), None + ) + return next_link.get("href", "") if next_link else None + + def _extract_pagination_token(self, next_href: str) -> Optional[str]: + """Extract the pagination token from the next link URL. + + Args: + next_href (str): The next page URL. + + Returns: + Optional[str]: The extracted token, or None if parsing fails. + """ + try: + return next_href.split("?")[1].split("=")[-1] + except (IndexError, AttributeError): + raise InvalidRequest(f"Failed to parse pagination token from {next_href}") diff --git a/datacosmos/stac/collection/models/__init__.py b/datacosmos/stac/collection/models/__init__.py new file mode 100644 index 0000000..f4c32e1 --- /dev/null +++ b/datacosmos/stac/collection/models/__init__.py @@ -0,0 +1 @@ +"""Models for the Collection Client.""" diff --git a/datacosmos/stac/collection/models/collection_update.py b/datacosmos/stac/collection/models/collection_update.py new file mode 100644 index 0000000..7d44619 --- /dev/null +++ b/datacosmos/stac/collection/models/collection_update.py @@ -0,0 +1,46 @@ +"""Represents a structured update model for STAC collections. + +Allows partial updates where only the provided fields are modified. +""" +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel, Field +from pystac import Extent, Link, Provider, Summaries + + +class CollectionUpdate(BaseModel): + """Represents a structured update model for STAC collections. + + Allows partial updates where only the provided fields are modified. + """ + + model_config = {"arbitrary_types_allowed": True} + + title: Optional[str] = Field(None, description="Title of the STAC collection.") + description: Optional[str] = Field( + None, description="Description of the collection." + ) + keywords: Optional[List[str]] = Field( + None, description="List of keywords associated with the collection." + ) + license: Optional[str] = Field(None, description="Collection license information.") + providers: Optional[List[Provider]] = Field( + None, description="List of data providers." + ) + extent: Optional[Extent] = Field( + None, description="Spatial and temporal extent of the collection." + ) + summaries: Optional[Summaries] = Field( + None, description="Summaries for the collection." + ) + links: Optional[List[Link]] = Field( + None, description="List of links associated with the collection." + ) + + def to_dict(self) -> Dict[str, Any]: + """Convert the model into a dictionary, excluding `None` values. + + Returns: + Dict[str, Any]: Dictionary representation of the update payload. + """ + return self.model_dump(by_alias=True, exclude_none=True) diff --git a/datacosmos/stac/item/item_client.py b/datacosmos/stac/item/item_client.py index a8f8426..6d0dbb8 100644 --- a/datacosmos/stac/item/item_client.py +++ b/datacosmos/stac/item/item_client.py @@ -24,7 +24,7 @@ class ItemClient: """Client for interacting with the STAC API.""" def __init__(self, client: DatacosmosClient): - """Initialize the STACClient with a DatacosmosClient. + """Initialize the ItemClient with a DatacosmosClient. Args: client (DatacosmosClient): The authenticated Datacosmos client instance. diff --git a/datacosmos/stac/item/models/__init__.py b/datacosmos/stac/item/models/__init__.py new file mode 100644 index 0000000..1a5b17b --- /dev/null +++ b/datacosmos/stac/item/models/__init__.py @@ -0,0 +1 @@ +"""Models for the Item Client.""" diff --git a/datacosmos/stac/stac_client.py b/datacosmos/stac/stac_client.py new file mode 100644 index 0000000..fad514f --- /dev/null +++ b/datacosmos/stac/stac_client.py @@ -0,0 +1,12 @@ +"""Unified interface for STAC API, combining Item & Collection operations.""" + +from datacosmos.stac.collection.collection_client import CollectionClient +from datacosmos.stac.item.item_client import ItemClient + + +class STACClient(ItemClient, CollectionClient): + """Unified interface for STAC API, combining Item & Collection operations.""" + + def __init__(self, client): + """Initialize the STACClient with a DatacosmosClient.""" + super().__init__(client) From 9fb35f2f691aadfea051305e4bea114ee2141a7a Mon Sep 17 00:00:00 2001 From: "tiago.peres.sousa" Date: Wed, 12 Feb 2025 12:43:11 +0000 Subject: [PATCH 3/7] Add unit tests for the CollectionClient --- .../test_create_collection.py | 63 ++++++++++++++++ .../test_delete_collection.py | 44 +++++++++++ .../test_fetch_collection.py | 73 +++++++++++++++++++ .../test_update_collection.py | 54 ++++++++++++++ 4 files changed, 234 insertions(+) create mode 100644 tests/unit/datacosmos/stac/collection/collection_client/test_create_collection.py create mode 100644 tests/unit/datacosmos/stac/collection/collection_client/test_delete_collection.py create mode 100644 tests/unit/datacosmos/stac/collection/collection_client/test_fetch_collection.py create mode 100644 tests/unit/datacosmos/stac/collection/collection_client/test_update_collection.py diff --git a/tests/unit/datacosmos/stac/collection/collection_client/test_create_collection.py b/tests/unit/datacosmos/stac/collection/collection_client/test_create_collection.py new file mode 100644 index 0000000..fb7caae --- /dev/null +++ b/tests/unit/datacosmos/stac/collection/collection_client/test_create_collection.py @@ -0,0 +1,63 @@ +from unittest.mock import MagicMock, patch + +from pystac import Collection, Extent, SpatialExtent, TemporalExtent +from pystac.utils import str_to_datetime + +from config.config import Config +from config.models.m2m_authentication_config import M2MAuthenticationConfig +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.collection.collection_client import CollectionClient + + +@patch("requests_oauthlib.OAuth2Session.fetch_token") +@patch.object(DatacosmosClient, "post") +@patch("datacosmos.stac.collection.collection_client.check_api_response") +def test_create_collection(mock_check_api_response, mock_post, mock_fetch_token): + """Test creating a STAC collection.""" + + mock_fetch_token.return_value = {"access_token": "mock-token", "expires_in": 3600} + + mock_response = MagicMock() + mock_response.status_code = 200 + mock_post.return_value = mock_response + mock_check_api_response.return_value = None + + collection = Collection( + id="test-collection", + description="A test STAC collection", + extent=Extent( + SpatialExtent([[-180.0, -90.0, 180.0, 90.0]]), + TemporalExtent( + [ + [ + str_to_datetime("2020-01-01T00:00:00Z"), + str_to_datetime("2023-12-31T23:59:59Z"), + ] + ] + ), + ), + license="proprietary", + stac_extensions=[], + ) + + config = Config( + authentication=M2MAuthenticationConfig( + type="m2m", + client_id="test-client-id", + client_secret="test-client-secret", + token_url="https://mock.token.url/oauth/token", + audience="https://mock.audience", + ) + ) + + client = DatacosmosClient(config=config) + collection_client = CollectionClient(client) + + collection_client.create_collection(collection) + + mock_post.assert_called_once_with( + client.config.stac.as_domain_url().with_suffix("/collections"), + json=collection.to_dict(), + ) + + mock_check_api_response.assert_called_once_with(mock_response) diff --git a/tests/unit/datacosmos/stac/collection/collection_client/test_delete_collection.py b/tests/unit/datacosmos/stac/collection/collection_client/test_delete_collection.py new file mode 100644 index 0000000..9f7c93e --- /dev/null +++ b/tests/unit/datacosmos/stac/collection/collection_client/test_delete_collection.py @@ -0,0 +1,44 @@ +from unittest.mock import MagicMock, patch + +from config.config import Config +from config.models.m2m_authentication_config import M2MAuthenticationConfig +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.collection.collection_client import CollectionClient + + +@patch("requests_oauthlib.OAuth2Session.fetch_token") +@patch.object(DatacosmosClient, "delete") +@patch("datacosmos.stac.collection.collection_client.check_api_response") +def test_delete_collection(mock_check_api_response, mock_delete, mock_fetch_token): + """Test deleting a STAC collection.""" + + mock_fetch_token.return_value = {"access_token": "mock-token", "expires_in": 3600} + + mock_response = MagicMock() + mock_response.status_code = 204 + mock_delete.return_value = mock_response + mock_check_api_response.return_value = None + + config = Config( + authentication=M2MAuthenticationConfig( + type="m2m", + client_id="test-client-id", + client_secret="test-client-secret", + token_url="https://mock.token.url/oauth/token", + audience="https://mock.audience", + ) + ) + + client = DatacosmosClient(config=config) + collection_client = CollectionClient(client) + + collection_client.delete_collection("test-collection") + + mock_delete.assert_called_once_with( + client.config.stac.as_domain_url().with_suffix("/collections/test-collection") + ) + + mock_check_api_response.assert_called_once_with(mock_response) + mock_delete.assert_called_with( + collection_client.base_url.with_suffix("/collections/test-collection") + ) diff --git a/tests/unit/datacosmos/stac/collection/collection_client/test_fetch_collection.py b/tests/unit/datacosmos/stac/collection/collection_client/test_fetch_collection.py new file mode 100644 index 0000000..3daa667 --- /dev/null +++ b/tests/unit/datacosmos/stac/collection/collection_client/test_fetch_collection.py @@ -0,0 +1,73 @@ +from unittest.mock import MagicMock, patch + +from pystac import Collection +from pystac.utils import datetime_to_str + +from config.config import Config +from config.models.m2m_authentication_config import M2MAuthenticationConfig +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.collection.collection_client import CollectionClient + + +@patch("requests_oauthlib.OAuth2Session.fetch_token") +@patch.object(DatacosmosClient, "get") +@patch("datacosmos.stac.collection.collection_client.check_api_response") +def test_fetch_collection(mock_check_api_response, mock_get, mock_fetch_token): + """Test fetching a single STAC collection by ID.""" + + mock_fetch_token.return_value = {"access_token": "mock-token", "expires_in": 3600} + + mock_response = MagicMock() + mock_response.json.return_value = { + "type": "Collection", + "id": "test-collection", + "stac_version": "1.1.0", + "description": "A test STAC collection", + "license": "proprietary", + "extent": { + "spatial": {"bbox": [[-180.0, -90.0, 180.0, 90.0]]}, + "temporal": { + "interval": [["2020-01-01T00:00:00Z", "2023-12-31T23:59:59Z"]] + }, + }, + "links": [], + "stac_extensions": [], + } + mock_response.status_code = 200 + mock_get.return_value = mock_response + mock_check_api_response.return_value = None + + config = Config( + authentication=M2MAuthenticationConfig( + type="m2m", + client_id="test-client-id", + client_secret="test-client-secret", + token_url="https://mock.token.url/oauth/token", + audience="https://mock.audience", + ) + ) + + client = DatacosmosClient(config=config) + collection_client = CollectionClient(client) + + collection = collection_client.fetch_collection("test-collection") + + assert isinstance(collection, Collection) + assert collection.id == "test-collection" + assert collection.description == "A test STAC collection" + assert collection.license == "proprietary" + assert collection.extent.spatial.bboxes == [[-180.0, -90.0, 180.0, 90.0]] + + actual_temporal_intervals = [ + [datetime_to_str(interval[0]), datetime_to_str(interval[1])] + for interval in collection.extent.temporal.intervals + ] + expected_temporal_intervals = [["2020-01-01T00:00:00Z", "2023-12-31T23:59:59Z"]] + + assert actual_temporal_intervals == expected_temporal_intervals + + mock_get.assert_called_once() + mock_check_api_response.assert_called_once_with(mock_response) + mock_get.assert_called_with( + collection_client.base_url.with_suffix("/collections/test-collection") + ) diff --git a/tests/unit/datacosmos/stac/collection/collection_client/test_update_collection.py b/tests/unit/datacosmos/stac/collection/collection_client/test_update_collection.py new file mode 100644 index 0000000..3994620 --- /dev/null +++ b/tests/unit/datacosmos/stac/collection/collection_client/test_update_collection.py @@ -0,0 +1,54 @@ +from unittest.mock import MagicMock, patch + +from config.config import Config +from config.models.m2m_authentication_config import M2MAuthenticationConfig +from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.stac.collection.collection_client import CollectionClient +from datacosmos.stac.collection.models.collection_update import CollectionUpdate + + +@patch("requests_oauthlib.OAuth2Session.fetch_token") +@patch.object(DatacosmosClient, "patch") +@patch("datacosmos.stac.collection.collection_client.check_api_response") +def test_update_collection(mock_check_api_response, mock_patch, mock_fetch_token): + """Test updating a STAC collection.""" + + mock_fetch_token.return_value = {"access_token": "mock-token", "expires_in": 3600} + + mock_response = MagicMock() + mock_response.status_code = 200 + mock_patch.return_value = mock_response + mock_check_api_response.return_value = None + + update_data = CollectionUpdate( + title="Updated Collection Title", + description="Updated description", + keywords=["updated", "collection"], + license="proprietary", + ) + + config = Config( + authentication=M2MAuthenticationConfig( + type="m2m", + client_id="test-client-id", + client_secret="test-client-secret", + token_url="https://mock.token.url/oauth/token", + audience="https://mock.audience", + ) + ) + + client = DatacosmosClient(config=config) + collection_client = CollectionClient(client) + + collection_client.update_collection("test-collection", update_data) + + mock_patch.assert_called_once_with( + client.config.stac.as_domain_url().with_suffix("/collections/test-collection"), + json=update_data.model_dump(by_alias=True, exclude_none=True), + ) + + mock_check_api_response.assert_called_once_with(mock_response) + mock_patch.assert_called_with( + collection_client.base_url.with_suffix("/collections/test-collection"), + json=update_data.model_dump(by_alias=True, exclude_none=True), + ) From 508d8e2034dbd9f2823a5530b045efaf4a4890f6 Mon Sep 17 00:00:00 2001 From: "tiago.peres.sousa" Date: Fri, 14 Feb 2025 10:31:47 +0000 Subject: [PATCH 4/7] rebase --- README.md | 2 +- datacosmos/stac/collection/collection_client.py | 2 +- datacosmos/stac/item/item_client.py | 12 +++--------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3341326..c641097 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ pip install datacosmos The recommended way to initialize the SDK is by passing a `Config` object with authentication credentials: ```python -from datacosmos.client import DatacosmosClient +from datacosmos.datacosmos_client import DatacosmosClient from datacosmos.config import Config config = Config( diff --git a/datacosmos/stac/collection/collection_client.py b/datacosmos/stac/collection/collection_client.py index e9396e0..013ec27 100644 --- a/datacosmos/stac/collection/collection_client.py +++ b/datacosmos/stac/collection/collection_client.py @@ -2,12 +2,12 @@ from typing import Generator, Optional -from common.sdk.http_response import check_api_response from pystac import Collection, Extent, SpatialExtent, TemporalExtent from pystac.utils import str_to_datetime from datacosmos.datacosmos_client import DatacosmosClient from datacosmos.stac.collection.models.collection_update import CollectionUpdate +from datacosmos.utils.http_response import check_api_response class CollectionClient: diff --git a/datacosmos/stac/item/item_client.py b/datacosmos/stac/item/item_client.py index 6d0dbb8..fc991e4 100644 --- a/datacosmos/stac/item/item_client.py +++ b/datacosmos/stac/item/item_client.py @@ -7,24 +7,18 @@ from pystac import Item -<<<<<<< HEAD:datacosmos/stac/stac_client.py -from datacosmos.client import DatacosmosClient -from datacosmos.exceptions.datacosmos_exception import DatacosmosException -from datacosmos.stac.models.item_update import ItemUpdate -from datacosmos.stac.models.search_parameters import SearchParameters -from datacosmos.utils.http_response import check_api_response -======= from datacosmos.datacosmos_client import DatacosmosClient +from datacosmos.exceptions.datacosmos_exception import DatacosmosException from datacosmos.stac.item.models.item_update import ItemUpdate from datacosmos.stac.item.models.search_parameters import SearchParameters ->>>>>>> 77a15fc (Rename client with datacosmos_client; Move current stac api functionalities to a item dedicated folder):datacosmos/stac/item/item_client.py +from datacosmos.utils.http_response import check_api_response class ItemClient: """Client for interacting with the STAC API.""" def __init__(self, client: DatacosmosClient): - """Initialize the ItemClient with a DatacosmosClient. + """Initialize the STACClient with a DatacosmosClient. Args: client (DatacosmosClient): The authenticated Datacosmos client instance. From be4c9c392e171b6dc69e42885bb1e2e0b807fbd9 Mon Sep 17 00:00:00 2001 From: "tiago.peres.sousa" Date: Fri, 14 Feb 2025 10:40:59 +0000 Subject: [PATCH 5/7] Update readme file with the new methods --- README.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c641097..8b343c0 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # DataCosmos SDK ## Overview + 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). ## Installation ### Install via PyPI + The easiest way to install the SDK is via **pip**: ```sh @@ -15,6 +17,7 @@ pip install datacosmos ## Getting Started ### Initializing the Client + The recommended way to initialize the SDK is by passing a `Config` object with authentication credentials: ```python @@ -33,11 +36,13 @@ client = DatacosmosClient(config=config) ``` Alternatively, the SDK can load configuration automatically from: + - A YAML file (`config/config.yaml`) - Environment variables ### STAC Client -The **STACClient** enables interaction with the STAC API, allowing for searching, retrieving, creating, updating, and deleting STAC items. + +The STACClient enables interaction with the STAC API, allowing for searching, retrieving, creating, updating, and deleting STAC items and collections. #### Initialize STACClient @@ -49,7 +54,65 @@ stac_client = STACClient(client) ### STACClient Methods +#### 1. **Fetch a Collection** + +```python +from datacosmos.stac.stac_client import STACClient +from datacosmos.datacosmos_client import DatacosmosClient + +datacosmos_client = DatacosmosClient() +stac_client = STACClient(datacosmos_client) + +collection = stac_client.fetch_collection("test-collection") +``` + +#### 2. **Fetch All Collections** + +```python +collections = list(stac_client.fetch_all_collections()) +``` + +#### 3. **Create a Collection** + +```python +from pystac import Collection + +new_collection = Collection( + id="test-collection", + title="Test Collection", + description="This is a test collection", + license="proprietary", + extent={ + "spatial": {"bbox": [[-180, -90, 180, 90]]}, + "temporal": {"interval": [["2023-01-01T00:00:00Z", None]]}, + }, +) + +stac_client.create_collection(new_collection) +``` + +#### 4. **Update a Collection** + +```python +from datacosmos.stac.collection.models.collection_update import CollectionUpdate + +update_data = CollectionUpdate( + title="Updated Collection Title version 2", + description="Updated description version 2", +) + +stac_client.update_collection("test-collection", update_data) +``` + +#### 5. **Delete a Collection** + +```python +collection_id = "test-collection" +stac_client.delete_collection(collection_id) +``` + #### 1. **Search Items** + ```python from datacosmos.stac.models.search_parameters import SearchParameters @@ -58,16 +121,19 @@ items = list(stac_client.search_items(parameters=parameters)) ``` #### 2. **Fetch a Single Item** + ```python item = stac_client.fetch_item(item_id="example-item", collection_id="example-collection") ``` #### 3. **Fetch All Items in a Collection** + ```python items = stac_client.fetch_collection_items(collection_id="example-collection") ``` #### 4. **Create a New STAC Item** + ```python from pystac import Item, Asset from datetime import datetime @@ -95,6 +161,7 @@ stac_client.create_item(collection_id="example-collection", item=stac_item) ``` #### 5. **Update an Existing STAC Item** + ```python from datacosmos.stac.models.item_update import ItemUpdate from pystac import Asset, Link @@ -124,22 +191,27 @@ stac_client.update_item(item_id="new-item", collection_id="example-collection", ``` #### 6. **Delete an Item** + ```python stac_client.delete_item(item_id="new-item", collection_id="example-collection") ``` ## Configuration Options + - **Recommended:** Instantiate `DatacosmosClient` with a `Config` object. - Alternatively, use **YAML files** (`config/config.yaml`). - Or, use **environment variables**. ## Contributing + If you would like to contribute: + 1. Fork the repository. 2. Create a feature branch. 3. Submit a pull request. ### Development Setup + If you are developing the SDK, you can use `uv` for dependency management: ```sh @@ -151,6 +223,7 @@ source .venv/bin/activate ``` Before making changes, ensure that: + - The code is formatted using **Black** and **isort**. - Static analysis and linting are performed using **ruff** and **pydocstyle**. - Security checks are performed using **bandit**. From 9669f05f928d4630e76235767589539aaf8c9ef6 Mon Sep 17 00:00:00 2001 From: "tiago.peres.sousa" Date: Fri, 14 Feb 2025 11:30:56 +0000 Subject: [PATCH 6/7] Remove shapely from dependencies --- .github/workflows/main.yaml | 12 ------------ datacosmos/stac/item/models/item_update.py | 12 ++++++++---- pyproject.toml | 3 +-- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index a2b4edb..0ae32d6 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -14,8 +14,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Install GEOS - run: sudo apt update && sudo apt install libgeos-dev - name: Install uv run: pip install uv - name: Set up uv environment @@ -33,8 +31,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Install GEOS - run: sudo apt update && sudo apt install libgeos-dev - name: Install uv run: pip install uv - name: Set up uv environment @@ -51,8 +47,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Install GEOS - run: sudo apt update && sudo apt install libgeos-dev - name: Install uv run: pip install uv - name: Set up uv environment @@ -69,8 +63,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Install GEOS - run: sudo apt update && sudo apt install libgeos-dev - name: Install uv run: pip install uv - name: Set up uv environment @@ -88,8 +80,6 @@ jobs: needs: [bandit, cognitive, lint, pydocstyle] steps: - uses: actions/checkout@v3 - - name: Install GEOS - run: sudo apt update && sudo apt install libgeos-dev - name: Install uv run: pip install uv - name: Set up uv environment @@ -108,8 +98,6 @@ jobs: if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 - - name: Install GEOS - run: sudo apt update && sudo apt install libgeos-dev - name: Install uv run: pip install uv - name: Set up uv environment diff --git a/datacosmos/stac/item/models/item_update.py b/datacosmos/stac/item/models/item_update.py index 338df78..3fbf8cf 100644 --- a/datacosmos/stac/item/models/item_update.py +++ b/datacosmos/stac/item/models/item_update.py @@ -4,7 +4,6 @@ from pydantic import BaseModel, Field, model_validator from pystac import Asset, Link -from shapely.geometry import mapping class ItemUpdate(BaseModel): @@ -21,9 +20,14 @@ class ItemUpdate(BaseModel): assets: Optional[dict[str, Asset]] = None links: Optional[list[Link]] = None - def set_geometry(self, geom) -> None: - """Convert a shapely geometry to GeoJSON format.""" - self.geometry = mapping(geom) + def set_geometry(self, geom_type: str, coordinates: list[Any]) -> None: + """Set the geometry manually without using shapely. + + Args: + geom_type (str): The type of geometry (e.g., 'Point', 'Polygon'). + coordinates (list[Any]): The coordinates defining the geometry. + """ + self.geometry = {"type": geom_type, "coordinates": coordinates} @staticmethod def has_valid_datetime(properties: dict[str, Any]) -> bool: diff --git a/pyproject.toml b/pyproject.toml index f36d303..ab723d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,8 +20,7 @@ dependencies = [ "oauthlib==3.2.0", "requests-oauthlib==1.3.1", "pydantic==2.10.6", - "pystac==1.12.1", - "shapely==1.8.0" + "pystac==1.12.1" ] [project.optional-dependencies] From 4e76dfd21ef9433d17e964e0c806b6cb484a1319 Mon Sep 17 00:00:00 2001 From: "tiago.peres.sousa" Date: Fri, 14 Feb 2025 14:59:30 +0000 Subject: [PATCH 7/7] update uv.lock --- uv.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/uv.lock b/uv.lock index f660384..34c641c 100644 --- a/uv.lock +++ b/uv.lock @@ -32,8 +32,6 @@ requests==2.31.0 # requests-oauthlib requests-oauthlib==1.3.1 # via datacosmos (pyproject.toml) -shapely==1.8.0 - # via datacosmos (pyproject.toml) six==1.17.0 # via python-dateutil typing-extensions==4.12.2