diff --git a/libs/unity-py/CHANGELOG.md b/libs/unity-py/CHANGELOG.md index 6a245713..9376c28e 100644 --- a/libs/unity-py/CHANGELOG.md +++ b/libs/unity-py/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.10.1] - 2025-03-04 + +### Added + +### Fixed + +* Do not fail on case insensitive portion of collection id in DataService::create_collection routine +* Fixed error handling of DataService routines to use .text instead of the invalid .message attrbute of the response object +* Cleaned up JSON submitted by DataService::create_collection to not contain less random values + +### Changed + +* Enforce a specific capitalization for the case insensitive portion of a collection id in DataService::create_collection +* DataService::create_collection now returns the JSON it submitted to the API + ## [0.10.0] - 2025-02-19 ### Added diff --git a/libs/unity-py/pyproject.toml b/libs/unity-py/pyproject.toml index 9099216e..be96f0df 100644 --- a/libs/unity-py/pyproject.toml +++ b/libs/unity-py/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "unity-sds-client" -version = "0.10.0" +version = "0.10.1" description = "Unity-Py is a Python client to simplify interactions with NASA's Unity Platform." authors = ["Anil Natha, Mike Gangl"] diff --git a/libs/unity-py/unity_sds_client/services/data_service.py b/libs/unity-py/unity_sds_client/services/data_service.py index 95aa91ae..2cffbbed 100644 --- a/libs/unity-py/unity_sds_client/services/data_service.py +++ b/libs/unity-py/unity_sds_client/services/data_service.py @@ -1,11 +1,16 @@ +import re import requests +from datetime import datetime, timezone + from unity_sds_client.unity_exception import UnityException from unity_sds_client.unity_session import UnitySession from unity_sds_client.resources.collection import Collection from unity_sds_client.resources.dataset import Dataset from unity_sds_client.resources.data_file import DataFile +# All capitals to match the unity-dataservices stage-out convention +UNITY_COLLECTION_INVARIANT_PREFIX = "URN:NASA:UNITY" class DataService(object): """ @@ -95,50 +100,33 @@ def create_collection(self, collection: type = Collection, dry_run=False): if collection is None: raise UnityException("Invalid collection provided.") - # test version Information? - # Test collection ID name: project and venue if self._session._project is None or self._session._venue is None: raise UnityException("To create a collection, the Unity session Project and Venue must be set!") - if not collection.collection_id.startswith(f"urn:nasa:unity:{self._session._project}:{self._session._venue}"): - raise UnityException(f"Collection Identifiers must start with urn:nasa:unity:{self._session._project}:{self._session._venue}") + # Enusure the collection ID contains a prefix that conforms to expectations, testing in a case insensitive manner + # But promoting to the preferred case + submission_collection_id = collection.collection_id + + if not re.search(rf'^{UNITY_COLLECTION_INVARIANT_PREFIX}', submission_collection_id, re.IGNORECASE): + raise UnityException(f"Collection Identifiers must start with {UNITY_COLLECTION_INVARIANT_PREFIX}") - collection = { - "title": "Collection " + collection.collection_id, + # Make the prefix conform to expected formatting (case) to ensure consistency across services + submission_collection_id = re.sub(rf'^{UNITY_COLLECTION_INVARIANT_PREFIX}', UNITY_COLLECTION_INVARIANT_PREFIX, submission_collection_id, flags=re.IGNORECASE) + + if not submission_collection_id.startswith(f"{UNITY_COLLECTION_INVARIANT_PREFIX}:{self._session._project}:{self._session._venue}"): + raise UnityException(f"Collection Identifiers must start with {UNITY_COLLECTION_INVARIANT_PREFIX}:{self._session._project}:{self._session._venue}") + + collection_json = { + "title": "Collection " + submission_collection_id, "type": "Collection", - "id": collection.collection_id, + "id": submission_collection_id, "stac_version": "1.0.0", - "description": "TODO", + "description": "Collection " + submission_collection_id, "providers": [ {"name": "unity"} ], - "links": [ - { - "rel": "root", - "href": "./collection.json?bucket=unknown_bucket®ex=%7BcmrMetadata.Granule.Collection.ShortName%7D___%7BcmrMetadata.Granule.Collection.VersionId%7D", - "type": "application/json", - "title": "test_file01.nc" - }, - { - "rel": "item", - "href": "./collection.json?bucket=protected®ex=%5Etest_file.%2A%5C.nc%24", - "type": "data", - "title": "test_file01.nc" - }, - { - "rel": "item", - "href": "./collection.json?bucket=protected®ex=%5Etest_file.%2A%5C.nc%5C.cas%24", - "type": "metadata", - "title": "test_file01.nc.cas" - }, - { - "rel": "item", - "href": "./collection.json?bucket=private®ex=%5Etest_file.%2A%5C.cmr%5C.xml%24", - "type": "metadata", - "title": "test_file01.cmr.xml" - } - ], + "links": [], "stac_extensions": [], "extent": { "spatial": { @@ -154,8 +142,8 @@ def create_collection(self, collection: type = Collection, dry_run=False): "temporal": { "interval": [ [ - "2022-10-04T00:00:00.000Z", - "2022-10-04T23:59:59.999Z" + datetime.now(timezone.utc).isoformat(), + datetime.now(timezone.utc).isoformat() ] ] } @@ -176,9 +164,12 @@ def create_collection(self, collection: type = Collection, dry_run=False): if not dry_run: url = self.endpoint + f'am-uds-dapa/collections' token = self._session.get_auth().get_token() - response = requests.post(url, headers={"Authorization": "Bearer " + token}, json=collection) + response = requests.post(url, headers={"Authorization": "Bearer " + token}, json=collection_json) + if response.status_code != 202: - raise UnityException("Error creating collection: " + response.message) + raise UnityException(f"Error creating collection: " + response.text) + + return collection_json def define_custom_metadata(self, metadata: dict): if self._session._project is None or self._session._venue is None: @@ -189,7 +180,7 @@ def define_custom_metadata(self, metadata: dict): response = requests.put(url, headers={"Authorization": "Bearer " + token}, params={"venue": self._session._venue}, json=metadata) if response.status_code != 200: - raise UnityException("Error adding custom metadata: " + response.message) + raise UnityException("Error adding custom metadata: " + response.text) def delete_collection_item(self, collection: type = Collection, granule_id: str = None): """