From 35488e188af85dfd279e467573472b32987e9d63 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Wed, 14 Jun 2023 11:50:38 -0400 Subject: [PATCH 1/9] Add XarrayAssetsExtension class --- pystac/extensions/xarray_assets.py | 172 +++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 pystac/extensions/xarray_assets.py diff --git a/pystac/extensions/xarray_assets.py b/pystac/extensions/xarray_assets.py new file mode 100644 index 000000000..6f31d25f6 --- /dev/null +++ b/pystac/extensions/xarray_assets.py @@ -0,0 +1,172 @@ +"""Implements the :stac-ext:`Xarray Assets Extension `.""" + +from __future__ import annotations + +from typing import Any, Dict, Generic, List, Optional, TypeVar, Union, cast + +import pystac +from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension +from pystac.extensions.hooks import ExtensionHooks + +T = TypeVar("T", pystac.Collection, pystac.Item, pystac.Asset) + +SCHEMA_URI = "https://stac-extensions.github.io/xarray-assets/v1.0.0/schema.json" + +PREFIX: str = "xarray:" +OPEN_KWARGS_PROP = PREFIX + "open_kwargs" +STORAGE_OPTIONS_PROP = PREFIX + "storage_options" + + +class XarrayAssetsExtension( + Generic[T], + PropertiesExtension, + ExtensionManagementMixin[Union[pystac.Collection, pystac.Item]], +): + """An abstract class that can be used to extend the properties of a + :class:`~pystac.Collection`, :class:`~pystac.Item`, or :class:`~pystac.Asset` with + properties from the :stac-ext:`Datacube Extension `. This class is + generic over the type of STAC Object to be extended (e.g. :class:`~pystac.Item`, + :class:`~pystac.Asset`). + + To create a concrete instance of :class:`XarrayAssetsExtension`, use the + :meth:`XarrayAssetsExtension.ext` method. For example: + + .. code-block:: python + + >>> item: pystac.Item = ... + >>> xr_ext = XarrayAssetsExtension.ext(item) + + """ + + @classmethod + def get_schema_uri(cls) -> str: + return SCHEMA_URI + + @classmethod + def ext(cls, obj: T, add_if_missing: bool = False) -> XarrayAssetsExtension[T]: + """Extend the given STAC Object with properties from the + :stac-ext:`XarrayAssets Extension `. + + This extension can be applied to instances of :class:`~pystac.Collection`, + :class:`~pystac.Item` or :class:`~pystac.Asset`. + + Raises: + pystac.ExtensionTypeError : If an invalid object type is passed. + """ + if isinstance(obj, pystac.Collection): + cls.validate_has_extension(obj, add_if_missing) + return cast(XarrayAssetsExtension[T], CollectionXarrayAssetsExtension(obj)) + if isinstance(obj, pystac.Item): + cls.validate_has_extension(obj, add_if_missing) + return cast(XarrayAssetsExtension[T], ItemXarrayAssetsExtension(obj)) + if isinstance(obj, pystac.Asset): + cls.validate_owner_has_extension(obj, add_if_missing) + return cast(XarrayAssetsExtension[T], AssetXarrayAssetsExtension(obj)) + else: + raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) + + +class CollectionXarrayAssetsExtension(XarrayAssetsExtension[pystac.Collection]): + """A concrete implementation of :class:`XarrayAssetsExtension` on a + :class:`~pystac.Collection` that extends the properties of the Item to include + properties defined in the :stac-ext:`XarrayAssets Extension `. + + This class should generally not be instantiated directly. Instead, call + :meth:`XarrayAssetsExtension.ext` on an :class:`~pystac.Collection` to extend it. + """ + + collection: pystac.Collection + properties: Dict[str, Any] + + def __init__(self, collection: pystac.Collection): + self.collection = collection + self.properties = collection.extra_fields + + def __repr__(self) -> str: + return "".format(self.collection.id) + + +class ItemXarrayAssetsExtension(XarrayAssetsExtension[pystac.Item]): + """A concrete implementation of :class:`XarrayAssetsExtension` on an + :class:`~pystac.Item` that extends the properties of the Item to include properties + defined in the :stac-ext:`XarrayAssets Extension `. + + This class should generally not be instantiated directly. Instead, call + :meth:`XarrayAssetsExtension.ext` on an :class:`~pystac.Item` to extend it. + """ + + item: pystac.Item + properties: Dict[str, Any] + + def __init__(self, item: pystac.Item): + self.item = item + self.properties = item.properties + + def __repr__(self) -> str: + return "".format(self.item.id) + + +class AssetXarrayAssetsExtension(XarrayAssetsExtension[pystac.Asset]): + """A concrete implementation of :class:`XarrayAssetsExtension` on an + :class:`~pystac.Asset` that extends the Asset fields to include properties defined + in the :stac-ext:`XarrayAssets Extension `. + + This class should generally not be instantiated directly. Instead, call + :meth:`XarrayAssetsExtension.ext` on an :class:`~pystac.Asset` to extend it. + """ + + asset_href: str + properties: Dict[str, Any] + additional_read_properties: Optional[List[Dict[str, Any]]] + + def __init__(self, asset: pystac.Asset): + self.asset_href = asset.href + self.properties = asset.extra_fields + if asset.owner and isinstance(asset.owner, pystac.Item): + self.additional_read_properties = [asset.owner.properties] + else: + self.additional_read_properties = None + + @property + def storage_options(self) -> Optional[Dict[str, Any]]: + """Additional keywords for accessing the dataset from remote storage""" + return self.properties.get(STORAGE_OPTIONS_PROP) + + @storage_options.setter + def storage_options(self, v: Optional[Dict[str, Any]]) -> Any: + if v is None: + self.properties.pop(STORAGE_OPTIONS_PROP, None) + else: + self.properties[STORAGE_OPTIONS_PROP] = v + + @property + def open_kwargs(self) -> Optional[Dict[str, Any]]: + """Additional keywords for opening the dataset""" + return self.properties.get(OPEN_KWARGS_PROP) + + @open_kwargs.setter + def open_kwargs(self, v: Optional[Dict[str, Any]]) -> Any: + if v is None: + self.properties.pop(OPEN_KWARGS_PROP, None) + else: + self.properties[OPEN_KWARGS_PROP] = v + + def __repr__(self) -> str: + return "".format(self.asset_href) + + def open(self, **kwargs: Any) -> Any: + try: + from xpystac.core import to_xarray + except ImportError: + raise ImportError("Missing optional dependency `xpystac`") + + return to_xarray(self, **kwargs) + + +class XarrayAssetsExtensionHooks(ExtensionHooks): + schema_uri: str = SCHEMA_URI + prev_extension_ids = {"xarray"} + stac_object_types = {pystac.STACObjectType.COLLECTION, pystac.STACObjectType.ITEM} + + +TABLE_EXTENSION_HOOKS: ExtensionHooks = XarrayAssetsExtensionHooks() From d73f50640aff642809d11000f646d151dcd171c4 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Wed, 14 Jun 2023 13:32:56 -0400 Subject: [PATCH 2/9] Add tests and docs --- docs/api.rst | 1 + docs/api/extensions.rst | 1 + docs/api/extensions/xarray_assets.rst | 6 + pystac/__init__.py | 2 + pystac/extensions/xarray_assets.py | 2 +- .../data-files/xarray-assets/collection.json | 61 +++++++ tests/data-files/xarray-assets/item.json | 56 +++++++ tests/extensions/test_xarray_assets.py | 155 ++++++++++++++++++ 8 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 docs/api/extensions/xarray_assets.rst create mode 100644 tests/data-files/xarray-assets/collection.json create mode 100644 tests/data-files/xarray-assets/item.json create mode 100644 tests/extensions/test_xarray_assets.py diff --git a/docs/api.rst b/docs/api.rst index 604000928..1e646cee0 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -107,6 +107,7 @@ PySTAC provides support for the following STAC Extensions: * :mod:`Timestamps ` * :mod:`Versioning Indicators ` * :mod:`View Geometry ` +* :mod:`Xarray Assets ` The following classes are used internally to implement these extensions and may be used to create custom implementations of STAC Extensions not supported by the library (see diff --git a/docs/api/extensions.rst b/docs/api/extensions.rst index a45a709f8..9b81196ec 100644 --- a/docs/api/extensions.rst +++ b/docs/api/extensions.rst @@ -32,3 +32,4 @@ pystac.extensions timestamps.TimestampsExtension version.VersionExtension view.ViewExtension + xarray_assets.XarrayAssetsExtension diff --git a/docs/api/extensions/xarray_assets.rst b/docs/api/extensions/xarray_assets.rst new file mode 100644 index 000000000..494093068 --- /dev/null +++ b/docs/api/extensions/xarray_assets.rst @@ -0,0 +1,6 @@ +pystac.extensions.xarray_assets +=============================== + +.. automodule:: pystac.extensions.xarray_assets + :members: + :undoc-members: diff --git a/pystac/__init__.py b/pystac/__init__.py index 6e96a83d3..6f3036811 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -104,6 +104,7 @@ import pystac.extensions.timestamps import pystac.extensions.version import pystac.extensions.view +import pystac.extensions.xarray_assets EXTENSION_HOOKS = pystac.extensions.hooks.RegisteredExtensionHooks( [ @@ -126,6 +127,7 @@ pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_HOOKS, pystac.extensions.version.VERSION_EXTENSION_HOOKS, pystac.extensions.view.VIEW_EXTENSION_HOOKS, + pystac.extensions.xarray_assets.XARRAY_ASSETS_EXTENSION_HOOKS, ] ) diff --git a/pystac/extensions/xarray_assets.py b/pystac/extensions/xarray_assets.py index 6f31d25f6..749637cbd 100644 --- a/pystac/extensions/xarray_assets.py +++ b/pystac/extensions/xarray_assets.py @@ -169,4 +169,4 @@ class XarrayAssetsExtensionHooks(ExtensionHooks): stac_object_types = {pystac.STACObjectType.COLLECTION, pystac.STACObjectType.ITEM} -TABLE_EXTENSION_HOOKS: ExtensionHooks = XarrayAssetsExtensionHooks() +XARRAY_ASSETS_EXTENSION_HOOKS: ExtensionHooks = XarrayAssetsExtensionHooks() diff --git a/tests/data-files/xarray-assets/collection.json b/tests/data-files/xarray-assets/collection.json new file mode 100644 index 000000000..58d6dd31e --- /dev/null +++ b/tests/data-files/xarray-assets/collection.json @@ -0,0 +1,61 @@ +{ + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/xarray-assets/v1.0.0/schema.json" + ], + "type": "Collection", + "id": "collection", + "title": "A title", + "description": "A description", + "license": "Apache-2.0", + "extent": { + "spatial": { + "bbox": [ + [ + 172.9, + 1.3, + 173, + 1.4 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2015-06-23T00:00:00Z", + null + ] + ] + } + }, + "assets": { + "example": { + "href": "abfs://cpdata/raw/terraclimate/4000m/raster.zarr", + "type": "application/vnd+zarr", + "xarray:storage_options": { + "account_name": "cpdataeuwest" + }, + "xarray:open_kwargs": { + "consolidated": true + } + } + }, + "summaries": { + "datetime": { + "minimum": "2015-06-23T00:00:00Z", + "maximum": "2019-07-10T13:44:56Z" + } + }, + "links": [ + { + "href": "./collection.json", + "rel": "root", + "title": "A title", + "type": "application/json" + }, + { + "href": "./item.json", + "rel": "item" + } + ] + } \ No newline at end of file diff --git a/tests/data-files/xarray-assets/item.json b/tests/data-files/xarray-assets/item.json new file mode 100644 index 000000000..bba6c5635 --- /dev/null +++ b/tests/data-files/xarray-assets/item.json @@ -0,0 +1,56 @@ +{ + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/xarray-assets/v1.0.0/schema.json" + ], + "type": "Feature", + "id": "item", + "bbox": [ + 172.9, + 1.3, + 173, + 1.4 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.9, + 1.3 + ], + [ + 173, + 1.3 + ], + [ + 173, + 1.4 + ], + [ + 172.9, + 1.4 + ], + [ + 172.9, + 1.3 + ] + ] + ] + }, + "properties": { + "datetime": "2020-12-11T22:38:32Z" + }, + "links": [], + "assets": { + "data": { + "href": "abfs://example.com/examples/file.zarr", + "xarray:storage_options": { + "account_name": "test-account" + }, + "xarray:open_kwargs": { + "consolidated": true + } + } + } + } \ No newline at end of file diff --git a/tests/extensions/test_xarray_assets.py b/tests/extensions/test_xarray_assets.py new file mode 100644 index 000000000..850e731ba --- /dev/null +++ b/tests/extensions/test_xarray_assets.py @@ -0,0 +1,155 @@ +"""Tests for pystac.tests.extensions.xarray_assets""" +import json + +import pytest + +import pystac +from pystac.extensions.xarray_assets import XarrayAssetsExtension +from tests.conftest import get_data_file + + +@pytest.fixture +def ext_collection_uri() -> str: + return get_data_file("xarray-assets/collection.json") + + +@pytest.fixture +def ext_collection(ext_collection_uri: str) -> pystac.Collection: + return pystac.Collection.from_file(ext_collection_uri) + + +@pytest.fixture +def ext_item_uri() -> str: + return get_data_file("xarray-assets/item.json") + + +@pytest.fixture +def ext_item(ext_item_uri: str) -> pystac.Item: + return pystac.Item.from_file(ext_item_uri) + + +@pytest.fixture +def ext_asset(ext_item: pystac.Item) -> pystac.Asset: + return ext_item.assets["data"] + + +def test_item_stac_extensions(ext_item: pystac.Item) -> None: + assert XarrayAssetsExtension.has_extension(ext_item) + + +def test_collection_stac_extensions(ext_collection: pystac.Collection) -> None: + assert XarrayAssetsExtension.has_extension(ext_collection) + + +def test_item_get_schema_uri(ext_item: pystac.Item) -> None: + assert XarrayAssetsExtension.get_schema_uri() in ext_item.stac_extensions + + +def test_collection_get_schema_uri(ext_collection: pystac.Collection) -> None: + assert XarrayAssetsExtension.get_schema_uri() in ext_collection.stac_extensions + + +def test_ext_raises_if_item_does_not_conform(item: pystac.Item) -> None: + with pytest.raises(pystac.errors.ExtensionNotImplemented): + XarrayAssetsExtension.ext(item) + + +def test_ext_raises_if_collection_does_not_conform( + collection: pystac.Collection, +) -> None: + with pytest.raises(pystac.errors.ExtensionNotImplemented): + XarrayAssetsExtension.ext(collection) + + +def test_ext_raises_on_catalog(catalog: pystac.Catalog) -> None: + with pytest.raises( + pystac.errors.ExtensionTypeError, + match="XarrayAssetsExtension does not apply to type 'Catalog'", + ): + XarrayAssetsExtension.ext(catalog) # type: ignore + + +def test_item_to_from_dict(ext_item_uri: str, ext_item: pystac.Item) -> None: + with open(ext_item_uri) as f: + d = json.load(f) + actual = ext_item.to_dict(include_self_link=False) + assert actual == d + + +def test_collection_to_from_dict( + ext_collection_uri: str, ext_collection: pystac.Item +) -> None: + with open(ext_collection_uri) as f: + d = json.load(f) + actual = ext_collection.to_dict(include_self_link=False) + assert actual == d + + +def test_add_to_item(item: pystac.Item) -> None: + assert not XarrayAssetsExtension.has_extension(item) + XarrayAssetsExtension.add_to(item) + + assert XarrayAssetsExtension.has_extension(item) + + +def test_add_to_collection(collection: pystac.Collection) -> None: + assert not XarrayAssetsExtension.has_extension(collection) + XarrayAssetsExtension.add_to(collection) + + assert XarrayAssetsExtension.has_extension(collection) + + +def test_item_validate(ext_item: pystac.Item) -> None: + assert ext_item.validate() + + +def test_collection_validate(ext_collection: pystac.Collection) -> None: + assert ext_collection.validate() + + +def test_fields_are_not_on_item(ext_item: pystac.Item) -> None: + assert not hasattr(XarrayAssetsExtension.ext(ext_item), "storage_options") + assert not hasattr(XarrayAssetsExtension.ext(ext_item), "open_kwargs") + + +def test_fields_are_not_on_collection(ext_collection: pystac.Item) -> None: + assert not hasattr(XarrayAssetsExtension.ext(ext_collection), "storage_options") + assert not hasattr(XarrayAssetsExtension.ext(ext_collection), "open_kwargs") + + +@pytest.mark.parametrize("field", ["storage_options", "open_kwargs"]) +def test_get_field(ext_asset: pystac.Asset, field: str) -> None: + prop = ext_asset.extra_fields[f"xarray:{field}"] + attr = getattr(XarrayAssetsExtension.ext(ext_asset), field) + + assert attr is not None + assert attr == prop + + +@pytest.mark.parametrize( + "field,value", + [ + ("storage_options", {"anon": True}), + ("open_kwargs", {"engine": "zarr"}), + ], +) +def test_set_field(ext_asset: pystac.Asset, field: str, value) -> None: # type: ignore + original = ext_asset.extra_fields[f"xarray:{field}"] + setattr(XarrayAssetsExtension.ext(ext_asset), field, value) + new = ext_asset.extra_fields[f"xarray:{field}"] + + assert new != original + assert new == value + + item = ext_asset.owner + assert item is not None + assert item.validate() + + +@pytest.mark.parametrize("field", ["storage_options", "open_kwargs"]) +def test_set_field_to_none_pops_from_dict(ext_asset: pystac.Asset, field: str) -> None: + prop_name = f"xarray:{field}" + assert prop_name in ext_asset.extra_fields + + setattr(XarrayAssetsExtension.ext(ext_asset), field, None) + assert prop_name not in ext_asset.extra_fields From 98ef984ccde950da506ddf7a83c8027e963b8bfa Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Wed, 14 Jun 2023 15:46:05 -0400 Subject: [PATCH 3/9] Add some `open` tests --- pystac/extensions/xarray_assets.py | 20 +++++++++----------- tests/extensions/test_xarray_assets.py | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/pystac/extensions/xarray_assets.py b/pystac/extensions/xarray_assets.py index 749637cbd..031169e33 100644 --- a/pystac/extensions/xarray_assets.py +++ b/pystac/extensions/xarray_assets.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any, Dict, Generic, List, Optional, TypeVar, Union, cast +from typing import Any, Dict, Generic, List, Optional, TypeVar, Union import pystac from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension @@ -55,13 +55,13 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> XarrayAssetsExtension[T]: """ if isinstance(obj, pystac.Collection): cls.validate_has_extension(obj, add_if_missing) - return cast(XarrayAssetsExtension[T], CollectionXarrayAssetsExtension(obj)) + return CollectionXarrayAssetsExtension(obj) if isinstance(obj, pystac.Item): cls.validate_has_extension(obj, add_if_missing) - return cast(XarrayAssetsExtension[T], ItemXarrayAssetsExtension(obj)) + return ItemXarrayAssetsExtension(obj) if isinstance(obj, pystac.Asset): cls.validate_owner_has_extension(obj, add_if_missing) - return cast(XarrayAssetsExtension[T], AssetXarrayAssetsExtension(obj)) + return AssetXarrayAssetsExtension(obj) else: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @@ -115,17 +115,15 @@ class AssetXarrayAssetsExtension(XarrayAssetsExtension[pystac.Asset]): :meth:`XarrayAssetsExtension.ext` on an :class:`~pystac.Asset` to extend it. """ - asset_href: str + asset: pystac.Asset properties: Dict[str, Any] - additional_read_properties: Optional[List[Dict[str, Any]]] + additional_read_properties: Optional[List[Dict[str, Any]]] = None def __init__(self, asset: pystac.Asset): - self.asset_href = asset.href + self.asset = asset self.properties = asset.extra_fields if asset.owner and isinstance(asset.owner, pystac.Item): self.additional_read_properties = [asset.owner.properties] - else: - self.additional_read_properties = None @property def storage_options(self) -> Optional[Dict[str, Any]]: @@ -152,7 +150,7 @@ def open_kwargs(self, v: Optional[Dict[str, Any]]) -> Any: self.properties[OPEN_KWARGS_PROP] = v def __repr__(self) -> str: - return "".format(self.asset_href) + return "".format(self.asset.href) def open(self, **kwargs: Any) -> Any: try: @@ -160,7 +158,7 @@ def open(self, **kwargs: Any) -> Any: except ImportError: raise ImportError("Missing optional dependency `xpystac`") - return to_xarray(self, **kwargs) + return to_xarray(self.asset, **kwargs) class XarrayAssetsExtensionHooks(ExtensionHooks): diff --git a/tests/extensions/test_xarray_assets.py b/tests/extensions/test_xarray_assets.py index 850e731ba..c9307f7ff 100644 --- a/tests/extensions/test_xarray_assets.py +++ b/tests/extensions/test_xarray_assets.py @@ -1,7 +1,9 @@ """Tests for pystac.tests.extensions.xarray_assets""" import json +import sys import pytest +from pytest_mock import MockerFixture import pystac from pystac.extensions.xarray_assets import XarrayAssetsExtension @@ -153,3 +155,22 @@ def test_set_field_to_none_pops_from_dict(ext_asset: pystac.Asset, field: str) - setattr(XarrayAssetsExtension.ext(ext_asset), field, None) assert prop_name not in ext_asset.extra_fields + + +def test_open_calls_xpystac_to_xarray( + ext_asset: pystac.Asset, mocker: MockerFixture +) -> None: + xpystac = pytest.importorskip("xpystac") + mocker.patch("xpystac.core.to_xarray") + xr_ext = XarrayAssetsExtension.ext(ext_asset) + xr_ext.open() # type: ignore + xpystac.core.to_xarray.assert_called_once_with(ext_asset) + + +def test_open_raises_if_xpystac_not_installed( + ext_asset: pystac.Asset, mocker: MockerFixture +) -> None: + mocker.patch.dict(sys.modules, {"xpystac.core": None}) + xr_ext = XarrayAssetsExtension.ext(ext_asset) + with pytest.raises(ImportError, match="Missing optional dependency"): + xr_ext.open() # type: ignore From 10a6745da0496b692efee989ae3fcd1bbdc9845d Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Wed, 14 Jun 2023 16:08:29 -0400 Subject: [PATCH 4/9] Docstring for open --- pystac/extensions/xarray_assets.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pystac/extensions/xarray_assets.py b/pystac/extensions/xarray_assets.py index 031169e33..1c65b5948 100644 --- a/pystac/extensions/xarray_assets.py +++ b/pystac/extensions/xarray_assets.py @@ -153,6 +153,18 @@ def __repr__(self) -> str: return "".format(self.asset.href) def open(self, **kwargs: Any) -> Any: + """Open the asset's data as an xarray object. + + Requires ``xpystac`` to be installed. + + Args: + **kwargs: All keyword arguments are passed along to ``xarray.open_dataset`` + + Returns: + xarray.Dataset: An ndimentional representation of the data referenced + by the asset. The data file will be accessed, but the data will not + necessarily all be eagerly read. + """ try: from xpystac.core import to_xarray except ImportError: From 83f43b4bdc67a4b3f8ae37b1c9b14f64e3bf7a20 Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Wed, 14 Jun 2023 16:27:33 -0400 Subject: [PATCH 5/9] Add xpystac to test deps --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 6c819d5af..7174f2603 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,6 +69,7 @@ test = [ "types-orjson~=3.6", "types-python-dateutil~=2.8", "types-urllib3~=1.26", + "xpystac==0.1.1", ] urllib3 = ["urllib3>=1.26"] validation = ["jsonschema>=4.0.1"] From 21ee602152f32712cb7a91e8480f3cd966f0f67b Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Tue, 20 Jun 2023 09:25:43 -0400 Subject: [PATCH 6/9] Update pystac/extensions/xarray_assets.py Co-authored-by: Pete Gadomski --- pystac/extensions/xarray_assets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pystac/extensions/xarray_assets.py b/pystac/extensions/xarray_assets.py index 1c65b5948..5431f6228 100644 --- a/pystac/extensions/xarray_assets.py +++ b/pystac/extensions/xarray_assets.py @@ -167,8 +167,8 @@ def open(self, **kwargs: Any) -> Any: """ try: from xpystac.core import to_xarray - except ImportError: - raise ImportError("Missing optional dependency `xpystac`") + except ImportError as err: + raise ImportError("Missing optional dependency `xpystac`") from err return to_xarray(self.asset, **kwargs) From 7f794d0e75cce3f26bdb6989954a32945dbfdcbf Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 22 Jun 2023 10:53:10 -0400 Subject: [PATCH 7/9] Remove open method --- pyproject.toml | 1 - pystac/extensions/xarray_assets.py | 20 -------------------- tests/extensions/test_xarray_assets.py | 21 --------------------- 3 files changed, 42 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index da6f40fd1..eb021fe3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,6 @@ test = [ "types-orjson~=3.6", "types-python-dateutil~=2.8", "types-urllib3~=1.26", - "xpystac==0.1.1", ] urllib3 = ["urllib3>=1.26"] validation = ["jsonschema>=4.0.1"] diff --git a/pystac/extensions/xarray_assets.py b/pystac/extensions/xarray_assets.py index 5431f6228..1ce5c806c 100644 --- a/pystac/extensions/xarray_assets.py +++ b/pystac/extensions/xarray_assets.py @@ -152,26 +152,6 @@ def open_kwargs(self, v: Optional[Dict[str, Any]]) -> Any: def __repr__(self) -> str: return "".format(self.asset.href) - def open(self, **kwargs: Any) -> Any: - """Open the asset's data as an xarray object. - - Requires ``xpystac`` to be installed. - - Args: - **kwargs: All keyword arguments are passed along to ``xarray.open_dataset`` - - Returns: - xarray.Dataset: An ndimentional representation of the data referenced - by the asset. The data file will be accessed, but the data will not - necessarily all be eagerly read. - """ - try: - from xpystac.core import to_xarray - except ImportError as err: - raise ImportError("Missing optional dependency `xpystac`") from err - - return to_xarray(self.asset, **kwargs) - class XarrayAssetsExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI diff --git a/tests/extensions/test_xarray_assets.py b/tests/extensions/test_xarray_assets.py index c9307f7ff..850e731ba 100644 --- a/tests/extensions/test_xarray_assets.py +++ b/tests/extensions/test_xarray_assets.py @@ -1,9 +1,7 @@ """Tests for pystac.tests.extensions.xarray_assets""" import json -import sys import pytest -from pytest_mock import MockerFixture import pystac from pystac.extensions.xarray_assets import XarrayAssetsExtension @@ -155,22 +153,3 @@ def test_set_field_to_none_pops_from_dict(ext_asset: pystac.Asset, field: str) - setattr(XarrayAssetsExtension.ext(ext_asset), field, None) assert prop_name not in ext_asset.extra_fields - - -def test_open_calls_xpystac_to_xarray( - ext_asset: pystac.Asset, mocker: MockerFixture -) -> None: - xpystac = pytest.importorskip("xpystac") - mocker.patch("xpystac.core.to_xarray") - xr_ext = XarrayAssetsExtension.ext(ext_asset) - xr_ext.open() # type: ignore - xpystac.core.to_xarray.assert_called_once_with(ext_asset) - - -def test_open_raises_if_xpystac_not_installed( - ext_asset: pystac.Asset, mocker: MockerFixture -) -> None: - mocker.patch.dict(sys.modules, {"xpystac.core": None}) - xr_ext = XarrayAssetsExtension.ext(ext_asset) - with pytest.raises(ImportError, match="Missing optional dependency"): - xr_ext.open() # type: ignore From 52133cd791470f362eaa900e1389ee805d95bf7b Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 22 Jun 2023 10:53:29 -0400 Subject: [PATCH 8/9] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 137f08a88..0578a3f2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - `KML` as a built in media type ([#1127](https://github.com/stac-utils/pystac/issues/1127)) - `move/copy/delete` operations for local Assets ([#1158](https://github.com/stac-utils/pystac/issues/1158)) - Latest core STAC spec jsonshemas are included in pytstac and used for validation ([#1165](https://github.com/stac-utils/pystac/pull/1165)) +- Xarray Assets Extension class ([#1161](https://github.com/stac-utils/pystac/pull/1161)) ### Changed From 09d49a3a4a77fdc27691cdaf456d8b28a202969b Mon Sep 17 00:00:00 2001 From: Julia Signell Date: Thu, 22 Jun 2023 10:55:30 -0400 Subject: [PATCH 9/9] Fix copypasta --- pystac/extensions/xarray_assets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pystac/extensions/xarray_assets.py b/pystac/extensions/xarray_assets.py index 1ce5c806c..faeab6744 100644 --- a/pystac/extensions/xarray_assets.py +++ b/pystac/extensions/xarray_assets.py @@ -24,7 +24,7 @@ class XarrayAssetsExtension( ): """An abstract class that can be used to extend the properties of a :class:`~pystac.Collection`, :class:`~pystac.Item`, or :class:`~pystac.Asset` with - properties from the :stac-ext:`Datacube Extension `. This class is + properties from the :stac-ext:`Xarray Assets Extension `. This class is generic over the type of STAC Object to be extended (e.g. :class:`~pystac.Item`, :class:`~pystac.Asset`).