Source code for pystac.serialization.identify

from __future__ import annotations

from enum import Enum
from functools import total_ordering
from typing import TYPE_CHECKING, Any

import pystac
from pystac.version import STACVersion

if TYPE_CHECKING:
    from pystac.stac_object import STACObjectType


[docs]class OldExtensionShortIDs(Enum): """Enumerates the IDs of common extensions.""" CHECKSUM = "checksum" COLLECTION_ASSETS = "collection-assets" DATACUBE = "datacube" # TODO EO = "eo" ITEM_ASSETS = "item-assets" # TODO LABEL = "label" POINTCLOUD = "pointcloud" PROJECTION = "projection" SAR = "sar" SAT = "sat" SCIENTIFIC = "scientific" SINGLE_FILE_STAC = "single-file-stac" TILED_ASSETS = "tiled-assets" TIMESTAMPS = "timestamps" VERSION = "version" VIEW = "view" FILE = "file"
[docs]@total_ordering class STACVersionID: """Defines STAC versions in an object that is orderable based on version number. For instance, ``1.0.0-beta.2 < 1.0.0`` """ version_string: str version_core: str version_prerelease: str | None def __init__(self, version_string: str) -> None: self.version_string = version_string # Account for RC or beta releases in version version_parts = version_string.split("-") self.version_core = version_parts[0] if len(version_parts) == 1: self.version_prerelease = None else: self.version_prerelease = "-".join(version_parts[1:]) def __str__(self) -> str: return self.version_string def __eq__(self, other: Any) -> bool: if not isinstance(other, STACVersionID): other = STACVersionID(str(other)) return str(self) == str(other) def __ne__(self, other: object) -> bool: return not self.__eq__(other) def __lt__(self, other: object) -> bool: if not isinstance(other, STACVersionID): other = STACVersionID(str(other)) if self.version_core < other.version_core: return True elif self.version_core > other.version_core: return False else: return self.version_prerelease is not None and ( other.version_prerelease is None or other.version_prerelease > self.version_prerelease )
[docs]class STACVersionRange: """Defines a range of STAC versions.""" min_version: STACVersionID max_version: STACVersionID def __init__( self, min_version: str | STACVersionID = "0.8.0", max_version: str | STACVersionID | None = None, ): if isinstance(min_version, str): self.min_version = STACVersionID(min_version) else: self.min_version = min_version if max_version is None: self.max_version = STACVersionID(STACVersion.DEFAULT_STAC_VERSION) else: if isinstance(max_version, str): self.max_version = STACVersionID(max_version) else: self.max_version = max_version
[docs] def set_min(self, v: STACVersionID) -> None: if self.min_version < v: if v < self.max_version: self.min_version = v else: self.min_version = self.max_version
[docs] def set_max(self, v: STACVersionID) -> None: if v < self.max_version: if self.min_version < v: self.max_version = v else: self.max_version = self.min_version
[docs] def set_to_single(self, v: STACVersionID) -> None: self.set_min(v) self.set_max(v)
[docs] def latest_valid_version(self) -> STACVersionID: return self.max_version
[docs] def contains(self, v: str | STACVersionID) -> bool: if isinstance(v, str): v = STACVersionID(v) return self.min_version <= v <= self.max_version
[docs] def is_single_version(self) -> bool: return self.min_version >= self.max_version
[docs] def is_earlier_than(self, v: str | STACVersionID) -> bool: if isinstance(v, str): v = STACVersionID(v) return self.max_version < v
[docs] def is_later_than(self, v: str | STACVersionID) -> bool: if isinstance(v, str): v = STACVersionID(v) return v < self.min_version
def __repr__(self) -> str: return f"<VERSIONS {self.min_version}-{self.max_version}>"
[docs]class STACJSONDescription: """Describes the STAC object information for a STAC object represented in JSON Attributes: object_type : Describes the STAC object type. One of :class:`~pystac.STACObjectType`. version_range : The STAC version range that describes what has been identified as potential valid versions of the stac object. extensions : List of extension schema URIs for extensions this object implements """ object_type: STACObjectType version_range: STACVersionRange extensions: list[str] def __init__( self, object_type: STACObjectType, version_range: STACVersionRange, extensions: list[str], ) -> None: self.object_type = object_type self.version_range = version_range self.extensions = extensions def __repr__(self) -> str: return "<{} {} ext={}>".format( self.object_type, self.version_range, ",".join(self.extensions) )
[docs]def identify_stac_object_type(json_dict: dict[str, Any]) -> STACObjectType | None: """Determines the STACObjectType of the provided JSON dict. If the JSON dict does not represent a STAC object, returns ``None``. Will first try to identify the object using ``"type"`` field as described in the guidelines in :stac-spec:`How to Differentiate STAC Files <best-practices.md#how-to-differentiate-stac-files>`. If this fails, will fall back to using the pre-1.0 heuristic described in `this issue <https://github.com/radiantearth/stac-spec/issues/889#issuecomment-684529444>`__ Args: json_dict : The dict of JSON to identify. """ stac_version = ( STACVersionID(json_dict["stac_version"]) if "stac_version" in json_dict else None ) obj_type = json_dict.get("type") # Try to identify using 'type' property for v1.0.0-rc.1 and higher introduced_type_attribute = STACVersionID("1.0.0-rc.1") if stac_version is not None and stac_version >= introduced_type_attribute: # Since v1.0.0-rc.1 requires a "type" field for all STAC objects, any object # that is missing this attribute is not a valid STAC object. if obj_type is None: return None # Try to match the "type" attribute if obj_type == pystac.STACObjectType.CATALOG: return pystac.STACObjectType.CATALOG elif obj_type == pystac.STACObjectType.COLLECTION: return pystac.STACObjectType.COLLECTION elif obj_type == pystac.STACObjectType.ITEM: return pystac.STACObjectType.ITEM else: return None # For pre-1.0 objects for version 0.8.* or later 'stac_version' must be present if stac_version is not None: # Pre-1.0 STAC objects with 'type' == "Feature" are Items if obj_type == "Feature": return pystac.STACObjectType.ITEM # Anything else with a 'type' field is not a STAC object if obj_type is not None: return None # Collections will contain either an 'extent' or a 'license' (or both) if "extent" in json_dict or "license" in json_dict: return pystac.STACObjectType.COLLECTION # Everything else that has a stac_version is a Catalog else: return pystac.STACObjectType.CATALOG return None
[docs]def identify_stac_object(json_dict: dict[str, Any]) -> STACJSONDescription: """Determines the STACJSONDescription of the provided JSON dict. Args: json_dict : The dict of STAC JSON to identify. Returns: STACJSONDescription: The description of the STAC object serialized in the given dict. """ object_type = identify_stac_object_type(json_dict) if object_type is None: extra_message = f"'type' attribute is {json_dict.get('type', 'not set')}" raise pystac.STACTypeError(json_dict, pystac.STACObject, extra_message) version_range = STACVersionRange() stac_version = json_dict.get("stac_version") stac_extensions = json_dict.get("stac_extensions", None) if stac_version is None: version_range.set_min(STACVersionID("0.8.0")) else: version_range.set_to_single(stac_version) if stac_extensions is not None: version_range.set_min(STACVersionID("0.8.0")) if stac_extensions is None: stac_extensions = [] return STACJSONDescription(object_type, version_range, stac_extensions)