Skip to content

Commit 0186fba

Browse files
committed
fix: cf extension added
1 parent 065a631 commit 0186fba

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed

pystac/extensions/cf.py

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
"""CF Extension Module."""
2+
3+
from __future__ import annotations
4+
5+
import copy
6+
from collections.abc import Iterable
7+
from typing import (
8+
Any,
9+
Generic,
10+
Literal,
11+
TypeVar,
12+
cast,
13+
)
14+
15+
import pystac
16+
from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension
17+
from pystac.utils import map_opt
18+
19+
#: Generalized version of :class:`~pystac.Collection`, `:class:`~pystac.Item`,
20+
#: :class:`~pystac.Asset`, or :class:`~pystac.ItemAssetDefinition`
21+
T = TypeVar(
22+
"T", pystac.Collection, pystac.Item, pystac.Asset, pystac.ItemAssetDefinition
23+
)
24+
25+
SCHEMA_URI = "https://stac-extensions.github.io/cf/v0.2.0/schema.json"
26+
PREFIX: str = "cf:"
27+
28+
# Field names
29+
PARAMETER_PROP = PREFIX + "parameter"
30+
31+
32+
class Parameter:
33+
"""Helper for Parameter entries."""
34+
35+
name: str
36+
unit: str | None
37+
38+
def __init__(self, name: str, unit: str | None):
39+
self.name = name
40+
self.unit = unit
41+
42+
def __eq__(self, other: Any) -> bool:
43+
if not isinstance(other, Parameter):
44+
return NotImplemented
45+
return self.name == other.name and self.unit == other.unit
46+
47+
def __repr__(self) -> str:
48+
"""Return string repr."""
49+
return f"<Parameter name={self.name} unit={self.unit}>"
50+
51+
def to_dict(self) -> dict[str, str | None]:
52+
return copy.deepcopy({"name": self.name, "unit": self.unit})
53+
54+
@staticmethod
55+
def from_dict(d: dict[str, str]) -> Parameter:
56+
name = d.get("name")
57+
if name is None:
58+
raise ValueError("name must be a valid string.")
59+
return Parameter(name, d.get("unit"))
60+
61+
62+
class CFExtension(
63+
Generic[T],
64+
PropertiesExtension,
65+
ExtensionManagementMixin[pystac.Item | pystac.Collection],
66+
):
67+
"""An abstract class that can be used to extend the properties of an
68+
:class:`~pystac.Asset`, :class:`~pystac.Item`, or a :class:`pystac.Collection`
69+
with properties from the :stac-ext:`CF Extension <cf>`.
70+
This class is generic over the type of STAC Object to be extended
71+
(e.g. :class:`~pystac.Item`, :class:`~pystac.Collection`).
72+
73+
To create a concrete instance of :class:`CFExtension`, use the
74+
:meth:`CFExtension.ext` method. For example:
75+
76+
.. code-block:: python
77+
78+
>>> item: pystac.Item = ...
79+
>>> cf_ext = CFExtension.ext(item)
80+
"""
81+
82+
name: Literal["cf"] = "cf"
83+
84+
def apply(
85+
self,
86+
parameters: list[Parameter] | None = None,
87+
) -> None:
88+
"""Apply CF Extension properties to the extended :class:`~pystac.Asset`,
89+
:class:`~pystac.Item`, or :class:`~pystac.Collection`.
90+
"""
91+
self.parameters = parameters
92+
93+
@property
94+
def parameters(self) -> list[Parameter] | None:
95+
"""Get or set the CF parameter(s)."""
96+
return map_opt(
97+
lambda params: [Parameter.from_dict(param) for param in params],
98+
self._get_property(PARAMETER_PROP, list[dict[str, Any]]),
99+
)
100+
101+
@parameters.setter
102+
def parameters(self, v: list[Parameter] | None) -> None:
103+
self._set_property(
104+
PARAMETER_PROP,
105+
map_opt(lambda params: [param.to_dict() for param in params], v),
106+
)
107+
108+
@classmethod
109+
def get_schema_uri(cls) -> str:
110+
"""Return this extension's schema URI."""
111+
return SCHEMA_URI
112+
113+
@classmethod
114+
def ext(cls, obj: T, add_if_missing: bool = False) -> CFExtension[T]:
115+
"""Extend the given STAC Object with properties from the
116+
:stac-ext:`CF Extension <cf>`.
117+
118+
This extension can be applied to instances of :class:`~pystac.Item`,
119+
:class:`~pystac.Asset`, or :class:`~pystac.Collection`.
120+
121+
Raises
122+
------
123+
pystac.ExtensionTypeError : If an invalid object type is passed.
124+
"""
125+
if isinstance(obj, pystac.Collection):
126+
cls.ensure_has_extension(obj, add_if_missing)
127+
return cast(CFExtension[T], CollectionCFExtension(obj))
128+
elif isinstance(obj, pystac.Item):
129+
cls.ensure_has_extension(obj, add_if_missing)
130+
return cast(CFExtension[T], ItemCFExtension(obj))
131+
elif isinstance(obj, pystac.Asset):
132+
cls.ensure_owner_has_extension(obj, add_if_missing)
133+
return cast(CFExtension[T], AssetCFExtension(obj))
134+
elif isinstance(obj, pystac.ItemAssetDefinition):
135+
cls.ensure_owner_has_extension(obj, add_if_missing)
136+
return cast(CFExtension[T], ItemAssetsCFExtension(obj))
137+
else:
138+
raise pystac.ExtensionTypeError(cls._ext_error_message(obj))
139+
140+
141+
class ItemCFExtension(CFExtension[pystac.Item]):
142+
"""
143+
A concrete implementation of :class:`CFExtension` on an :class:`~pystac.Item`.
144+
145+
Extends the properties of the Item to include properties defined in the
146+
:stac-ext:`CF Extension <cf>`.
147+
148+
This class should generally not be instantiated directly. Instead, call
149+
:meth:`CFExtension.ext` on an :class:`~pystac.Item` to extend it.
150+
"""
151+
152+
item: pystac.Item
153+
properties: dict[str, Any]
154+
155+
def __init__(self, item: pystac.Item) -> None:
156+
self.item = item
157+
self.properties = item.properties
158+
159+
def __repr__(self) -> str:
160+
"""Return repr."""
161+
return f"<ItemCFExtension Item id={self.item.id}>"
162+
163+
164+
class ItemAssetsCFExtension(CFExtension[pystac.ItemAssetDefinition]):
165+
"""Extension for CF item assets."""
166+
167+
asset_defn: pystac.ItemAssetDefinition
168+
properties: dict[str, Any]
169+
170+
def __init__(self, item_asset: pystac.ItemAssetDefinition) -> None:
171+
self.asset_defn = item_asset
172+
self.properties = item_asset.properties
173+
174+
175+
class AssetCFExtension(CFExtension[pystac.Asset]):
176+
"""
177+
A concrete implementation of :class:`CFExtension` on an :class:`~pystac.Asset`.
178+
179+
Extends the Asset fields to include properties defined in the
180+
:stac-ext:`CF Extension <cf>`.
181+
182+
This class should generally not be instantiated directly. Instead, call
183+
:meth:`CFExtension.ext` on an :class:`~pystac.Asset` to extend it.
184+
"""
185+
186+
asset_href: str
187+
"""The ``href`` value of the :class:`~pystac.Asset` being extended."""
188+
189+
properties: dict[str, Any]
190+
"""The :class:`~pystac.Asset` fields, including extension properties."""
191+
192+
additional_read_properties: Iterable[dict[str, Any]] | None = None
193+
"""If present, this will be a list containing 1 dictionary representing the
194+
properties of the owning :class:`~pystac.Item`."""
195+
196+
def __init__(self, asset: pystac.Asset) -> None:
197+
self.asset_href = asset.href
198+
self.properties = asset.extra_fields
199+
if asset.owner and isinstance(asset.owner, pystac.Item):
200+
self.additional_read_properties = [asset.owner.properties]
201+
202+
def __repr__(self) -> str:
203+
"""Return repr."""
204+
return f"<AssetCFExtension Asset href={self.asset_href}>"
205+
206+
207+
class CollectionCFExtension(CFExtension[pystac.Collection]):
208+
"""Extension for CF data."""
209+
210+
collection: pystac.Collection
211+
properties: dict[str, Any]
212+
213+
def __init__(self, collection: pystac.Collection):
214+
self.collection = collection
215+
self.properties = collection.extra_fields
216+
217+
def __repr__(self) -> str:
218+
return f"<CollectionCFExtension Item id={self.collection.id}>"

0 commit comments

Comments
 (0)