diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c77a271c3..b1365993c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,6 +2,13 @@ # Please run `pre-commit run --all-files` when adding or changing entries. repos: + - repo: https://github.com/asottile/pyupgrade + rev: v3.15.0 + hooks: + - id: pyupgrade + args: + - "--py39-plus" + - repo: local hooks: - id: ruff diff --git a/docs/conf.py b/docs/conf.py index 798be2dd0..848eb9b2f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # @@ -15,7 +14,7 @@ import os import subprocess import sys -from typing import Any, Dict, List +from typing import Any sys.path.insert(0, os.path.abspath(".")) sys.path.insert(0, os.path.abspath("../")) @@ -152,7 +151,7 @@ # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # -html_sidebars: Dict[str, List[str]] = {"index": []} +html_sidebars: dict[str, list[str]] = {"index": []} # -- Options for HTMLHelp output --------------------------------------------- @@ -163,7 +162,7 @@ # -- Options for LaTeX output ------------------------------------------------ -latex_elements: Dict[str, Any] = { +latex_elements: dict[str, Any] = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', diff --git a/pystac/__init__.py b/pystac/__init__.py index 6f3036811..305d15121 100644 --- a/pystac/__init__.py +++ b/pystac/__init__.py @@ -43,7 +43,7 @@ ] import os -from typing import Any, Dict, Optional +from typing import Any, Optional from pystac.errors import ( TemplateError, @@ -197,7 +197,7 @@ def write_file( def read_dict( - d: Dict[str, Any], + d: dict[str, Any], href: Optional[str] = None, root: Optional[Catalog] = None, stac_io: Optional[StacIO] = None, diff --git a/pystac/asset.py b/pystac/asset.py index 4c3b48f7b..916a64c70 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -4,7 +4,7 @@ import shutil from copy import copy, deepcopy from html import escape -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar from pystac import common_metadata, utils from pystac.html.jinja_env import get_jinja_env @@ -41,38 +41,38 @@ class Asset: href: str """Link to the asset object. Relative and absolute links are both allowed.""" - title: Optional[str] + title: str | None """Optional displayed title for clients and users.""" - description: Optional[str] + description: str | None """A description of the Asset providing additional details, such as how it was processed or created. CommonMark 0.29 syntax MAY be used for rich text representation.""" - media_type: Optional[str] + media_type: str | None """Optional description of the media type. Registered Media Types are preferred. See :class:`~pystac.MediaType` for common media types.""" - roles: Optional[List[str]] + roles: list[str] | None """Optional, Semantic roles (i.e. thumbnail, overview, data, metadata) of the asset.""" - owner: Optional[Union[Item, Collection]] + owner: Item | Collection | None """The :class:`~pystac.Item` or :class:`~pystac.Collection` that this asset belongs to, or ``None`` if it has no owner.""" - extra_fields: Dict[str, Any] + extra_fields: dict[str, Any] """Optional, additional fields for this asset. This is used by extensions as a way to serialize and deserialize properties on asset object JSON.""" def __init__( self, href: str, - title: Optional[str] = None, - description: Optional[str] = None, - media_type: Optional[str] = None, - roles: Optional[List[str]] = None, - extra_fields: Optional[Dict[str, Any]] = None, + title: str | None = None, + description: str | None = None, + media_type: str | None = None, + roles: list[str] | None = None, + extra_fields: dict[str, Any] | None = None, ) -> None: self.href = utils.make_posix_style(href) self.title = title @@ -84,7 +84,7 @@ def __init__( # The Item which owns this Asset. self.owner = None - def set_owner(self, obj: Union[Collection, Item]) -> None: + def set_owner(self, obj: Collection | Item) -> None: """Sets the owning item of this Asset. The owning item will be used to resolve relative HREFs of this asset. @@ -94,7 +94,7 @@ def set_owner(self, obj: Union[Collection, Item]) -> None: """ self.owner = obj - def get_absolute_href(self) -> Optional[str]: + def get_absolute_href(self) -> str | None: """Gets the absolute href for this asset, if possible. If this Asset has no associated Item, and the asset HREF is a relative path, @@ -114,14 +114,14 @@ def get_absolute_href(self) -> Optional[str]: return utils.make_absolute_href(self.href, item_self) return None - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this Asset as a dictionary. Returns: dict: A serialization of the Asset. """ - d: Dict[str, Any] = {"href": self.href} + d: dict[str, Any] = {"href": self.href} if self.media_type is not None: d["type"] = self.media_type @@ -179,7 +179,7 @@ def common_metadata(self) -> CommonMetadata: return common_metadata.CommonMetadata(self) def __repr__(self) -> str: - return "".format(self.href) + return f"" def _repr_html_(self) -> str: jinja_env = get_jinja_env() @@ -190,7 +190,7 @@ def _repr_html_(self) -> str: return escape(repr(self)) @classmethod - def from_dict(cls: Type[A], d: Dict[str, Any]) -> A: + def from_dict(cls: type[A], d: dict[str, Any]) -> A: """Constructs an Asset from a dict. Returns: @@ -276,7 +276,7 @@ def ext(self) -> AssetExt: def _absolute_href( - href: str, owner: Optional[Union[Item, Collection]], action: str = "access" + href: str, owner: Item | Collection | None, action: str = "access" ) -> str: if utils.is_absolute_href(href): return href diff --git a/pystac/cache.py b/pystac/cache.py index be71033a1..9b409c414 100644 --- a/pystac/cache.py +++ b/pystac/cache.py @@ -2,7 +2,7 @@ from collections import ChainMap from copy import copy -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, cast +from typing import TYPE_CHECKING, Any, cast import pystac @@ -11,7 +11,7 @@ from pystac.stac_object import STACObject -def get_cache_key(stac_object: STACObject) -> Tuple[str, bool]: +def get_cache_key(stac_object: STACObject) -> tuple[str, bool]: """Produce a cache key for the given STAC object. If a self href is set, use that as the cache key. @@ -27,8 +27,8 @@ def get_cache_key(stac_object: STACObject) -> Tuple[str, bool]: if href is not None: return href, True else: - ids: List[str] = [] - obj: Optional[pystac.STACObject] = stac_object + ids: list[str] = [] + obj: pystac.STACObject | None = stac_object while obj is not None: ids.append(obj.id) obj = obj.get_parent() @@ -61,23 +61,23 @@ class ResolvedObjectCache: to collections. """ - id_keys_to_objects: Dict[str, STACObject] + id_keys_to_objects: dict[str, STACObject] """Existing cache of a key made up of the STACObject and it's parents IDs mapped to the cached STACObject.""" - hrefs_to_objects: Dict[str, STACObject] + hrefs_to_objects: dict[str, STACObject] """STAC Object HREFs matched to their cached object.""" - ids_to_collections: Dict[str, Collection] + ids_to_collections: dict[str, Collection] """Map of collection IDs to collections.""" - _collection_cache: Optional["ResolvedObjectCollectionCache"] + _collection_cache: ResolvedObjectCollectionCache | None def __init__( self, - id_keys_to_objects: Optional[Dict[str, STACObject]] = None, - hrefs_to_objects: Optional[Dict[str, STACObject]] = None, - ids_to_collections: Optional[Dict[str, Collection]] = None, + id_keys_to_objects: dict[str, STACObject] | None = None, + hrefs_to_objects: dict[str, STACObject] | None = None, + ids_to_collections: dict[str, Collection] | None = None, ): self.id_keys_to_objects = id_keys_to_objects or {} self.hrefs_to_objects = hrefs_to_objects or {} @@ -111,7 +111,7 @@ def get_or_cache(self, obj: STACObject) -> STACObject: self.cache(obj) return obj - def get(self, obj: STACObject) -> Optional[STACObject]: + def get(self, obj: STACObject) -> STACObject | None: """Get the cached object that has the same cache key as the given object. Args: @@ -128,7 +128,7 @@ def get(self, obj: STACObject) -> Optional[STACObject]: else: return self.id_keys_to_objects.get(key) - def get_by_href(self, href: str) -> Optional[STACObject]: + def get_by_href(self, href: str) -> STACObject | None: """Gets the cached object at href. Args: @@ -139,7 +139,7 @@ def get_by_href(self, href: str) -> Optional[STACObject]: """ return self.hrefs_to_objects.get(href) - def get_collection_by_id(self, id: str) -> Optional[Collection]: + def get_collection_by_id(self, id: str) -> Collection | None: """Retrieved a cached Collection by its ID. Args: @@ -199,7 +199,7 @@ def as_collection_cache(self) -> CollectionCache: @staticmethod def merge( - first: "ResolvedObjectCache", second: "ResolvedObjectCache" + first: ResolvedObjectCache, second: ResolvedObjectCache ) -> ResolvedObjectCache: """Merges two ResolvedObjectCache. @@ -247,23 +247,21 @@ class CollectionCache: in common properties. """ - cached_ids: Dict[str, Union[Collection, Dict[str, Any]]] - cached_hrefs: Dict[str, Union[Collection, Dict[str, Any]]] + cached_ids: dict[str, Collection | dict[str, Any]] + cached_hrefs: dict[str, Collection | dict[str, Any]] def __init__( self, - cached_ids: Optional[Dict[str, Union[Collection, Dict[str, Any]]]] = None, - cached_hrefs: Optional[Dict[str, Union[Collection, Dict[str, Any]]]] = None, + cached_ids: dict[str, Collection | dict[str, Any]] | None = None, + cached_hrefs: dict[str, Collection | dict[str, Any]] | None = None, ): self.cached_ids = cached_ids or {} self.cached_hrefs = cached_hrefs or {} - def get_by_id( - self, collection_id: str - ) -> Optional[Union[Collection, Dict[str, Any]]]: + def get_by_id(self, collection_id: str) -> Collection | dict[str, Any] | None: return self.cached_ids.get(collection_id) - def get_by_href(self, href: str) -> Optional[Union[Collection, Dict[str, Any]]]: + def get_by_href(self, href: str) -> Collection | dict[str, Any] | None: return self.cached_hrefs.get(href) def contains_id(self, collection_id: str) -> bool: @@ -271,8 +269,8 @@ def contains_id(self, collection_id: str) -> bool: def cache( self, - collection: Union[Collection, Dict[str, Any]], - href: Optional[str] = None, + collection: Collection | dict[str, Any], + href: str | None = None, ) -> None: """Caches a collection JSON.""" if isinstance(collection, pystac.Collection): @@ -290,22 +288,20 @@ class ResolvedObjectCollectionCache(CollectionCache): def __init__( self, resolved_object_cache: ResolvedObjectCache, - cached_ids: Optional[Dict[str, Union[Collection, Dict[str, Any]]]] = None, - cached_hrefs: Optional[Dict[str, Union[Collection, Dict[str, Any]]]] = None, + cached_ids: dict[str, Collection | dict[str, Any]] | None = None, + cached_hrefs: dict[str, Collection | dict[str, Any]] | None = None, ): super().__init__(cached_ids, cached_hrefs) self.resolved_object_cache = resolved_object_cache - def get_by_id( - self, collection_id: str - ) -> Optional[Union[Collection, Dict[str, Any]]]: + def get_by_id(self, collection_id: str) -> Collection | dict[str, Any] | None: result = self.resolved_object_cache.get_collection_by_id(collection_id) if result is None: return super().get_by_id(collection_id) else: return result - def get_by_href(self, href: str) -> Optional[Union[Collection, Dict[str, Any]]]: + def get_by_href(self, href: str) -> Collection | dict[str, Any] | None: result = self.resolved_object_cache.get_by_href(href) if result is None: return super().get_by_href(href) @@ -319,16 +315,16 @@ def contains_id(self, collection_id: str) -> bool: def cache( self, - collection: Union[Collection, Dict[str, Any]], - href: Optional[str] = None, + collection: Collection | dict[str, Any], + href: str | None = None, ) -> None: super().cache(collection, href) @staticmethod def merge( resolved_object_cache: ResolvedObjectCache, - first: Optional["ResolvedObjectCollectionCache"], - second: Optional["ResolvedObjectCollectionCache"], + first: ResolvedObjectCollectionCache | None, + second: ResolvedObjectCollectionCache | None, ) -> ResolvedObjectCollectionCache: first_cached_ids = {} if first is not None: diff --git a/pystac/catalog.py b/pystac/catalog.py index c69735871..680a31b6a 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -2,19 +2,13 @@ import os import warnings +from collections.abc import Iterable, Iterator from copy import deepcopy from itertools import chain from typing import ( TYPE_CHECKING, Any, Callable, - Dict, - Iterable, - Iterator, - List, - Optional, - Tuple, - Type, TypeVar, Union, cast, @@ -83,7 +77,7 @@ class CatalogType(StringEnum): """ @classmethod - def determine_type(cls, stac_json: Dict[str, Any]) -> Optional[CatalogType]: + def determine_type(cls, stac_json: dict[str, Any]) -> CatalogType | None: """Determines the catalog type based on a STAC JSON dict. Only applies to Catalogs or Collections @@ -141,27 +135,27 @@ class Catalog(STACObject): description: str """Detailed multi-line description to fully explain the catalog.""" - extra_fields: Dict[str, Any] + extra_fields: dict[str, Any] """Extra fields that are part of the top-level JSON properties of the Catalog.""" id: str """Identifier for the catalog.""" - links: List[Link] + links: list[Link] """A list of :class:`~pystac.Link` objects representing all links associated with this Catalog.""" - title: Optional[str] + title: str | None """Optional short descriptive one-line title for the catalog.""" - stac_extensions: List[str] + stac_extensions: list[str] """List of extensions the Catalog implements.""" _resolved_objects: ResolvedObjectCache STAC_OBJECT_TYPE = pystac.STACObjectType.CATALOG - _stac_io: Optional[pystac.StacIO] = None + _stac_io: pystac.StacIO | None = None """Optional instance of StacIO that will be used by default for any IO operations on objects contained by this catalog. Set while reading in a catalog. This is set when a catalog @@ -176,10 +170,10 @@ def __init__( self, id: str, description: str, - title: Optional[str] = None, - stac_extensions: Optional[List[str]] = None, - extra_fields: Optional[Dict[str, Any]] = None, - href: Optional[str] = None, + title: str | None = None, + stac_extensions: list[str] | None = None, + extra_fields: dict[str, Any] | None = None, + href: str | None = None, catalog_type: CatalogType = CatalogType.ABSOLUTE_PUBLISHED, ): super().__init__(stac_extensions or []) @@ -204,9 +198,9 @@ def __init__( self._resolved_objects.cache(self) def __repr__(self) -> str: - return "".format(self.id) + return f"" - def set_root(self, root: Optional["Catalog"]) -> None: + def set_root(self, root: Catalog | None) -> None: STACObject.set_root(self, root) if root is not None: root._resolved_objects = ResolvedObjectCache.merge( @@ -228,9 +222,9 @@ def is_relative(self) -> bool: def add_child( self, - child: Union["Catalog", Collection], - title: Optional[str] = None, - strategy: Optional[HrefLayoutStrategy] = None, + child: Catalog | Collection, + title: str | None = None, + strategy: HrefLayoutStrategy | None = None, set_parent: bool = True, ) -> Link: """Adds a link to a child :class:`~pystac.Catalog` or @@ -277,9 +271,7 @@ def add_child( self.add_link(child_link) return child_link - def add_children( - self, children: Iterable[Union["Catalog", Collection]] - ) -> List[Link]: + def add_children(self, children: Iterable[Catalog | Collection]) -> list[Link]: """Adds links to multiple :class:`~pystac.Catalog` or `~pystac.Collection` objects. This method will set each child's parent to this object, and their root to this Catalog's root. @@ -295,8 +287,8 @@ def add_children( def add_item( self, item: Item, - title: Optional[str] = None, - strategy: Optional[HrefLayoutStrategy] = None, + title: str | None = None, + strategy: HrefLayoutStrategy | None = None, set_parent: bool = True, ) -> Link: """Adds a link to an :class:`~pystac.Item`. @@ -345,8 +337,8 @@ def add_item( def add_items( self, items: Iterable[Item], - strategy: Optional[HrefLayoutStrategy] = None, - ) -> List[Link]: + strategy: HrefLayoutStrategy | None = None, + ) -> list[Link]: """Adds links to multiple :class:`Items `. This method will set each item's parent to this object, and their root to @@ -365,7 +357,7 @@ def add_items( def get_child( self, id: str, recursive: bool = False, sort_links_by_id: bool = True - ) -> Optional[Union["Catalog", Collection]]: + ) -> Catalog | Collection | None: """Gets the child of this catalog with the given ID, if it exists. Args: @@ -382,12 +374,12 @@ def get_child( or None if not found. """ if not recursive: - children: Iterable[Union[pystac.Catalog, pystac.Collection]] + children: Iterable[pystac.Catalog | pystac.Collection] if not sort_links_by_id: children = self.get_children() else: - def sort_function(links: List[Link]) -> List[Link]: + def sort_function(links: list[Link]) -> list[Link]: return sorted( links, key=lambda x: (href := x.get_href()) is None or id not in href, @@ -407,7 +399,7 @@ def sort_function(links: List[Link]) -> List[Link]: return child return None - def get_children(self) -> Iterable[Union["Catalog", Collection]]: + def get_children(self) -> Iterable[Catalog | Collection]: """Return all children of this catalog. Return: @@ -434,7 +426,7 @@ def get_all_collections(self) -> Iterable[Collection]: for child in self.get_children(): yield from child.get_collections() - def get_child_links(self) -> List[Link]: + def get_child_links(self) -> list[Link]: """Return all child links of this catalog. Return: @@ -458,7 +450,7 @@ def remove_child(self, child_id: str) -> None: Args: child_id : The ID of the child to remove. """ - new_links: List[pystac.Link] = [] + new_links: list[pystac.Link] = [] root = self.get_root() for link in self.links: if link.rel != pystac.RelType.CHILD: @@ -473,7 +465,7 @@ def remove_child(self, child_id: str) -> None: child.set_root(None) self.links = new_links - def get_item(self, id: str, recursive: bool = False) -> Optional[Item]: + def get_item(self, id: str, recursive: bool = False) -> Item | None: """ DEPRECATED. @@ -555,7 +547,7 @@ def remove_item(self, item_id: str) -> None: Args: item_id : The ID of the item to remove. """ - new_links: List[pystac.Link] = [] + new_links: list[pystac.Link] = [] root = self.get_root() for link in self.links: if link.rel != pystac.RelType.ITEM: @@ -594,7 +586,7 @@ def get_all_items(self) -> Iterator[Item]: *(child.get_items(recursive=True) for child in self.get_children()), ) - def get_item_links(self) -> List[Link]: + def get_item_links(self) -> list[Link]: """Return all item links of this catalog. Return: @@ -604,7 +596,7 @@ def get_item_links(self) -> List[Link]: def to_dict( self, include_self_link: bool = True, transform_hrefs: bool = True - ) -> Dict[str, Any]: + ) -> dict[str, Any]: links = [ x for x in self.links @@ -613,7 +605,7 @@ def to_dict( if not include_self_link: links = [x for x in links if x.rel != pystac.RelType.SELF] - d: Dict[str, Any] = { + d: dict[str, Any] = { "type": self.STAC_OBJECT_TYPE.value.title(), "id": self.id, "stac_version": pystac.get_stac_version(), @@ -674,9 +666,9 @@ def make_all_asset_hrefs_absolute(self) -> None: def normalize_and_save( self, root_href: str, - catalog_type: Optional[CatalogType] = None, - strategy: Optional[HrefLayoutStrategy] = None, - stac_io: Optional[pystac.StacIO] = None, + catalog_type: CatalogType | None = None, + strategy: HrefLayoutStrategy | None = None, + stac_io: pystac.StacIO | None = None, skip_unresolved: bool = False, ) -> None: """Normalizes link HREFs to the given root_href, and saves the catalog. @@ -711,7 +703,7 @@ def normalize_and_save( def normalize_hrefs( self, root_href: str, - strategy: Optional[HrefLayoutStrategy] = None, + strategy: HrefLayoutStrategy | None = None, skip_unresolved: bool = False, ) -> None: """Normalize HREFs will regenerate all link HREFs based on @@ -746,8 +738,8 @@ def normalize_hrefs( root_href = make_absolute_href(root_href, os.getcwd(), start_is_dir=True) def process_item( - item: Item, _root_href: str, parent: Optional[Catalog] - ) -> Optional[Callable[[], None]]: + item: Item, _root_href: str, parent: Catalog | None + ) -> Callable[[], None] | None: if not skip_unresolved: item.resolve_links() @@ -767,9 +759,9 @@ def process_catalog( cat: Catalog, _root_href: str, is_root: bool, - parent: Optional[Catalog] = None, - ) -> List[Callable[[], None]]: - setter_funcs: List[Callable[[], None]] = [] + parent: Catalog | None = None, + ) -> list[Callable[[], None]]: + setter_funcs: list[Callable[[], None]] = [] if not skip_unresolved: cat.resolve_links() @@ -821,9 +813,9 @@ def fn() -> None: def generate_subcatalogs( self, template: str, - defaults: Optional[Dict[str, Any]] = None, - parent_ids: Optional[List[str]] = None, - ) -> List["Catalog"]: + defaults: dict[str, Any] | None = None, + parent_ids: list[str] | None = None, + ) -> list[Catalog]: """Walks through the catalog and generates subcatalogs for items based on the template string. @@ -845,7 +837,7 @@ def generate_subcatalogs( Returns: [catalog]: List of new catalogs created """ - result: List[Catalog] = [] + result: list[Catalog] = [] parent_ids = parent_ids or list() parent_ids.append(self.id) for child in self.get_children(): @@ -857,16 +849,14 @@ def generate_subcatalogs( layout_template = LayoutTemplate(template, defaults=defaults) - keep_item_links: List[Link] = [] + keep_item_links: list[Link] = [] item_links = [lk for lk in self.links if lk.rel == pystac.RelType.ITEM] for link in item_links: link.resolve_stac_object(root=self.get_root()) item = cast(pystac.Item, link.target) subcat_ids = layout_template.substitute(item).split("/") id_iter = reversed(parent_ids) - if all( - ["{}".format(id) == next(id_iter, None) for id in reversed(subcat_ids)] - ): + if all([f"{id}" == next(id_iter, None) for id in reversed(subcat_ids)]): # Skip items for which the sub-catalog structure already # matches the template. The list of parent IDs can include more # elements on the root side, so compare the reversed sequences. @@ -900,9 +890,9 @@ def generate_subcatalogs( def save( self, - catalog_type: Optional[CatalogType] = None, - dest_href: Optional[str] = None, - stac_io: Optional[pystac.StacIO] = None, + catalog_type: CatalogType | None = None, + dest_href: str | None = None, + stac_io: pystac.StacIO | None = None, ) -> None: """Save this catalog and all it's children/item to files determined by the object's self link HREF or a specified path. @@ -996,7 +986,7 @@ def save( def walk( self, - ) -> Iterable[Tuple["Catalog", Iterable["Catalog"], Iterable[Item]]]: + ) -> Iterable[tuple[Catalog, Iterable[Catalog], Iterable[Item]]]: """Walks through children and items of catalogs. For each catalog in the STAC's tree rooted at this catalog (including this @@ -1032,9 +1022,7 @@ def fully_resolve(self) -> None: for item in items: pass - def validate_all( - self, max_items: Optional[int] = None, recursive: bool = True - ) -> int: + def validate_all(self, max_items: int | None = None, recursive: bool = True) -> int: """Validates each catalog, collection, item contained within this catalog. Walks through the children and items of the catalog and validates each @@ -1068,7 +1056,7 @@ def validate_all( n += 1 return n - def _object_links(self) -> List[Union[str, pystac.RelType]]: + def _object_links(self) -> list[str | pystac.RelType]: return [ pystac.RelType.CHILD, pystac.RelType.ITEM, @@ -1077,7 +1065,7 @@ def _object_links(self) -> List[Union[str, pystac.RelType]]: def map_items( self, - item_mapper: Callable[[Item], Union[Item, List[Item]]], + item_mapper: Callable[[Item], Item | list[Item]], ) -> Catalog: """Creates a copy of a catalog, with each item passed through the item_mapper function. @@ -1098,7 +1086,7 @@ def process_catalog(catalog: Catalog) -> None: for child in catalog.get_children(): process_catalog(child) - item_links: List[Link] = [] + item_links: list[Link] = [] for item_link in catalog.get_item_links(): item_link.resolve_stac_object(root=self.get_root()) mapped = item_mapper(cast(pystac.Item, item_link.target)) @@ -1122,7 +1110,7 @@ def map_assets( self, asset_mapper: Callable[ [str, Asset], - Union[Asset, Tuple[str, Asset], Dict[str, Asset]], + Asset | tuple[str, Asset] | dict[str, Asset], ], ) -> Catalog: """Creates a copy of a catalog, with each Asset for each Item passed @@ -1140,8 +1128,8 @@ def map_assets( """ def apply_asset_mapper( - tup: Tuple[str, Asset] - ) -> List[Tuple[str, pystac.Asset]]: + tup: tuple[str, Asset] + ) -> list[tuple[str, pystac.Asset]]: k, v = tup result = asset_mapper(k, v) if result is None: @@ -1177,22 +1165,22 @@ def describe(self, include_hrefs: bool = False, _indent: int = 0) -> None: """ s = "{}* {}".format(" " * _indent, self) if include_hrefs: - s += " {}".format(self.get_self_href()) + s += f" {self.get_self_href()}" print(s) for child in self.get_children(): child.describe(include_hrefs=include_hrefs, _indent=_indent + 4) for item in self.get_items(): s = "{}* {}".format(" " * (_indent + 2), item) if include_hrefs: - s += " {}".format(item.get_self_href()) + s += f" {item.get_self_href()}" print(s) @classmethod def from_dict( - cls: Type[C], - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional["Catalog"] = None, + cls: type[C], + d: dict[str, Any], + href: str | None = None, + root: Catalog | None = None, migrate: bool = False, preserve_dict: bool = True, ) -> C: @@ -1240,14 +1228,12 @@ def from_dict( return cat def full_copy( - self, root: Optional["Catalog"] = None, parent: Optional["Catalog"] = None + self, root: Catalog | None = None, parent: Catalog | None = None ) -> Catalog: return cast(Catalog, super().full_copy(root, parent)) @classmethod - def from_file( - cls: Type[C], href: HREF, stac_io: Optional[pystac.StacIO] = None - ) -> C: + def from_file(cls: type[C], href: HREF, stac_io: pystac.StacIO | None = None) -> C: if stac_io is None: stac_io = pystac.StacIO.default() @@ -1257,5 +1243,5 @@ def from_file( return result @classmethod - def matches_object_type(cls, d: Dict[str, Any]) -> bool: + def matches_object_type(cls, d: dict[str, Any]) -> bool: return identify_stac_object_type(d) == STACObjectType.CATALOG diff --git a/pystac/collection.py b/pystac/collection.py index f8746dc09..c798a8021 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -1,17 +1,13 @@ from __future__ import annotations import warnings +from collections.abc import Iterable from copy import deepcopy from datetime import datetime from typing import ( TYPE_CHECKING, Any, - Dict, - Iterable, - List, Optional, - Tuple, - Type, TypeVar, Union, cast, @@ -47,9 +43,9 @@ C = TypeVar("C", bound="Collection") -TemporalIntervals = Union[List[List[datetime]], List[List[Optional[datetime]]]] +TemporalIntervals = Union[list[list[datetime]], list[list[Optional[datetime]]]] TemporalIntervalsLike = Union[ - TemporalIntervals, List[datetime], List[Optional[datetime]] + TemporalIntervals, list[datetime], list[Optional[datetime]] ] @@ -66,31 +62,31 @@ class SpatialExtent: Spatial Extent object. """ - bboxes: List[List[float]] + bboxes: list[list[float]] """A list of bboxes that represent the spatial extent of the collection. Each bbox can be 2D or 3D. The length of the bbox array must be 2*n where n is the number of dimensions. For example, a 2D Collection with only one bbox would be [[xmin, ymin, xmax, ymax]]""" - extra_fields: Dict[str, Any] + extra_fields: dict[str, Any] """Dictionary containing additional top-level fields defined on the Spatial Extent object.""" def __init__( self, - bboxes: Union[List[List[float]], List[float]], - extra_fields: Optional[Dict[str, Any]] = None, + bboxes: list[list[float]] | list[float], + extra_fields: dict[str, Any] | None = None, ) -> None: # A common mistake is to pass in a single bbox instead of a list of bboxes. # Account for this by transforming the input in that case. if isinstance(bboxes, list) and isinstance(bboxes[0], float): - self.bboxes: List[List[float]] = [cast(List[float], bboxes)] + self.bboxes: list[list[float]] = [cast(list[float], bboxes)] else: - self.bboxes = cast(List[List[float]], bboxes) + self.bboxes = cast(list[list[float]], bboxes) self.extra_fields = extra_fields or {} - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this spatial extent as a dictionary. Returns: @@ -111,7 +107,7 @@ def clone(self) -> SpatialExtent: ) @staticmethod - def from_dict(d: Dict[str, Any]) -> SpatialExtent: + def from_dict(d: dict[str, Any]) -> SpatialExtent: """Constructs a SpatialExtent from a dict. Returns: @@ -123,7 +119,7 @@ def from_dict(d: Dict[str, Any]) -> SpatialExtent: @staticmethod def from_coordinates( - coordinates: List[Any], extra_fields: Optional[Dict[str, Any]] = None + coordinates: list[Any], extra_fields: dict[str, Any] | None = None ) -> SpatialExtent: """Constructs a SpatialExtent from a set of coordinates. @@ -141,12 +137,12 @@ def from_coordinates( """ def process_coords( - coord_lists: List[Any], - xmin: Optional[float] = None, - ymin: Optional[float] = None, - xmax: Optional[float] = None, - ymax: Optional[float] = None, - ) -> Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]: + coord_lists: list[Any], + xmin: float | None = None, + ymin: float | None = None, + xmax: float | None = None, + ymax: float | None = None, + ) -> tuple[float | None, float | None, float | None, float | None]: for coord in coord_lists: if isinstance(coord[0], list): xmin, ymin, xmax, ymax = process_coords( @@ -197,14 +193,14 @@ class TemporalExtent: represented by either the start (the first element of the interval) or the end (the second element of the interval) being None.""" - extra_fields: Dict[str, Any] + extra_fields: dict[str, Any] """Dictionary containing additional top-level fields defined on the Temporal Extent object.""" def __init__( self, intervals: TemporalIntervals, - extra_fields: Optional[Dict[str, Any]] = None, + extra_fields: dict[str, Any] | None = None, ): # A common mistake is to pass in a single interval instead of a # list of intervals. Account for this by transforming the input @@ -216,13 +212,13 @@ def __init__( self.extra_fields = extra_fields or {} - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this temporal extent as a dictionary. Returns: dict: A serialization of the TemporalExtent. """ - encoded_intervals: List[List[Optional[str]]] = [] + encoded_intervals: list[list[str | None]] = [] for i in self.intervals: start = None end = None @@ -250,13 +246,13 @@ def clone(self) -> TemporalExtent: ) @staticmethod - def from_dict(d: Dict[str, Any]) -> TemporalExtent: + def from_dict(d: dict[str, Any]) -> TemporalExtent: """Constructs an TemporalExtent from a dict. Returns: TemporalExtent: The TemporalExtent deserialized from the JSON dict. """ - parsed_intervals: List[List[Optional[datetime]]] = [] + parsed_intervals: list[list[datetime | None]] = [] for i in d["interval"]: if isinstance(i, str): # d["interval"] is a list of strings, so we correct the list and @@ -315,7 +311,7 @@ class Extent: temporal: TemporalExtent """Potential temporal extent covered by the collection.""" - extra_fields: Dict[str, Any] + extra_fields: dict[str, Any] """Dictionary containing additional top-level fields defined on the Extent object.""" @@ -323,13 +319,13 @@ def __init__( self, spatial: SpatialExtent, temporal: TemporalExtent, - extra_fields: Optional[Dict[str, Any]] = None, + extra_fields: dict[str, Any] | None = None, ): self.spatial = spatial self.temporal = temporal self.extra_fields = extra_fields or {} - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this extent as a dictionary. Returns: @@ -357,7 +353,7 @@ def clone(self) -> Extent: ) @staticmethod - def from_dict(d: Dict[str, Any]) -> Extent: + def from_dict(d: dict[str, Any]) -> Extent: """Constructs an Extent from a dict. Returns: @@ -373,7 +369,7 @@ def from_dict(d: Dict[str, Any]) -> Extent: @staticmethod def from_items( - items: Iterable[Item], extra_fields: Optional[Dict[str, Any]] = None + items: Iterable[Item], extra_fields: dict[str, Any] | None = None ) -> Extent: """Create an Extent based on the datetimes and bboxes of a list of items. @@ -386,15 +382,15 @@ def from_items( Extent: An Extent that spatially and temporally covers all of the given items. """ - bounds_values: List[List[float]] = [ + bounds_values: list[list[float]] = [ [float("inf")], [float("inf")], [float("-inf")], [float("-inf")], ] - datetimes: List[datetime] = [] - starts: List[datetime] = [] - ends: List[datetime] = [] + datetimes: list[datetime] = [] + starts: list[datetime] = [] + ends: list[datetime] = [] for item in items: if item.bbox is not None: @@ -476,7 +472,7 @@ class Collection(Catalog): :attr:`~pystac.Asset.owner` attribute set to the created Collection. """ - assets: Dict[str, Asset] + assets: dict[str, Asset] """Map of Assets""" description: str @@ -489,27 +485,27 @@ class Collection(Catalog): id: str """Identifier for the collection.""" - stac_extensions: List[str] + stac_extensions: list[str] """List of extensions the Collection implements.""" - title: Optional[str] + title: str | None """Optional short descriptive one-line title for the collection.""" - keywords: Optional[List[str]] + keywords: list[str] | None """Optional list of keywords describing the collection.""" - providers: Optional[List[Provider]] + providers: list[Provider] | None """Optional list of providers of this Collection.""" summaries: Summaries """A map of property summaries, either a set of values or statistics such as a range.""" - links: List[Link] + links: list[Link] """A list of :class:`~pystac.Link` objects representing all links associated with this Collection.""" - extra_fields: Dict[str, Any] + extra_fields: dict[str, Any] """Extra fields that are part of the top-level JSON properties of the Collection.""" STAC_OBJECT_TYPE = STACObjectType.COLLECTION @@ -523,16 +519,16 @@ def __init__( id: str, description: str, extent: Extent, - title: Optional[str] = None, - stac_extensions: Optional[List[str]] = None, - href: Optional[str] = None, - extra_fields: Optional[Dict[str, Any]] = None, - catalog_type: Optional[CatalogType] = None, + title: str | None = None, + stac_extensions: list[str] | None = None, + href: str | None = None, + extra_fields: dict[str, Any] | None = None, + catalog_type: CatalogType | None = None, license: str = "proprietary", - keywords: Optional[List[str]] = None, - providers: Optional[List[Provider]] = None, - summaries: Optional[Summaries] = None, - assets: Optional[Dict[str, Asset]] = None, + keywords: list[str] | None = None, + providers: list[Provider] | None = None, + summaries: Summaries | None = None, + assets: dict[str, Asset] | None = None, ): super().__init__( id, @@ -546,7 +542,7 @@ def __init__( self.extent = extent self.license = license - self.stac_extensions: List[str] = stac_extensions or [] + self.stac_extensions: list[str] = stac_extensions or [] self.keywords = keywords self.providers = providers self.summaries = summaries or Summaries.empty() @@ -557,13 +553,13 @@ def __init__( self.add_asset(k, asset) def __repr__(self) -> str: - return "".format(self.id) + return f"" def add_item( self, item: Item, - title: Optional[str] = None, - strategy: Optional[HrefLayoutStrategy] = None, + title: str | None = None, + strategy: HrefLayoutStrategy | None = None, set_parent: bool = True, ) -> Link: link = super().add_item(item, title, strategy, set_parent) @@ -572,7 +568,7 @@ def add_item( def to_dict( self, include_self_link: bool = True, transform_hrefs: bool = True - ) -> Dict[str, Any]: + ) -> dict[str, Any]: d = super().to_dict( include_self_link=include_self_link, transform_hrefs=transform_hrefs ) @@ -625,10 +621,10 @@ def clone(self) -> Collection: @classmethod def from_dict( - cls: Type[C], - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional[Catalog] = None, + cls: type[C], + d: dict[str, Any], + href: str | None = None, + root: Catalog | None = None, migrate: bool = False, preserve_dict: bool = True, ) -> C: @@ -707,7 +703,7 @@ def from_dict( return collection - def get_item(self, id: str, recursive: bool = False) -> Optional[Item]: + def get_item(self, id: str, recursive: bool = False) -> Item | None: """Returns an item with a given ID. Args: @@ -730,9 +726,9 @@ def get_item(self, id: str, recursive: bool = False) -> Optional[Item]: def get_assets( self, - media_type: Optional[Union[str, pystac.MediaType]] = None, - role: Optional[str] = None, - ) -> Dict[str, Asset]: + media_type: str | pystac.MediaType | None = None, + role: str | None = None, + ) -> dict[str, Asset]: """Get this collection's assets. Args: @@ -824,12 +820,12 @@ def update_extent_from_items(self) -> None: self.extent = Extent.from_items(self.get_items(recursive=True)) def full_copy( - self, root: Optional["Catalog"] = None, parent: Optional["Catalog"] = None + self, root: Catalog | None = None, parent: Catalog | None = None ) -> Collection: return cast(Collection, super().full_copy(root, parent)) @classmethod - def matches_object_type(cls, d: Dict[str, Any]) -> bool: + def matches_object_type(cls, d: dict[str, Any]) -> bool: return identify_stac_object_type(d) == STACObjectType.COLLECTION @property diff --git a/pystac/common_metadata.py b/pystac/common_metadata.py index ebabcabed..c2d12e71d 100644 --- a/pystac/common_metadata.py +++ b/pystac/common_metadata.py @@ -1,7 +1,7 @@ from __future__ import annotations from datetime import datetime -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, TypeVar, Union, cast +from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast import pystac from pystac import utils @@ -25,13 +25,13 @@ class CommonMetadata: properties : Dictionary of attributes that is the Item's properties """ - object: Union[Asset, Item] + object: Asset | Item """The object from which common metadata is obtained.""" - def __init__(self, object: Union[Asset, Item]): + def __init__(self, object: Asset | Item): self.object = object - def _set_field(self, prop_name: str, v: Optional[Any]) -> None: + def _set_field(self, prop_name: str, v: Any | None) -> None: if hasattr(self.object, prop_name): setattr(self.object, prop_name, v) elif hasattr(self.object, "properties"): @@ -41,7 +41,7 @@ def _set_field(self, prop_name: str, v: Optional[Any]) -> None: else: item.properties[prop_name] = v elif hasattr(self.object, "extra_fields") and isinstance( - self.object.extra_fields, Dict + self.object.extra_fields, dict ): if v is None: self.object.extra_fields.pop(prop_name, None) @@ -50,14 +50,14 @@ def _set_field(self, prop_name: str, v: Optional[Any]) -> None: else: raise pystac.STACError(f"Cannot set field {prop_name} on {self}.") - def _get_field(self, prop_name: str, _typ: Type[P]) -> Optional[P]: + def _get_field(self, prop_name: str, _typ: type[P]) -> P | None: if hasattr(self.object, prop_name): return cast(Optional[P], getattr(self.object, prop_name)) elif hasattr(self.object, "properties"): item = cast(pystac.Item, self.object) return item.properties.get(prop_name) elif hasattr(self.object, "extra_fields") and isinstance( - self.object.extra_fields, Dict + self.object.extra_fields, dict ): return self.object.extra_fields.get(prop_name) else: @@ -65,67 +65,67 @@ def _get_field(self, prop_name: str, _typ: Type[P]) -> Optional[P]: # Basics @property - def title(self) -> Optional[str]: + def title(self) -> str | None: """Gets or set the object's title.""" return self._get_field("title", str) @title.setter - def title(self, v: Optional[str]) -> None: + def title(self, v: str | None) -> None: self._set_field("title", v) @property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Gets or set the object's description.""" return self._get_field("description", str) @description.setter - def description(self, v: Optional[str]) -> None: + def description(self, v: str | None) -> None: self._set_field("description", v) # Date and Time Range @property - def start_datetime(self) -> Optional[datetime]: + def start_datetime(self) -> datetime | None: """Get or set the object's start_datetime.""" return utils.map_opt( utils.str_to_datetime, self._get_field("start_datetime", str) ) @start_datetime.setter - def start_datetime(self, v: Optional[datetime]) -> None: + def start_datetime(self, v: datetime | None) -> None: self._set_field("start_datetime", utils.map_opt(utils.datetime_to_str, v)) @property - def end_datetime(self) -> Optional[datetime]: + def end_datetime(self) -> datetime | None: """Get or set the item's end_datetime.""" return utils.map_opt( utils.str_to_datetime, self._get_field("end_datetime", str) ) @end_datetime.setter - def end_datetime(self, v: Optional[datetime]) -> None: + def end_datetime(self, v: datetime | None) -> None: self._set_field("end_datetime", utils.map_opt(utils.datetime_to_str, v)) # License @property - def license(self) -> Optional[str]: + def license(self) -> str | None: """Get or set the current license.""" return self._get_field("license", str) @license.setter - def license(self, v: Optional[str]) -> None: + def license(self, v: str | None) -> None: self._set_field("license", v) # Providers @property - def providers(self) -> Optional[List[Provider]]: + def providers(self) -> list[Provider] | None: """Get or set a list of the object's providers.""" return utils.map_opt( lambda providers: [pystac.Provider.from_dict(d) for d in providers], - self._get_field("providers", List[Dict[str, Any]]), + self._get_field("providers", list[dict[str, Any]]), ) @providers.setter - def providers(self, v: Optional[List[Provider]]) -> None: + def providers(self, v: list[Provider] | None) -> None: self._set_field( "providers", utils.map_opt(lambda providers: [p.to_dict() for p in providers], v), @@ -133,53 +133,53 @@ def providers(self, v: Optional[List[Provider]]) -> None: # Instrument @property - def platform(self) -> Optional[str]: + def platform(self) -> str | None: """Gets or set the object's platform attribute.""" return self._get_field("platform", str) @platform.setter - def platform(self, v: Optional[str]) -> None: + def platform(self, v: str | None) -> None: self._set_field("platform", v) @property - def instruments(self) -> Optional[List[str]]: + def instruments(self) -> list[str] | None: """Gets or sets the names of the instruments used.""" - return self._get_field("instruments", List[str]) + return self._get_field("instruments", list[str]) @instruments.setter - def instruments(self, v: Optional[List[str]]) -> None: + def instruments(self, v: list[str] | None) -> None: self._set_field("instruments", v) @property - def constellation(self) -> Optional[str]: + def constellation(self) -> str | None: """Gets or set the name of the constellation associate with an object.""" return self._get_field("constellation", str) @constellation.setter - def constellation(self, v: Optional[str]) -> None: + def constellation(self, v: str | None) -> None: self._set_field("constellation", v) @property - def mission(self) -> Optional[str]: + def mission(self) -> str | None: """Gets or set the name of the mission associated with an object.""" return self._get_field("mission", str) @mission.setter - def mission(self, v: Optional[str]) -> None: + def mission(self, v: str | None) -> None: self._set_field("mission", v) @property - def gsd(self) -> Optional[float]: + def gsd(self) -> float | None: """Gets or sets the Ground Sample Distance at the sensor.""" return self._get_field("gsd", float) @gsd.setter - def gsd(self, v: Optional[float]) -> None: + def gsd(self, v: float | None) -> None: self._set_field("gsd", v) # Metadata @property - def created(self) -> Optional[datetime]: + def created(self) -> datetime | None: """Get or set the metadata file's creation date. All datetime attributes have setters that can take either a string or a datetime, but always stores the attribute as a string. @@ -193,11 +193,11 @@ def created(self) -> Optional[datetime]: return utils.map_opt(utils.str_to_datetime, self._get_field("created", str)) @created.setter - def created(self, v: Optional[datetime]) -> None: + def created(self, v: datetime | None) -> None: self._set_field("created", utils.map_opt(utils.datetime_to_str, v)) @property - def updated(self) -> Optional[datetime]: + def updated(self) -> datetime | None: """Get or set the metadata file's update date. All datetime attributes have setters that can take either a string or a datetime, but always stores the attribute as a string @@ -211,5 +211,5 @@ def updated(self) -> Optional[datetime]: return utils.map_opt(utils.str_to_datetime, self._get_field("updated", str)) @updated.setter - def updated(self, v: Optional[datetime]) -> None: + def updated(self, v: datetime | None) -> None: self._set_field("updated", utils.map_opt(utils.datetime_to_str, v)) diff --git a/pystac/errors.py b/pystac/errors.py index abfd8aaee..6d40e4d8c 100644 --- a/pystac/errors.py +++ b/pystac/errors.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union class TemplateError(Exception): @@ -26,7 +26,7 @@ class STACTypeError(Exception): def __init__( self, - bad_dict: Dict[str, Any], + bad_dict: dict[str, Any], expected: type, extra_message: Optional[str] = "", ): diff --git a/pystac/extensions/base.py b/pystac/extensions/base.py index f10f2e6e3..a1ea09ddf 100644 --- a/pystac/extensions/base.py +++ b/pystac/extensions/base.py @@ -3,17 +3,12 @@ import re import warnings from abc import ABC, abstractmethod +from collections.abc import Iterable from typing import ( TYPE_CHECKING, Any, - Dict, Generic, - Iterable, - List, - Optional, - Type, TypeVar, - Union, cast, ) @@ -42,7 +37,7 @@ def __init__(self, collection: pystac.Collection) -> None: def _set_summary( self, prop_key: str, - v: Optional[Union[List[Any], pystac.RangeSummary[Any], Dict[str, Any]]], + v: list[Any] | pystac.RangeSummary[Any] | dict[str, Any] | None, ) -> None: if v is None: self.summaries.remove(prop_key) @@ -62,14 +57,14 @@ class PropertiesExtension(ABC): :class:`~pystac.extensions.eo.PropertiesEOExtension` for an example. """ - properties: Dict[str, Any] + properties: dict[str, Any] """The properties that this extension wraps. The extension which implements PropertiesExtension can use ``_get_property`` and ``_set_property`` to get and set values on this instance. Note that _set_properties mutates the properties directly.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """Additional read-only properties accessible from the extended object. These are used when extending an :class:`~pystac.Asset` to give access to the @@ -78,19 +73,19 @@ class PropertiesExtension(ABC): ``additional_read_properties`` will take precedence. """ - def _get_property(self, prop_name: str, _typ: Type[P]) -> Optional[P]: - maybe_property: Optional[P] = self.properties.get(prop_name) + def _get_property(self, prop_name: str, _typ: type[P]) -> P | None: + maybe_property: P | None = self.properties.get(prop_name) if maybe_property is not None: return maybe_property if self.additional_read_properties is not None: for props in self.additional_read_properties: - maybe_additional_property: Optional[P] = props.get(prop_name) + maybe_additional_property: P | None = props.get(prop_name) if maybe_additional_property is not None: return maybe_additional_property return None def _set_property( - self, prop_name: str, v: Optional[Any], pop_if_none: bool = True + self, prop_name: str, v: Any | None, pop_if_none: bool = True ) -> None: if v is None and pop_if_none: self.properties.pop(prop_name, None) @@ -123,7 +118,7 @@ def get_schema_uri(cls) -> str: raise NotImplementedError @classmethod - def get_schema_uris(cls) -> List[str]: + def get_schema_uris(cls) -> list[str]: """Gets a list of schema URIs associated with this extension.""" warnings.warn( "get_schema_uris is deprecated and will be removed in v2", @@ -163,7 +158,7 @@ def has_extension(cls, obj: S) -> bool: @classmethod def validate_owner_has_extension( cls, - asset: Union[pystac.Asset, AssetDefinition], + asset: pystac.Asset | AssetDefinition, add_if_missing: bool = False, ) -> None: """ @@ -195,7 +190,7 @@ def validate_owner_has_extension( @classmethod def ensure_owner_has_extension( cls, - asset: Union[pystac.Asset, AssetDefinition], + asset: pystac.Asset | AssetDefinition, add_if_missing: bool = False, ) -> None: """Given an :class:`~pystac.Asset`, checks if the asset's owner has this diff --git a/pystac/extensions/classification.py b/pystac/extensions/classification.py index 4407510b9..31d98d47c 100644 --- a/pystac/extensions/classification.py +++ b/pystac/extensions/classification.py @@ -4,15 +4,12 @@ import re import warnings +from collections.abc import Iterable +from re import Pattern from typing import ( Any, - Dict, Generic, - Iterable, - List, Literal, - Optional, - Pattern, TypeVar, Union, cast, @@ -36,7 +33,7 @@ "https://stac-extensions.github.io/classification/v{version}/schema.json" ) DEFAULT_VERSION: str = "1.1.0" -SUPPORTED_VERSIONS: List[str] = ["1.1.0", "1.0.0"] +SUPPORTED_VERSIONS: list[str] = ["1.1.0", "1.0.0"] # Field names PREFIX: str = "classification:" @@ -53,17 +50,17 @@ class Classification: Use Classification.create to create a new Classification. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]) -> None: + def __init__(self, properties: dict[str, Any]) -> None: self.properties = properties def apply( self, value: int, description: str, - name: Optional[str] = None, - color_hint: Optional[str] = None, + name: str | None = None, + color_hint: str | None = None, ) -> None: """ Set the properties for a new Classification. @@ -98,8 +95,8 @@ def create( cls, value: int, description: str, - name: Optional[str] = None, - color_hint: Optional[str] = None, + name: str | None = None, + color_hint: str | None = None, ) -> Classification: """ Create a new Classification. @@ -148,7 +145,7 @@ def description(self, v: str) -> None: self.properties["description"] = v @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Get or set the name of the class Returns: @@ -157,14 +154,14 @@ def name(self) -> Optional[str]: return self.properties.get("name") @name.setter - def name(self, v: Optional[str]) -> None: + def name(self, v: str | None) -> None: if v is not None: self.properties["name"] = v else: self.properties.pop("name", None) @property - def color_hint(self) -> Optional[str]: + def color_hint(self) -> str | None: """Get or set the optional color hint for this class. The color hint must be a six-character string of capitalized hexadecimal @@ -176,7 +173,7 @@ def color_hint(self) -> Optional[str]: return self.properties.get("color_hint") @color_hint.setter - def color_hint(self, v: Optional[str]) -> None: + def color_hint(self, v: str | None) -> None: if v is not None: match = COLOR_HINT_PATTERN.match(v) assert ( @@ -186,7 +183,7 @@ def color_hint(self, v: Optional[str]) -> None: else: self.properties.pop("color_hint", None) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns the dictionary encoding of this class Returns: @@ -214,19 +211,19 @@ class Bitfield: Use Bitfield.create to create a new Bitfield. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]): + def __init__(self, properties: dict[str, Any]): self.properties = properties def apply( self, offset: int, length: int, - classes: List[Classification], - roles: Optional[List[str]] = None, - description: Optional[str] = None, - name: Optional[str] = None, + classes: list[Classification], + roles: list[str] | None = None, + description: str | None = None, + name: str | None = None, ) -> None: """Sets the properties for this Bitfield. @@ -261,10 +258,10 @@ def create( cls, offset: int, length: int, - classes: List[Classification], - roles: Optional[List[str]] = None, - description: Optional[str] = None, - name: Optional[str] = None, + classes: list[Classification], + roles: list[str] | None = None, + description: str | None = None, + name: str | None = None, ) -> Bitfield: """Sets the properties for this Bitfield. @@ -321,7 +318,7 @@ def length(self, v: int) -> None: self.properties["length"] = v @property - def classes(self) -> List[Classification]: + def classes(self) -> list[Classification]: """Get or set the class definitions for the bitfield Returns: @@ -331,7 +328,7 @@ def classes(self) -> List[Classification]: return [ Classification(d) for d in cast( - List[Dict[str, Any]], + list[dict[str, Any]], get_required( self.properties.get("classes"), self, @@ -341,11 +338,11 @@ def classes(self) -> List[Classification]: ] @classes.setter - def classes(self, v: List[Classification]) -> None: + def classes(self, v: list[Classification]) -> None: self.properties["classes"] = [c.to_dict() for c in v] @property - def roles(self) -> Optional[List[str]]: + def roles(self) -> list[str] | None: """Get or set the role of the bitfield. See `Asset Roles @@ -357,14 +354,14 @@ def roles(self) -> Optional[List[str]]: return self.properties.get("roles") @roles.setter - def roles(self, v: Optional[List[str]]) -> None: + def roles(self, v: list[str] | None) -> None: if v is not None: self.properties["roles"] = v else: self.properties.pop("roles", None) @property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Get or set the optional description of a bitfield. Returns: @@ -373,14 +370,14 @@ def description(self) -> Optional[str]: return self.properties.get("description") @description.setter - def description(self, v: Optional[str]) -> None: + def description(self, v: str | None) -> None: if v is not None: self.properties["description"] = v else: self.properties.pop("description", None) @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Get or set the optional name of the bitfield. Returns: @@ -389,7 +386,7 @@ def name(self) -> Optional[str]: return self.properties.get("name") @name.setter - def name(self, v: Optional[str]) -> None: + def name(self, v: str | None) -> None: if v is not None: self.properties["name"] = v else: @@ -401,7 +398,7 @@ def __repr__(self) -> str: f"classes={self.classes}>" ) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns the dictionary encoding of this bitfield Returns: @@ -428,13 +425,13 @@ class ClassificationExtension( """ name: Literal["classification"] = "classification" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" def apply( self, - classes: Optional[List[Classification]] = None, - bitfields: Optional[List[Bitfield]] = None, + classes: list[Classification] | None = None, + bitfields: list[Bitfield] | None = None, ) -> None: """Applies the classifiation extension fields to the extended object. @@ -455,7 +452,7 @@ def apply( self.bitfields = bitfields @property - def classes(self) -> Optional[List[Classification]]: + def classes(self) -> list[Classification] | None: """Get or set the classes for the base object Note: Setting the classes will clear the object's bitfields if they are @@ -467,21 +464,21 @@ def classes(self) -> Optional[List[Classification]]: return self._get_classes() @classes.setter - def classes(self, v: Optional[List[Classification]]) -> None: + def classes(self, v: list[Classification] | None) -> None: if self._get_bitfields() is not None: self.bitfields = None self._set_property( CLASSES_PROP, map_opt(lambda classes: [c.to_dict() for c in classes], v) ) - def _get_classes(self) -> Optional[List[Classification]]: + def _get_classes(self) -> list[Classification] | None: return map_opt( lambda classes: [Classification(c) for c in classes], - self._get_property(CLASSES_PROP, List[Dict[str, Any]]), + self._get_property(CLASSES_PROP, list[dict[str, Any]]), ) @property - def bitfields(self) -> Optional[List[Bitfield]]: + def bitfields(self) -> list[Bitfield] | None: """Get or set the bitfields for the base object Note: Setting the bitfields will clear the object's classes if they are @@ -493,7 +490,7 @@ def bitfields(self) -> Optional[List[Bitfield]]: return self._get_bitfields() @bitfields.setter - def bitfields(self, v: Optional[List[Bitfield]]) -> None: + def bitfields(self, v: list[Bitfield] | None) -> None: if self._get_classes() is not None: self.classes = None self._set_property( @@ -501,10 +498,10 @@ def bitfields(self, v: Optional[List[Bitfield]]) -> None: map_opt(lambda bitfields: [b.to_dict() for b in bitfields], v), ) - def _get_bitfields(self) -> Optional[List[Bitfield]]: + def _get_bitfields(self) -> list[Bitfield] | None: return map_opt( lambda bitfields: [Bitfield(b) for b in bitfields], - self._get_property(BITFIELDS_PROP, List[Dict[str, Any]]), + self._get_property(BITFIELDS_PROP, list[dict[str, Any]]), ) @classmethod @@ -512,7 +509,7 @@ def get_schema_uri(cls) -> str: return SCHEMA_URI_PATTERN.format(version=DEFAULT_VERSION) @classmethod - def get_schema_uris(cls) -> List[str]: + def get_schema_uris(cls) -> list[str]: warnings.warn( "get_schema_uris is deprecated and will be removed in v2", DeprecationWarning, @@ -561,7 +558,7 @@ def summaries( class ItemClassificationExtension(ClassificationExtension[pystac.Item]): item: pystac.Item - properties: Dict[str, Any] + properties: dict[str, Any] def __init__(self, item: pystac.Item): self.item = item @@ -574,8 +571,8 @@ def __repr__(self) -> str: class AssetClassificationExtension(ClassificationExtension[pystac.Asset]): asset: pystac.Asset asset_href: str - properties: Dict[str, Any] - additional_read_properties: Optional[Iterable[Dict[str, Any]]] + properties: dict[str, Any] + additional_read_properties: Iterable[dict[str, Any]] | None def __init__(self, asset: pystac.Asset): self.asset = asset @@ -591,7 +588,7 @@ def __repr__(self) -> str: class ItemAssetsClassificationExtension( ClassificationExtension[item_assets.AssetDefinition] ): - properties: Dict[str, Any] + properties: dict[str, Any] asset_defn: item_assets.AssetDefinition def __init__(self, item_asset: item_assets.AssetDefinition): @@ -603,7 +600,7 @@ def __repr__(self) -> str: class RasterBandClassificationExtension(ClassificationExtension[RasterBand]): - properties: Dict[str, Any] + properties: dict[str, Any] raster_band: RasterBand def __init__(self, raster_band: RasterBand): @@ -616,25 +613,25 @@ def __repr__(self) -> str: class SummariesClassificationExtension(SummariesExtension): @property - def classes(self) -> Optional[List[Classification]]: + def classes(self) -> list[Classification] | None: return map_opt( lambda classes: [Classification(c) for c in classes], self.summaries.get_list(CLASSES_PROP), ) @classes.setter - def classes(self, v: Optional[List[Classification]]) -> None: + def classes(self, v: list[Classification] | None) -> None: self._set_summary(CLASSES_PROP, map_opt(lambda x: [c.to_dict() for c in x], v)) @property - def bitfields(self) -> Optional[List[Bitfield]]: + def bitfields(self) -> list[Bitfield] | None: return map_opt( lambda bitfields: [Bitfield(b) for b in bitfields], self.summaries.get_list(BITFIELDS_PROP), ) @bitfields.setter - def bitfields(self, v: Optional[List[Bitfield]]) -> None: + def bitfields(self, v: list[Bitfield] | None) -> None: self._set_summary( BITFIELDS_PROP, map_opt(lambda x: [b.to_dict() for b in x], v) ) @@ -650,7 +647,7 @@ class ClassificationExtensionHooks(ExtensionHooks): stac_object_types = {pystac.STACObjectType.ITEM} def migrate( - self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: if SCHEMA_URI_PATTERN.format(version="1.0.0") in info.extensions: for asset in obj.get("assets", {}).values(): diff --git a/pystac/extensions/datacube.py b/pystac/extensions/datacube.py index eee9ad473..bcebd684d 100644 --- a/pystac/extensions/datacube.py +++ b/pystac/extensions/datacube.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC -from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar, Union, cast +from typing import Any, Generic, Literal, TypeVar, Union, cast import pystac from pystac.extensions import item_assets @@ -68,13 +68,13 @@ class Dimension(ABC): ` docs for details. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]) -> None: + def __init__(self, properties: dict[str, Any]) -> None: self.properties = properties @property - def dim_type(self) -> Union[DimensionType, str]: + def dim_type(self) -> DimensionType | str: """The type of the dimension. Must be ``"spatial"`` for :stac-ext:`Horizontal Spatial Dimension Objects ` or :stac-ext:`Vertical Spatial Dimension Objects @@ -87,27 +87,27 @@ def dim_type(self) -> Union[DimensionType, str]: ) @dim_type.setter - def dim_type(self, v: Union[DimensionType, str]) -> None: + def dim_type(self, v: DimensionType | str) -> None: self.properties[DIM_TYPE_PROP] = v @property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Detailed multi-line description to explain the dimension. `CommonMark 0.29 `__ syntax MAY be used for rich text representation.""" return self.properties.get(DIM_DESC_PROP) @description.setter - def description(self, v: Optional[str]) -> None: + def description(self, v: str | None) -> None: if v is None: self.properties.pop(DIM_DESC_PROP, None) else: self.properties[DIM_DESC_PROP] = v - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return self.properties @staticmethod - def from_dict(d: Dict[str, Any]) -> Dimension: + def from_dict(d: dict[str, Any]) -> Dimension: dim_type: str = get_required( d.get(DIM_TYPE_PROP), "cube_dimension", DIM_TYPE_PROP ) @@ -131,7 +131,7 @@ def from_dict(d: Dict[str, Any]) -> Dimension: class SpatialDimension(Dimension): @property - def extent(self) -> List[float]: + def extent(self) -> list[float]: """Extent (lower and upper bounds) of the dimension as two-dimensional array. Open intervals with ``None`` are not allowed.""" return get_required( @@ -139,28 +139,28 @@ def extent(self) -> List[float]: ) @extent.setter - def extent(self, v: List[float]) -> None: + def extent(self, v: list[float]) -> None: self.properties[DIM_EXTENT_PROP] = v @property - def values(self) -> Optional[List[float]]: + def values(self) -> list[float] | None: """Optional set of all potential values.""" return self.properties.get(DIM_VALUES_PROP) @values.setter - def values(self, v: Optional[List[float]]) -> None: + def values(self, v: list[float] | None) -> None: if v is None: self.properties.pop(DIM_VALUES_PROP, None) else: self.properties[DIM_VALUES_PROP] = v @property - def step(self) -> Optional[float]: + def step(self) -> float | None: """The space between the values. Use ``None`` for irregularly spaced steps.""" return self.properties.get(DIM_STEP_PROP) @step.setter - def step(self, v: Optional[float]) -> None: + def step(self, v: float | None) -> None: self.properties[DIM_STEP_PROP] = v def clear_step(self) -> None: @@ -170,7 +170,7 @@ def clear_step(self) -> None: self.properties.pop(DIM_STEP_PROP, None) @property - def reference_system(self) -> Optional[Union[str, float, Dict[str, Any]]]: + def reference_system(self) -> str | float | dict[str, Any] | None: """The spatial reference system for the data, specified as `numerical EPSG code `__, `WKT2 (ISO 19162) string `__ or `PROJJSON @@ -179,7 +179,7 @@ def reference_system(self) -> Optional[Union[str, float, Dict[str, Any]]]: return self.properties.get(DIM_REF_SYS_PROP) @reference_system.setter - def reference_system(self, v: Optional[Union[str, float, Dict[str, Any]]]) -> None: + def reference_system(self, v: str | float | dict[str, Any] | None) -> None: if v is None: self.properties.pop(DIM_REF_SYS_PROP, None) else: @@ -212,13 +212,13 @@ def axis(self, v: VerticalSpatialDimensionAxis) -> None: self.properties[DIM_AXIS_PROP] = v @property - def unit(self) -> Optional[str]: + def unit(self) -> str | None: """The unit of measurement for the data, preferably compliant to `UDUNITS-2 `__ units (singular).""" return self.properties.get(DIM_UNIT_PROP) @unit.setter - def unit(self, v: Optional[str]) -> None: + def unit(self, v: str | None) -> None: if v is None: self.properties.pop(DIM_UNIT_PROP, None) else: @@ -227,7 +227,7 @@ def unit(self, v: Optional[str]) -> None: class TemporalDimension(Dimension): @property - def extent(self) -> Optional[List[Optional[str]]]: + def extent(self) -> list[str | None] | None: """Extent (lower and upper bounds) of the dimension as two-dimensional array. The dates and/or times must be strings compliant to `ISO 8601 `__. ``None`` is allowed for open date @@ -235,35 +235,35 @@ def extent(self) -> Optional[List[Optional[str]]]: return self.properties.get(DIM_EXTENT_PROP) @extent.setter - def extent(self, v: Optional[List[Optional[str]]]) -> None: + def extent(self, v: list[str | None] | None) -> None: if v is None: self.properties.pop(DIM_EXTENT_PROP, None) else: self.properties[DIM_EXTENT_PROP] = v @property - def values(self) -> Optional[List[str]]: + def values(self) -> list[str] | None: """If the dimension consists of set of specific values they can be listed here. The dates and/or times must be strings compliant to `ISO 8601 `__.""" return self.properties.get(DIM_VALUES_PROP) @values.setter - def values(self, v: Optional[List[str]]) -> None: + def values(self, v: list[str] | None) -> None: if v is None: self.properties.pop(DIM_VALUES_PROP, None) else: self.properties[DIM_VALUES_PROP] = v @property - def step(self) -> Optional[str]: + def step(self) -> str | None: """The space between the temporal instances as `ISO 8601 duration `__, e.g. P1D. Use null for irregularly spaced steps.""" return self.properties.get(DIM_STEP_PROP) @step.setter - def step(self, v: Optional[str]) -> None: + def step(self, v: str | None) -> None: self.properties[DIM_STEP_PROP] = v def clear_step(self) -> None: @@ -275,7 +275,7 @@ def clear_step(self) -> None: class AdditionalDimension(Dimension): @property - def extent(self) -> Optional[List[Optional[float]]]: + def extent(self) -> list[float | None] | None: """If the dimension consists of `ordinal `__ values, the extent (lower and upper bounds) of the values as two-dimensional array. Use @@ -283,34 +283,34 @@ def extent(self) -> Optional[List[Optional[float]]]: return self.properties.get(DIM_EXTENT_PROP) @extent.setter - def extent(self, v: Optional[List[Optional[float]]]) -> None: + def extent(self, v: list[float | None] | None) -> None: if v is None: self.properties.pop(DIM_EXTENT_PROP, None) else: self.properties[DIM_EXTENT_PROP] = v @property - def values(self) -> Optional[Union[List[str], List[float]]]: + def values(self) -> list[str] | list[float] | None: """A set of all potential values, especially useful for `nominal `__ values.""" return self.properties.get(DIM_VALUES_PROP) @values.setter - def values(self, v: Optional[Union[List[str], List[float]]]) -> None: + def values(self, v: list[str] | list[float] | None) -> None: if v is None: self.properties.pop(DIM_VALUES_PROP, None) else: self.properties[DIM_VALUES_PROP] = v @property - def step(self) -> Optional[float]: + def step(self) -> float | None: """If the dimension consists of `interval `__ values, the space between the values. Use null for irregularly spaced steps.""" return self.properties.get(DIM_STEP_PROP) @step.setter - def step(self, v: Optional[float]) -> None: + def step(self, v: float | None) -> None: self.properties[DIM_STEP_PROP] = v def clear_step(self) -> None: @@ -320,25 +320,25 @@ def clear_step(self) -> None: self.properties.pop(DIM_STEP_PROP, None) @property - def unit(self) -> Optional[str]: + def unit(self) -> str | None: """The unit of measurement for the data, preferably compliant to `UDUNITS-2 units `__ (singular).""" return self.properties.get(DIM_UNIT_PROP) @unit.setter - def unit(self, v: Optional[str]) -> None: + def unit(self, v: str | None) -> None: if v is None: self.properties.pop(DIM_UNIT_PROP, None) else: self.properties[DIM_UNIT_PROP] = v @property - def reference_system(self) -> Optional[Union[str, float, Dict[str, Any]]]: + def reference_system(self) -> str | float | dict[str, Any] | None: """The reference system for the data.""" return self.properties.get(DIM_REF_SYS_PROP) @reference_system.setter - def reference_system(self, v: Optional[Union[str, float, Dict[str, Any]]]) -> None: + def reference_system(self, v: str | float | dict[str, Any] | None) -> None: if v is None: self.properties.pop(DIM_REF_SYS_PROP, None) else: @@ -359,13 +359,13 @@ class Variable: ` docs for details. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]) -> None: + def __init__(self, properties: dict[str, Any]) -> None: self.properties = properties @property - def dimensions(self) -> List[str]: + def dimensions(self) -> list[str]: """The dimensions of the variable. Should refer to keys in the ``cube:dimensions`` object or be an empty list if the variable has no dimensions @@ -377,35 +377,35 @@ def dimensions(self) -> List[str]: ) @dimensions.setter - def dimensions(self, v: List[str]) -> None: + def dimensions(self, v: list[str]) -> None: self.properties[VAR_DIMENSIONS_PROP] = v @property - def var_type(self) -> Union[VariableType, str]: + def var_type(self) -> VariableType | str: """Type of the variable, either ``data`` or ``auxiliary``""" return get_required( self.properties.get(VAR_TYPE_PROP), "cube:variable", VAR_TYPE_PROP ) @var_type.setter - def var_type(self, v: Union[VariableType, str]) -> None: + def var_type(self, v: VariableType | str) -> None: self.properties[VAR_TYPE_PROP] = v @property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Detailed multi-line description to explain the variable. `CommonMark 0.29 `__ syntax MAY be used for rich text representation.""" return self.properties.get(VAR_DESC_PROP) @description.setter - def description(self, v: Optional[str]) -> None: + def description(self, v: str | None) -> None: if v is None: self.properties.pop(VAR_DESC_PROP, None) else: self.properties[VAR_DESC_PROP] = v @property - def extent(self) -> List[Union[float, str, None]]: + def extent(self) -> list[float | str | None]: """If the variable consists of `ordinal values `, the extent (lower and upper bounds) of the values as two-dimensional array. Use ``None`` @@ -415,40 +415,40 @@ def extent(self) -> List[Union[float, str, None]]: ) @extent.setter - def extent(self, v: List[Union[float, str, None]]) -> None: + def extent(self, v: list[float | str | None]) -> None: self.properties[VAR_EXTENT_PROP] = v @property - def values(self) -> Optional[List[Union[float, str]]]: + def values(self) -> list[float | str] | None: """A set of all potential values, especially useful for `nominal values `.""" return self.properties.get(VAR_VALUES_PROP) @values.setter - def values(self, v: Optional[List[Union[float, str]]]) -> None: + def values(self, v: list[float | str] | None) -> None: if v is None: self.properties.pop(VAR_VALUES_PROP) else: self.properties[VAR_VALUES_PROP] = v @property - def unit(self) -> Optional[str]: + def unit(self) -> str | None: """The unit of measurement for the data, preferably compliant to `UDUNITS-2 ` units (singular)""" return self.properties.get(VAR_UNIT_PROP) @unit.setter - def unit(self, v: Optional[str]) -> None: + def unit(self, v: str | None) -> None: if v is None: self.properties.pop(VAR_UNIT_PROP) else: self.properties[VAR_UNIT_PROP] = v @staticmethod - def from_dict(d: Dict[str, Any]) -> Variable: + def from_dict(d: dict[str, Any]) -> Variable: return Variable(d) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return self.properties @@ -476,8 +476,8 @@ class DatacubeExtension( def apply( self, - dimensions: Dict[str, Dimension], - variables: Optional[Dict[str, Variable]] = None, + dimensions: dict[str, Dimension], + variables: dict[str, Variable] | None = None, ) -> None: """Applies Datacube Extension properties to the extended :class:`~pystac.Collection`, :class:`~pystac.Item` or :class:`~pystac.Asset`. @@ -492,32 +492,32 @@ def apply( self.variables = variables @property - def dimensions(self) -> Dict[str, Dimension]: + def dimensions(self) -> dict[str, Dimension]: """A dictionary where each key is the name of a dimension and each value is a :class:`~Dimension` object. """ result = get_required( - self._get_property(DIMENSIONS_PROP, Dict[str, Any]), self, DIMENSIONS_PROP + self._get_property(DIMENSIONS_PROP, dict[str, Any]), self, DIMENSIONS_PROP ) return {k: Dimension.from_dict(v) for k, v in result.items()} @dimensions.setter - def dimensions(self, v: Dict[str, Dimension]) -> None: + def dimensions(self, v: dict[str, Dimension]) -> None: self._set_property(DIMENSIONS_PROP, {k: dim.to_dict() for k, dim in v.items()}) @property - def variables(self) -> Optional[Dict[str, Variable]]: + def variables(self) -> dict[str, Variable] | None: """A dictionary where each key is the name of a variable and each value is a :class:`~Variable` object. """ - result = self._get_property(VARIABLES_PROP, Dict[str, Any]) + result = self._get_property(VARIABLES_PROP, dict[str, Any]) if result is None: return None return {k: Variable.from_dict(v) for k, v in result.items()} @variables.setter - def variables(self, v: Optional[Dict[str, Variable]]) -> None: + def variables(self, v: dict[str, Variable] | None) -> None: self._set_property( VARIABLES_PROP, map_opt( @@ -567,14 +567,14 @@ class CollectionDatacubeExtension(DatacubeExtension[pystac.Collection]): """ collection: pystac.Collection - properties: Dict[str, Any] + 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) + return f"" class ItemDatacubeExtension(DatacubeExtension[pystac.Item]): @@ -587,14 +587,14 @@ class ItemDatacubeExtension(DatacubeExtension[pystac.Item]): """ item: pystac.Item - properties: Dict[str, Any] + 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) + return f"" class AssetDatacubeExtension(DatacubeExtension[pystac.Asset]): @@ -607,8 +607,8 @@ class AssetDatacubeExtension(DatacubeExtension[pystac.Asset]): """ asset_href: str - properties: Dict[str, Any] - additional_read_properties: Optional[List[Dict[str, Any]]] + properties: dict[str, Any] + additional_read_properties: list[dict[str, Any]] | None def __init__(self, asset: pystac.Asset): self.asset_href = asset.href @@ -619,11 +619,11 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = None def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" class ItemAssetsDatacubeExtension(DatacubeExtension[item_assets.AssetDefinition]): - properties: Dict[str, Any] + properties: dict[str, Any] asset_defn: item_assets.AssetDefinition def __init__(self, item_asset: item_assets.AssetDefinition): diff --git a/pystac/extensions/eo.py b/pystac/extensions/eo.py index dc4b7b326..024ad459d 100644 --- a/pystac/extensions/eo.py +++ b/pystac/extensions/eo.py @@ -3,15 +3,11 @@ from __future__ import annotations import warnings +from collections.abc import Iterable from typing import ( Any, - Dict, Generic, - Iterable, - List, Literal, - Optional, - Tuple, TypeVar, Union, cast, @@ -32,7 +28,7 @@ T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition) SCHEMA_URI: str = "https://stac-extensions.github.io/eo/v1.1.0/schema.json" -SCHEMA_URIS: List[str] = [ +SCHEMA_URIS: list[str] = [ "https://stac-extensions.github.io/eo/v1.0.0/schema.json", SCHEMA_URI, ] @@ -44,7 +40,7 @@ SNOW_COVER_PROP: str = PREFIX + "snow_cover" -def validated_percentage(v: Optional[float]) -> Optional[float]: +def validated_percentage(v: float | None) -> float | None: if v is not None and not isinstance(v, (float, int)) or isinstance(v, bool): raise ValueError(f"Invalid percentage: {v} must be number") if v is not None and not 0 <= v <= 100: @@ -58,19 +54,19 @@ class Band: Use :meth:`Band.create` to create a new Band. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]) -> None: + def __init__(self, properties: dict[str, Any]) -> None: self.properties = properties def apply( self, name: str, - common_name: Optional[str] = None, - description: Optional[str] = None, - center_wavelength: Optional[float] = None, - full_width_half_max: Optional[float] = None, - solar_illumination: Optional[float] = None, + common_name: str | None = None, + description: str | None = None, + center_wavelength: float | None = None, + full_width_half_max: float | None = None, + solar_illumination: float | None = None, ) -> None: """ Sets the properties for this Band. @@ -98,11 +94,11 @@ def apply( def create( cls, name: str, - common_name: Optional[str] = None, - description: Optional[str] = None, - center_wavelength: Optional[float] = None, - full_width_half_max: Optional[float] = None, - solar_illumination: Optional[float] = None, + common_name: str | None = None, + description: str | None = None, + center_wavelength: float | None = None, + full_width_half_max: float | None = None, + solar_illumination: float | None = None, ) -> Band: """ Creates a new band. @@ -144,7 +140,7 @@ def name(self, v: str) -> None: self.properties["name"] = v @property - def common_name(self) -> Optional[str]: + def common_name(self) -> str | None: """Get or sets the name commonly used to refer to the band to make it easier to search for bands across instruments. See the :stac-ext:`list of accepted common names `. @@ -155,14 +151,14 @@ def common_name(self) -> Optional[str]: return self.properties.get("common_name") @common_name.setter - def common_name(self, v: Optional[str]) -> None: + def common_name(self, v: str | None) -> None: if v is not None: self.properties["common_name"] = v else: self.properties.pop("common_name", None) @property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Get or sets the description to fully explain the band. CommonMark 0.29 syntax MAY be used for rich text representation. @@ -172,14 +168,14 @@ def description(self) -> Optional[str]: return self.properties.get("description") @description.setter - def description(self, v: Optional[str]) -> None: + def description(self, v: str | None) -> None: if v is not None: self.properties["description"] = v else: self.properties.pop("description", None) @property - def center_wavelength(self) -> Optional[float]: + def center_wavelength(self) -> float | None: """Get or sets the center wavelength of the band, in micrometers (μm). Returns: @@ -188,14 +184,14 @@ def center_wavelength(self) -> Optional[float]: return self.properties.get("center_wavelength") @center_wavelength.setter - def center_wavelength(self, v: Optional[float]) -> None: + def center_wavelength(self, v: float | None) -> None: if v is not None: self.properties["center_wavelength"] = v else: self.properties.pop("center_wavelength", None) @property - def full_width_half_max(self) -> Optional[float]: + def full_width_half_max(self) -> float | None: """Get or sets the full width at half maximum (FWHM). The width of the band, as measured at half the maximum transmission, in micrometers (μm). @@ -205,14 +201,14 @@ def full_width_half_max(self) -> Optional[float]: return self.properties.get("full_width_half_max") @full_width_half_max.setter - def full_width_half_max(self, v: Optional[float]) -> None: + def full_width_half_max(self, v: float | None) -> None: if v is not None: self.properties["full_width_half_max"] = v else: self.properties.pop("full_width_half_max", None) @property - def solar_illumination(self) -> Optional[float]: + def solar_illumination(self) -> float | None: """Get or sets the solar illumination of the band, as measured at half the maximum transmission, in W/m2/micrometers. @@ -222,16 +218,16 @@ def solar_illumination(self) -> Optional[float]: return self.properties.get("solar_illumination") @solar_illumination.setter - def solar_illumination(self, v: Optional[float]) -> None: + def solar_illumination(self, v: float | None) -> None: if v is not None: self.properties["solar_illumination"] = v else: self.properties.pop("solar_illumination", None) def __repr__(self) -> str: - return "".format(self.name) + return f"" - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this band as a dictionary. Returns: @@ -240,7 +236,7 @@ def to_dict(self) -> Dict[str, Any]: return self.properties @staticmethod - def band_range(common_name: str) -> Optional[Tuple[float, float]]: + def band_range(common_name: str) -> tuple[float, float] | None: """Gets the band range for a common band name. Args: @@ -273,7 +269,7 @@ def band_range(common_name: str) -> Optional[Tuple[float, float]]: return name_to_range.get(common_name) @staticmethod - def band_description(common_name: str) -> Optional[str]: + def band_description(common_name: str) -> str | None: """Returns a description of the band for one with a common name. Args: @@ -286,7 +282,7 @@ def band_description(common_name: str) -> Optional[str]: """ r = Band.band_range(common_name) if r is not None: - return "Common name: {}, Range: {} to {}".format(common_name, r[0], r[1]) + return f"Common name: {common_name}, Range: {r[0]} to {r[1]}" return None @@ -314,9 +310,9 @@ class EOExtension( def apply( self, - bands: Optional[List[Band]] = None, - cloud_cover: Optional[float] = None, - snow_cover: Optional[float] = None, + bands: list[Band] | None = None, + cloud_cover: float | None = None, + snow_cover: float | None = None, ) -> None: """Applies Electro-Optical Extension properties to the extended :class:`~pystac.Item` or :class:`~pystac.Asset`. @@ -336,7 +332,7 @@ def apply( self.snow_cover = validated_percentage(snow_cover) @property - def bands(self) -> Optional[List[Band]]: + def bands(self) -> list[Band] | None: """Gets or sets a list of available bands where each item is a :class:`~Band` object (or ``None`` if no bands have been set). If not available the field should not be provided. @@ -344,19 +340,19 @@ def bands(self) -> Optional[List[Band]]: return self._get_bands() @bands.setter - def bands(self, v: Optional[List[Band]]) -> None: + def bands(self, v: list[Band] | None) -> None: self._set_property( BANDS_PROP, map_opt(lambda bands: [b.to_dict() for b in bands], v) ) - def _get_bands(self) -> Optional[List[Band]]: + def _get_bands(self) -> list[Band] | None: return map_opt( lambda bands: [Band(b) for b in bands], - self._get_property(BANDS_PROP, List[Dict[str, Any]]), + self._get_property(BANDS_PROP, list[dict[str, Any]]), ) @property - def cloud_cover(self) -> Optional[float]: + def cloud_cover(self) -> float | None: """Get or sets the estimate of cloud cover as a percentage (0-100) of the entire scene. If not available the field should not be provided. @@ -366,11 +362,11 @@ def cloud_cover(self) -> Optional[float]: return self._get_property(CLOUD_COVER_PROP, float) @cloud_cover.setter - def cloud_cover(self, v: Optional[float]) -> None: + def cloud_cover(self, v: float | None) -> None: self._set_property(CLOUD_COVER_PROP, validated_percentage(v), pop_if_none=True) @property - def snow_cover(self) -> Optional[float]: + def snow_cover(self) -> float | None: """Get or sets the estimate of snow cover as a percentage (0-100) of the entire scene. If not available the field should not be provided. @@ -380,7 +376,7 @@ def snow_cover(self) -> Optional[float]: return self._get_property(SNOW_COVER_PROP, float) @snow_cover.setter - def snow_cover(self, v: Optional[float]) -> None: + def snow_cover(self, v: float | None) -> None: self._set_property(SNOW_COVER_PROP, validated_percentage(v), pop_if_none=True) @classmethod @@ -388,7 +384,7 @@ def get_schema_uri(cls) -> str: return SCHEMA_URI @classmethod - def get_schema_uris(cls) -> List[str]: + def get_schema_uris(cls) -> list[str]: warnings.warn( "get_schema_uris is deprecated and will be removed in v2", DeprecationWarning, @@ -440,26 +436,26 @@ class ItemEOExtension(EOExtension[pystac.Item]): item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" def __init__(self, item: pystac.Item): self.item = item self.properties = item.properties - def _get_bands(self) -> Optional[List[Band]]: + def _get_bands(self) -> list[Band] | None: """Get or sets a list of :class:`~pystac.Band` objects that represent the available bands. """ - bands = self._get_property(BANDS_PROP, List[Dict[str, Any]]) + bands = self._get_property(BANDS_PROP, list[dict[str, Any]]) # get assets with eo:bands even if not in item if bands is None: - asset_bands: List[Dict[str, Any]] = [] + asset_bands: list[dict[str, Any]] = [] for _, value in self.item.get_assets().items(): if BANDS_PROP in value.extra_fields: asset_bands.extend( - cast(List[Dict[str, Any]], value.extra_fields.get(BANDS_PROP)) + cast(list[dict[str, Any]], value.extra_fields.get(BANDS_PROP)) ) if any(asset_bands): bands = asset_bands @@ -470,9 +466,9 @@ def _get_bands(self) -> Optional[List[Band]]: def get_assets( self, - name: Optional[str] = None, - common_name: Optional[str] = None, - ) -> Dict[str, pystac.Asset]: + name: str | None = None, + common_name: str | None = None, + ) -> dict[str, pystac.Asset]: """Get the item's assets where eo:bands are defined. Args: @@ -498,7 +494,7 @@ def get_assets( } def __repr__(self) -> str: - return "".format(self.item.id) + return f"" class AssetEOExtension(EOExtension[pystac.Asset]): @@ -513,20 +509,20 @@ class AssetEOExtension(EOExtension[pystac.Asset]): asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """If present, this will be a list containing 1 dictionary representing the properties of the owning :class:`~pystac.Item`.""" - def _get_bands(self) -> Optional[List[Band]]: + def _get_bands(self) -> list[Band] | None: if BANDS_PROP not in self.properties: return None return list( map( lambda band: Band(band), - cast(List[Dict[str, Any]], self.properties.get(BANDS_PROP)), + cast(list[dict[str, Any]], self.properties.get(BANDS_PROP)), ) ) @@ -537,20 +533,20 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" class ItemAssetsEOExtension(EOExtension[item_assets.AssetDefinition]): - properties: Dict[str, Any] + properties: dict[str, Any] asset_defn: item_assets.AssetDefinition - def _get_bands(self) -> Optional[List[Band]]: + def _get_bands(self) -> list[Band] | None: if BANDS_PROP not in self.properties: return None return list( map( lambda band: Band(band), - cast(List[Dict[str, Any]], self.properties.get(BANDS_PROP)), + cast(list[dict[str, Any]], self.properties.get(BANDS_PROP)), ) ) @@ -566,7 +562,7 @@ class SummariesEOExtension(SummariesExtension): """ @property - def bands(self) -> Optional[List[Band]]: + def bands(self) -> list[Band] | None: """Get or sets the summary of :attr:`EOExtension.bands` values for this Collection. """ @@ -577,29 +573,29 @@ def bands(self) -> Optional[List[Band]]: ) @bands.setter - def bands(self, v: Optional[List[Band]]) -> None: + def bands(self, v: list[Band] | None) -> None: self._set_summary(BANDS_PROP, map_opt(lambda x: [b.to_dict() for b in x], v)) @property - def cloud_cover(self) -> Optional[RangeSummary[float]]: + def cloud_cover(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`EOExtension.cloud_cover` values for this Collection. """ return self.summaries.get_range(CLOUD_COVER_PROP) @cloud_cover.setter - def cloud_cover(self, v: Optional[RangeSummary[float]]) -> None: + def cloud_cover(self, v: RangeSummary[float] | None) -> None: self._set_summary(CLOUD_COVER_PROP, v) @property - def snow_cover(self) -> Optional[RangeSummary[float]]: + def snow_cover(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`EOExtension.snow_cover` values for this Collection. """ return self.summaries.get_range(SNOW_COVER_PROP) @snow_cover.setter - def snow_cover(self, v: Optional[RangeSummary[float]]) -> None: + def snow_cover(self, v: RangeSummary[float] | None) -> None: self._set_summary(SNOW_COVER_PROP, v) @@ -612,7 +608,7 @@ class EOExtensionHooks(ExtensionHooks): stac_object_types = {pystac.STACObjectType.ITEM} def migrate( - self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: if version < "0.9": # Some eo fields became common_metadata @@ -649,16 +645,16 @@ def migrate( ] for field in eo_to_view_fields: - if "eo:{}".format(field) in obj["properties"]: + if f"eo:{field}" in obj["properties"]: if "stac_extensions" not in obj: obj["stac_extensions"] = [] if view.SCHEMA_URI not in obj["stac_extensions"]: obj["stac_extensions"].append(view.SCHEMA_URI) - if "view:{}".format(field) not in obj["properties"]: - obj["properties"]["view:{}".format(field)] = obj["properties"][ - "eo:{}".format(field) + if f"view:{field}" not in obj["properties"]: + obj["properties"][f"view:{field}"] = obj["properties"][ + f"eo:{field}" ] - del obj["properties"]["eo:{}".format(field)] + del obj["properties"][f"eo:{field}"] # eo:epsg became proj:epsg eo_epsg = PREFIX + "epsg" @@ -689,7 +685,7 @@ def migrate( bands = obj["properties"]["eo:bands"] for asset in obj["assets"].values(): if "eo:bands" in asset: - new_bands: List[Dict[str, Any]] = [] + new_bands: list[dict[str, Any]] = [] for band_index in asset["eo:bands"]: new_bands.append(bands[band_index]) asset["eo:bands"] = new_bands diff --git a/pystac/extensions/ext.py b/pystac/extensions/ext.py index 93a30fee5..c7a213287 100644 --- a/pystac/extensions/ext.py +++ b/pystac/extensions/ext.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, Dict, Generic, Literal, TypeVar, cast +from typing import Any, Generic, Literal, TypeVar, cast import pystac from pystac.extensions.classification import ClassificationExtension @@ -46,7 +46,7 @@ "xarray", ] -EXTENSION_NAME_MAPPING: Dict[EXTENSION_NAMES, Any] = { +EXTENSION_NAME_MAPPING: dict[EXTENSION_NAMES, Any] = { ClassificationExtension.name: ClassificationExtension, DatacubeExtension.name: DatacubeExtension, EOExtension.name: EOExtension, @@ -97,7 +97,7 @@ def cube(self) -> DatacubeExtension[pystac.Collection]: return DatacubeExtension.ext(self.stac_object) @property - def item_assets(self) -> Dict[str, AssetDefinition]: + def item_assets(self) -> dict[str, AssetDefinition]: return ItemAssetsExtension.ext(self.stac_object).item_assets @property diff --git a/pystac/extensions/file.py b/pystac/extensions/file.py index 1d76d228a..d9ef4c749 100644 --- a/pystac/extensions/file.py +++ b/pystac/extensions/file.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Any, Dict, Iterable, List, Literal, Optional, Union +from collections.abc import Iterable +from typing import Any, Literal, Union import pystac from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension @@ -36,12 +37,12 @@ class MappingObject: """Represents a value map used by assets that are used as classification layers, and give details about the values in the asset and their meanings.""" - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]) -> None: + def __init__(self, properties: dict[str, Any]) -> None: self.properties = properties - def apply(self, values: List[Any], summary: str) -> None: + def apply(self, values: list[Any], summary: str) -> None: """Sets the properties for this :class:`~MappingObject` instance. Args: @@ -52,7 +53,7 @@ def apply(self, values: List[Any], summary: str) -> None: self.summary = summary @classmethod - def create(cls, values: List[Any], summary: str) -> MappingObject: + def create(cls, values: list[Any], summary: str) -> MappingObject: """Creates a new :class:`~MappingObject` instance. Args: @@ -64,13 +65,13 @@ def create(cls, values: List[Any], summary: str) -> MappingObject: return m @property - def values(self) -> List[Any]: + def values(self) -> list[Any]: """Gets or sets the list of value(s) in the file. At least one array element is required.""" return get_required(self.properties.get("values"), self, "values") @values.setter - def values(self, v: List[Any]) -> None: + def values(self, v: list[Any]) -> None: self.properties["values"] = v @property @@ -83,10 +84,10 @@ def summary(self, v: str) -> None: self.properties["summary"] = v @classmethod - def from_dict(cls, d: Dict[str, Any]) -> MappingObject: + def from_dict(cls, d: dict[str, Any]) -> MappingObject: return cls.create(**d) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return self.properties @@ -110,10 +111,10 @@ class FileExtension( asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """If present, this will be a list containing 1 dictionary representing the properties of the owning :class:`~pystac.Item`.""" @@ -124,15 +125,15 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" def apply( self, - byte_order: Optional[ByteOrder] = None, - checksum: Optional[str] = None, - header_size: Optional[int] = None, - size: Optional[int] = None, - values: Optional[List[MappingObject]] = None, + byte_order: ByteOrder | None = None, + checksum: str | None = None, + header_size: int | None = None, + size: int | None = None, + values: list[MappingObject] | None = None, ) -> None: """Applies file extension properties to the extended Item. @@ -155,45 +156,45 @@ def apply( self.values = values @property - def byte_order(self) -> Optional[ByteOrder]: + def byte_order(self) -> ByteOrder | None: """Gets or sets the byte order of integer values in the file. One of big-endian or little-endian.""" return self._get_property(BYTE_ORDER_PROP, ByteOrder) @byte_order.setter - def byte_order(self, v: Optional[ByteOrder]) -> None: + def byte_order(self, v: ByteOrder | None) -> None: self._set_property(BYTE_ORDER_PROP, v) @property - def checksum(self) -> Optional[str]: + def checksum(self) -> str | None: """Get or sets the multihash for the corresponding file, encoded as hexadecimal (base 16) string with lowercase letters.""" return self._get_property(CHECKSUM_PROP, str) @checksum.setter - def checksum(self, v: Optional[str]) -> None: + def checksum(self, v: str | None) -> None: self._set_property(CHECKSUM_PROP, v) @property - def header_size(self) -> Optional[int]: + def header_size(self) -> int | None: """Get or sets the header size of the file, in bytes.""" return self._get_property(HEADER_SIZE_PROP, int) @header_size.setter - def header_size(self, v: Optional[int]) -> None: + def header_size(self, v: int | None) -> None: self._set_property(HEADER_SIZE_PROP, v) @property - def size(self) -> Optional[int]: + def size(self) -> int | None: """Get or sets the size of the file, in bytes.""" return self._get_property(SIZE_PROP, int) @size.setter - def size(self, v: Optional[int]) -> None: + def size(self, v: int | None) -> None: self._set_property(SIZE_PROP, v) @property - def values(self) -> Optional[List[MappingObject]]: + def values(self) -> list[MappingObject] | None: """Get or sets the list of :class:`~MappingObject` instances that lists the values that are in the file and describe their meaning. See the :stac-ext:`Mapping Object ` docs for an example. If given, @@ -202,11 +203,11 @@ def values(self) -> Optional[List[MappingObject]]: lambda values: [ MappingObject.from_dict(mapping_obj) for mapping_obj in values ], - self._get_property(VALUES_PROP, List[Dict[str, Any]]), + self._get_property(VALUES_PROP, list[dict[str, Any]]), ) @values.setter - def values(self, v: Optional[List[MappingObject]]) -> None: + def values(self, v: list[MappingObject] | None) -> None: self._set_property( VALUES_PROP, map_opt( @@ -238,10 +239,10 @@ class FileExtensionHooks(ExtensionHooks): stac_object_types = {pystac.STACObjectType.ITEM} def migrate( - self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: # The checksum field was previously it's own extension. - old_checksum: Optional[Dict[str, str]] = None + old_checksum: dict[str, str] | None = None if info.version_range.latest_valid_version() < "v1.0.0-rc.2": if OldExtensionShortIDs.CHECKSUM.value in info.extensions: old_item_checksum = obj["properties"].get("checksum:multihash") diff --git a/pystac/extensions/grid.py b/pystac/extensions/grid.py index e977e1cab..ea857b2ec 100644 --- a/pystac/extensions/grid.py +++ b/pystac/extensions/grid.py @@ -4,14 +4,15 @@ import re import warnings -from typing import Any, Dict, List, Literal, Optional, Pattern, Set, Union +from re import Pattern +from typing import Any, Literal, Union import pystac from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension from pystac.extensions.hooks import ExtensionHooks SCHEMA_URI: str = "https://stac-extensions.github.io/grid/v1.1.0/schema.json" -SCHEMA_URIS: List[str] = [ +SCHEMA_URIS: list[str] = [ "https://stac-extensions.github.io/grid/v1.0.0/schema.json", SCHEMA_URI, ] @@ -56,7 +57,7 @@ class GridExtension( item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" def __init__(self, item: pystac.Item): @@ -64,7 +65,7 @@ def __init__(self, item: pystac.Item): self.properties = item.properties def __repr__(self) -> str: - return "".format(self.item.id) + return f"" def apply(self, code: str) -> None: """Applies Grid extension properties to the extended Item. @@ -75,7 +76,7 @@ def apply(self, code: str) -> None: self.code = validated_code(code) @property - def code(self) -> Optional[str]: + def code(self) -> str | None: """Get or sets the latitude band of the datasource.""" return self._get_property(CODE_PROP, str) @@ -88,7 +89,7 @@ def get_schema_uri(cls) -> str: return SCHEMA_URI @classmethod - def get_schema_uris(cls) -> List[str]: + def get_schema_uris(cls) -> list[str]: warnings.warn( "get_schema_uris is deprecated and will be removed in v2", DeprecationWarning, @@ -115,7 +116,7 @@ def ext(cls, obj: pystac.Item, add_if_missing: bool = False) -> GridExtension: class GridExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = {*[uri for uri in SCHEMA_URIS if uri != SCHEMA_URI]} + prev_extension_ids: set[str] = {*[uri for uri in SCHEMA_URIS if uri != SCHEMA_URI]} stac_object_types = {pystac.STACObjectType.ITEM} diff --git a/pystac/extensions/hooks.py b/pystac/extensions/hooks.py index f1e035392..f858bc84c 100644 --- a/pystac/extensions/hooks.py +++ b/pystac/extensions/hooks.py @@ -1,8 +1,9 @@ from __future__ import annotations from abc import ABC, abstractmethod +from collections.abc import Iterable from functools import lru_cache -from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Set, Union +from typing import TYPE_CHECKING, Any import pystac from pystac.serialization.identify import STACJSONDescription, STACVersionID @@ -20,7 +21,7 @@ def schema_uri(self) -> str: @property @abstractmethod - def prev_extension_ids(self) -> Set[str]: + def prev_extension_ids(self) -> set[str]: """A set of previous extension IDs (schema URIs or old short ids) that should be migrated to the latest schema URI in the 'stac_extensions' property. Override with a class attribute so that the set of previous @@ -30,22 +31,20 @@ def prev_extension_ids(self) -> Set[str]: @property @abstractmethod - def stac_object_types(self) -> Set[pystac.STACObjectType]: + def stac_object_types(self) -> set[pystac.STACObjectType]: """A set of STACObjectType for which migration logic will be applied.""" raise NotImplementedError - @lru_cache() - def _get_stac_object_types(self) -> Set[str]: + @lru_cache + def _get_stac_object_types(self) -> set[str]: """Translation of stac_object_types to strings, cached""" - return set([x.value for x in self.stac_object_types]) + return {x.value for x in self.stac_object_types} - def get_object_links( - self, obj: STACObject - ) -> Optional[List[Union[str, pystac.RelType]]]: + def get_object_links(self, obj: STACObject) -> list[str | pystac.RelType] | None: return None def migrate( - self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: """Migrate a STAC Object in dict format from a previous version. The base implementation will update the stac_extensions to the latest @@ -66,16 +65,16 @@ def migrate( class RegisteredExtensionHooks: - hooks: Dict[str, ExtensionHooks] + hooks: dict[str, ExtensionHooks] def __init__(self, hooks: Iterable[ExtensionHooks]): - self.hooks = dict([(e.schema_uri, e) for e in hooks]) + self.hooks = {e.schema_uri: e for e in hooks} def add_extension_hooks(self, hooks: ExtensionHooks) -> None: e_id = hooks.schema_uri if e_id in self.hooks: raise pystac.ExtensionAlreadyExistsError( - "ExtensionDefinition with id '{}' already exists.".format(e_id) + f"ExtensionDefinition with id '{e_id}' already exists." ) self.hooks[e_id] = hooks @@ -84,10 +83,8 @@ def remove_extension_hooks(self, extension_id: str) -> None: if extension_id in self.hooks: del self.hooks[extension_id] - def get_extended_object_links( - self, obj: STACObject - ) -> List[Union[str, pystac.RelType]]: - result: Optional[List[Union[str, pystac.RelType]]] = None + def get_extended_object_links(self, obj: STACObject) -> list[str | pystac.RelType]: + result: list[str | pystac.RelType] | None = None for ext in obj.stac_extensions: if ext in self.hooks: ext_result = self.hooks[ext].get_object_links(obj) @@ -99,7 +96,7 @@ def get_extended_object_links( return result or [] def migrate( - self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: for hooks in self.hooks.values(): if info.object_type in hooks._get_stac_object_types(): diff --git a/pystac/extensions/item_assets.py b/pystac/extensions/item_assets.py index 33f9b6627..37c3b72d1 100644 --- a/pystac/extensions/item_assets.py +++ b/pystac/extensions/item_assets.py @@ -3,7 +3,7 @@ from __future__ import annotations from copy import deepcopy -from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional +from typing import TYPE_CHECKING, Any, Literal import pystac from pystac.extensions.base import ExtensionManagementMixin @@ -31,12 +31,12 @@ class AssetDefinition: See the :stac-ext:`Asset Object ` for details. """ - properties: Dict[str, Any] + properties: dict[str, Any] - owner: Optional[pystac.Collection] + owner: pystac.Collection | None def __init__( - self, properties: Dict[str, Any], owner: Optional[pystac.Collection] = None + self, properties: dict[str, Any], owner: pystac.Collection | None = None ) -> None: self.properties = properties self.owner = owner @@ -49,11 +49,11 @@ def __eq__(self, o: object) -> bool: @classmethod def create( cls, - title: Optional[str], - description: Optional[str], - media_type: Optional[str], - roles: Optional[List[str]], - extra_fields: Optional[Dict[str, Any]] = None, + title: str | None, + description: str | None, + media_type: str | None, + roles: list[str] | None, + extra_fields: dict[str, Any] | None = None, ) -> AssetDefinition: """ Creates a new asset definition. @@ -85,11 +85,11 @@ def create( def apply( self, - title: Optional[str], - description: Optional[str], - media_type: Optional[str], - roles: Optional[List[str]], - extra_fields: Optional[Dict[str, Any]] = None, + title: str | None, + description: str | None, + media_type: str | None, + roles: list[str] | None, + extra_fields: dict[str, Any] | None = None, ) -> None: """ Sets the properties for this asset definition. @@ -128,60 +128,60 @@ def set_owner(self, obj: pystac.Collection) -> None: self.owner = obj @property - def title(self) -> Optional[str]: + def title(self) -> str | None: """Gets or sets the displayed title for clients and users.""" return self.properties.get(ASSET_TITLE_PROP) @title.setter - def title(self, v: Optional[str]) -> None: + def title(self, v: str | None) -> None: if v is None: self.properties.pop(ASSET_TITLE_PROP, None) else: self.properties[ASSET_TITLE_PROP] = v @property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Gets or sets a description of the Asset providing additional details, such as how it was processed or created. `CommonMark 0.29 `__ syntax MAY be used for rich text representation.""" return self.properties.get(ASSET_DESC_PROP) @description.setter - def description(self, v: Optional[str]) -> None: + def description(self, v: str | None) -> None: if v is None: self.properties.pop(ASSET_DESC_PROP, None) else: self.properties[ASSET_DESC_PROP] = v @property - def media_type(self) -> Optional[str]: + def media_type(self) -> str | None: """Gets or sets the `media type `__ of the asset.""" return self.properties.get(ASSET_TYPE_PROP) @media_type.setter - def media_type(self, v: Optional[str]) -> None: + def media_type(self, v: str | None) -> None: if v is None: self.properties.pop(ASSET_TYPE_PROP, None) else: self.properties[ASSET_TYPE_PROP] = v @property - def roles(self) -> Optional[List[str]]: + def roles(self) -> list[str] | None: """Gets or sets the `semantic roles `__ of the asset, similar to the use of rel in links.""" return self.properties.get(ASSET_ROLES_PROP) @roles.setter - def roles(self, v: Optional[List[str]]) -> None: + def roles(self, v: list[str] | None) -> None: if v is None: self.properties.pop(ASSET_ROLES_PROP, None) else: self.properties[ASSET_ROLES_PROP] = v - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns a dictionary representing this ``AssetDefinition``.""" return deepcopy(self.properties) @@ -228,16 +228,16 @@ def __init__(self, collection: pystac.Collection) -> None: self.collection = collection @property - def item_assets(self) -> Dict[str, AssetDefinition]: + def item_assets(self) -> dict[str, AssetDefinition]: """Gets or sets a dictionary of assets that can be found in member Items. Maps the asset key to an :class:`AssetDefinition` instance.""" - result: Dict[str, Any] = get_required( + result: dict[str, Any] = get_required( self.collection.extra_fields.get(ITEM_ASSETS_PROP), self, ITEM_ASSETS_PROP ) return {k: AssetDefinition(v, self.collection) for k, v in result.items()} @item_assets.setter - def item_assets(self, v: Dict[str, AssetDefinition]) -> None: + def item_assets(self, v: dict[str, AssetDefinition]) -> None: self.collection.extra_fields[ITEM_ASSETS_PROP] = { k: asset_def.properties for k, asset_def in v.items() } @@ -273,7 +273,7 @@ class ItemAssetsExtensionHooks(ExtensionHooks): stac_object_types = {pystac.STACObjectType.COLLECTION} def migrate( - self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: # Handle that the "item-assets" extension had the id of "assets", before # collection assets (since removed) took over the ID of "assets" diff --git a/pystac/extensions/label.py b/pystac/extensions/label.py index 1dbd65fbe..a94a2b003 100644 --- a/pystac/extensions/label.py +++ b/pystac/extensions/label.py @@ -3,7 +3,8 @@ from __future__ import annotations import warnings -from typing import Any, Dict, Iterable, List, Literal, Optional, Sequence, Union, cast +from collections.abc import Iterable, Sequence +from typing import Any, Literal, Union, cast import pystac from pystac.extensions.base import ExtensionManagementMixin, SummariesExtension @@ -70,15 +71,15 @@ class LabelClasses: Use :meth:`LabelClasses.create` to create a new instance from property values. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]): + def __init__(self, properties: dict[str, Any]): self.properties = properties def apply( self, - classes: Sequence[Union[str, int, float]], - name: Optional[str] = None, + classes: Sequence[str | int | float], + name: str | None = None, ) -> None: """Sets the properties for this instance. @@ -94,8 +95,8 @@ def apply( @classmethod def create( cls, - classes: Sequence[Union[str, int, float]], - name: Optional[str] = None, + classes: Sequence[str | int | float], + name: str | None = None, ) -> LabelClasses: """Creates a new :class:`~LabelClasses` instance. @@ -110,23 +111,23 @@ def create( return c @property - def classes(self) -> Sequence[Union[str, int, float]]: + def classes(self) -> Sequence[str | int | float]: """Gets or sets the class values.""" return get_required(self.properties.get("classes"), self, "classes") @classes.setter - def classes(self, v: Sequence[Union[str, int, float]]) -> None: + def classes(self, v: Sequence[str | int | float]) -> None: self.properties["classes"] = v @property - def name(self) -> Optional[str]: + def name(self) -> str | None: """Gets or sets the property key within each Feature in the asset corresponding to class labels. If labels are raster-formatted, use ``None``. """ return self.properties.get("name") @name.setter - def name(self, v: Optional[str]) -> None: + def name(self, v: str | None) -> None: # The "name" property is required but may be null self.properties["name"] = v @@ -144,7 +145,7 @@ def __eq__(self, o: object) -> bool: return self.to_dict() == o - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this label classes object as a dictionary.""" return self.properties @@ -155,9 +156,9 @@ class LabelCount: Use :meth:`LabelCount.create` to create a new instance. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]): + def __init__(self, properties: dict[str, Any]): self.properties = properties def apply(self, name: str, count: int) -> None: @@ -200,7 +201,7 @@ def count(self) -> int: def count(self, v: int) -> None: self.properties["count"] = v - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this label count object as a dictionary.""" return self.properties @@ -220,9 +221,9 @@ class LabelStatistics: Use :meth:`LabelStatistics.create` to create a new instance. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]) -> None: + def __init__(self, properties: dict[str, Any]) -> None: self.properties = properties def apply(self, name: str, value: float) -> None: @@ -265,7 +266,7 @@ def value(self) -> float: def value(self, v: float) -> None: self.properties["value"] = v - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this label statistics object as a dictionary.""" return self.properties @@ -286,16 +287,16 @@ class LabelOverview: Use :meth:`LabelOverview.create` to create a new instance. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]): + def __init__(self, properties: dict[str, Any]): self.properties = properties def apply( self, - property_key: Optional[str], - counts: Optional[List[LabelCount]] = None, - statistics: Optional[List[LabelStatistics]] = None, + property_key: str | None, + counts: list[LabelCount] | None = None, + statistics: list[LabelStatistics] | None = None, ) -> None: """Sets the properties for this instance. @@ -318,9 +319,9 @@ class labels that these counts or statistics are referencing. If the @classmethod def create( cls, - property_key: Optional[str], - counts: Optional[List[LabelCount]] = None, - statistics: Optional[List[LabelStatistics]] = None, + property_key: str | None, + counts: list[LabelCount] | None = None, + statistics: list[LabelStatistics] | None = None, ) -> LabelOverview: """Creates a new instance. @@ -340,17 +341,17 @@ class labels. return x @property - def property_key(self) -> Optional[str]: + def property_key(self) -> str | None: """Gets or sets the property key within the asset corresponding to class labels.""" return self.properties.get("property_key") @property_key.setter - def property_key(self, v: Optional[str]) -> None: + def property_key(self, v: str | None) -> None: self.properties["property_key"] = v @property - def counts(self) -> Optional[List[LabelCount]]: + def counts(self) -> list[LabelCount] | None: """Gets or sets the list of :class:`LabelCounts` containing counts for categorical data.""" counts = self.properties.get("counts") @@ -359,19 +360,17 @@ def counts(self) -> Optional[List[LabelCount]]: return [LabelCount(c) for c in counts] @counts.setter - def counts(self, v: Optional[List[LabelCount]]) -> None: + def counts(self, v: list[LabelCount] | None) -> None: if v is None: self.properties.pop("counts", None) else: if not isinstance(v, list): - raise pystac.STACError( - "counts must be a list! Invalid input: {}".format(v) - ) + raise pystac.STACError(f"counts must be a list! Invalid input: {v}") self.properties["counts"] = [c.to_dict() for c in v] @property - def statistics(self) -> Optional[List[LabelStatistics]]: + def statistics(self) -> list[LabelStatistics] | None: """Gets or sets the list of :class:`LabelStatistics` containing statistics for regression/continuous numeric value data.""" statistics = self.properties.get("statistics") @@ -381,13 +380,13 @@ def statistics(self) -> Optional[List[LabelStatistics]]: return [LabelStatistics(s) for s in statistics] @statistics.setter - def statistics(self, v: Optional[List[LabelStatistics]]) -> None: + def statistics(self, v: list[LabelStatistics] | None) -> None: if v is None: self.properties.pop("statistics", None) else: self.properties["statistics"] = [s.to_dict() for s in v] - def merge_counts(self, other: "LabelOverview") -> LabelOverview: + def merge_counts(self, other: LabelOverview) -> LabelOverview: """Merges the counts associated with this overview with another overview. Creates a new instance. @@ -406,9 +405,9 @@ def merge_counts(self, other: "LabelOverview") -> LabelOverview: if other.counts is None: new_counts = self.counts else: - count_by_prop: Dict[str, int] = {} + count_by_prop: dict[str, int] = {} - def add_counts(counts: List[LabelCount]) -> None: + def add_counts(counts: list[LabelCount]) -> None: for c in counts: if c.name not in count_by_prop: count_by_prop[c.name] = c.count @@ -420,7 +419,7 @@ def add_counts(counts: List[LabelCount]) -> None: new_counts = [LabelCount.create(k, v) for k, v in count_by_prop.items()] return LabelOverview.create(self.property_key, counts=new_counts) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this label overview as a dictionary.""" return self.properties @@ -459,11 +458,11 @@ def apply( self, label_description: str, label_type: LabelType, - label_properties: Optional[List[str]] = None, - label_classes: Optional[List[LabelClasses]] = None, - label_tasks: Optional[List[Union[LabelTask, str]]] = None, - label_methods: Optional[List[Union[LabelMethod, str]]] = None, - label_overviews: Optional[List[LabelOverview]] = None, + label_properties: list[str] | None = None, + label_classes: list[LabelClasses] | None = None, + label_tasks: list[LabelTask | str] | None = None, + label_methods: list[LabelMethod | str] | None = None, + label_overviews: list[LabelOverview] | None = None, ) -> None: """Applies label extension properties to the extended Item. @@ -520,7 +519,7 @@ def label_type(self, v: LabelType) -> None: self.obj.properties[TYPE_PROP] = v @property - def label_properties(self) -> Optional[List[str]]: + def label_properties(self) -> list[str] | None: """Gets or sets the names of the property field(s) in each Feature of the label asset's FeatureCollection that contains the classes (keywords from label:classes if the property defines classes). @@ -528,11 +527,11 @@ def label_properties(self) -> Optional[List[str]]: return self.obj.properties.get(PROPERTIES_PROP) @label_properties.setter - def label_properties(self, v: Optional[List[str]]) -> None: + def label_properties(self, v: list[str] | None) -> None: self.obj.properties[PROPERTIES_PROP] = v @property - def label_classes(self) -> Optional[List[LabelClasses]]: + def label_classes(self) -> list[LabelClasses] | None: """Gets or set a list of :class:`LabelClasses` defining the list of possible class names for each label:properties. (e.g., tree, building, car, hippo). @@ -544,48 +543,48 @@ class names for each label:properties. (e.g., tree, building, car, hippo). return None @label_classes.setter - def label_classes(self, v: Optional[List[LabelClasses]]) -> None: + def label_classes(self, v: list[LabelClasses] | None) -> None: if v is None: self.obj.properties.pop(CLASSES_PROP, None) else: if not isinstance(v, list): raise pystac.STACError( - "label_classes must be a list! Invalid input: {}".format(v) + f"label_classes must be a list! Invalid input: {v}" ) classes = [x.to_dict() for x in v] self.obj.properties[CLASSES_PROP] = classes @property - def label_tasks(self) -> Optional[List[Union[LabelTask, str]]]: + def label_tasks(self) -> list[LabelTask | str] | None: """Gets or set a list of tasks these labels apply to. Usually a subset of 'regression', 'classification', 'detection', or 'segmentation', but may be arbitrary values.""" return self.obj.properties.get(TASKS_PROP) @label_tasks.setter - def label_tasks(self, v: Optional[List[Union[LabelTask, str]]]) -> None: + def label_tasks(self, v: list[LabelTask | str] | None) -> None: if v is None: self.obj.properties.pop(TASKS_PROP, None) else: self.obj.properties[TASKS_PROP] = v @property - def label_methods(self) -> Optional[List[Union[LabelMethod, str]]]: + def label_methods(self) -> list[LabelMethod | str] | None: """Gets or set a list of methods used for labeling. Usually a subset of 'automated' or 'manual', but may be arbitrary values.""" return self.obj.properties.get("label:methods") @label_methods.setter - def label_methods(self, v: Optional[List[Union[LabelMethod, str]]]) -> None: + def label_methods(self, v: list[LabelMethod | str] | None) -> None: if v is None: self.obj.properties.pop("label:methods", None) else: self.obj.properties["label:methods"] = v @property - def label_overviews(self) -> Optional[List[LabelOverview]]: + def label_overviews(self) -> list[LabelOverview] | None: """Gets or set a list of :class:`LabelOverview` instances that store counts (for classification-type data) or summary statistics (for continuous numerical/regression data).""" @@ -596,20 +595,20 @@ def label_overviews(self) -> Optional[List[LabelOverview]]: return None @label_overviews.setter - def label_overviews(self, v: Optional[List[LabelOverview]]) -> None: + def label_overviews(self, v: list[LabelOverview] | None) -> None: if v is None: self.obj.properties.pop(OVERVIEWS_PROP, None) else: self.obj.properties[OVERVIEWS_PROP] = [x.to_dict() for x in v] def __repr__(self) -> str: - return "".format(self.obj.id) + return f"" def add_source( self, source_item: pystac.Item, - title: Optional[str] = None, - assets: Optional[List[str]] = None, + title: str | None = None, + assets: list[str] | None = None, ) -> None: """Adds a link to a source item. @@ -644,9 +643,9 @@ def get_sources(self) -> Iterable[pystac.Item]: def add_labels( self, href: str, - title: Optional[str] = None, - media_type: Optional[str] = None, - properties: Optional[Dict[str, Any]] = None, + title: str | None = None, + media_type: str | None = None, + properties: dict[str, Any] | None = None, ) -> None: """Adds a label asset to this LabelItem. @@ -672,8 +671,8 @@ def add_labels( def add_geojson_labels( self, href: str, - title: Optional[str] = None, - properties: Optional[Dict[str, Any]] = None, + title: str | None = None, + properties: dict[str, Any] | None = None, ) -> None: """Adds a GeoJSON label asset to this LabelItem. @@ -697,7 +696,7 @@ def get_schema_uri(cls) -> str: return SCHEMA_URI @classmethod - def get_schema_uris(cls) -> List[str]: + def get_schema_uris(cls) -> list[str]: warnings.warn( "get_schema_uris is deprecated and will be removed in v2", DeprecationWarning, @@ -733,7 +732,7 @@ class SummariesLabelExtension(SummariesExtension): """ @property - def label_properties(self) -> Optional[List[str]]: + def label_properties(self) -> list[str] | None: """Get or sets the summary of :attr:`LabelExtension.label_properties` values for this Collection. """ @@ -741,11 +740,11 @@ def label_properties(self) -> Optional[List[str]]: return self.summaries.get_list(PROPERTIES_PROP) @label_properties.setter - def label_properties(self, v: Optional[List[str]]) -> None: + def label_properties(self, v: list[str] | None) -> None: self._set_summary(PROPERTIES_PROP, v) @property - def label_classes(self) -> Optional[List[LabelClasses]]: + def label_classes(self) -> list[LabelClasses] | None: """Get or sets the summary of :attr:`LabelExtension.label_classes` values for this Collection. """ @@ -756,13 +755,13 @@ def label_classes(self) -> Optional[List[LabelClasses]]: ) @label_classes.setter - def label_classes(self, v: Optional[List[LabelClasses]]) -> None: + def label_classes(self, v: list[LabelClasses] | None) -> None: self._set_summary( CLASSES_PROP, map_opt(lambda classes: [c.to_dict() for c in classes], v) ) @property - def label_type(self) -> Optional[List[LabelType]]: + def label_type(self) -> list[LabelType] | None: """Get or sets the summary of :attr:`LabelExtension.label_type` values for this Collection. """ @@ -770,11 +769,11 @@ def label_type(self) -> Optional[List[LabelType]]: return self.summaries.get_list(TYPE_PROP) @label_type.setter - def label_type(self, v: Optional[List[LabelType]]) -> None: + def label_type(self, v: list[LabelType] | None) -> None: self._set_summary(TYPE_PROP, v) @property - def label_tasks(self) -> Optional[List[Union[LabelTask, str]]]: + def label_tasks(self) -> list[LabelTask | str] | None: """Get or sets the summary of :attr:`LabelExtension.label_tasks` values for this Collection. """ @@ -782,11 +781,11 @@ def label_tasks(self) -> Optional[List[Union[LabelTask, str]]]: return self.summaries.get_list(TASKS_PROP) @label_tasks.setter - def label_tasks(self, v: Optional[List[Union[LabelTask, str]]]) -> None: + def label_tasks(self, v: list[LabelTask | str] | None) -> None: self._set_summary(TASKS_PROP, v) @property - def label_methods(self) -> Optional[List[Union[LabelMethod, str]]]: + def label_methods(self) -> list[LabelMethod | str] | None: """Get or sets the summary of :attr:`LabelExtension.label_methods` values for this Collection. """ @@ -794,7 +793,7 @@ def label_methods(self) -> Optional[List[Union[LabelMethod, str]]]: return self.summaries.get_list(METHODS_PROP) @label_methods.setter - def label_methods(self, v: Optional[List[Union[LabelMethod, str]]]) -> None: + def label_methods(self, v: list[LabelMethod | str] | None) -> None: self._set_summary(METHODS_PROP, v) @@ -808,13 +807,13 @@ class LabelExtensionHooks(ExtensionHooks): def get_object_links( self, so: pystac.STACObject - ) -> Optional[List[Union[str, pystac.RelType]]]: + ) -> list[str | pystac.RelType] | None: if isinstance(so, pystac.Item): return [LabelRelType.SOURCE] return None def migrate( - self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: if info.object_type == pystac.STACObjectType.ITEM and version < "1.0.0": props = obj["properties"] diff --git a/pystac/extensions/mgrs.py b/pystac/extensions/mgrs.py index ea8aeed96..24b90f494 100644 --- a/pystac/extensions/mgrs.py +++ b/pystac/extensions/mgrs.py @@ -1,7 +1,8 @@ """Implements the :stac-ext:`MGRS Extension `.""" import re -from typing import Any, Dict, FrozenSet, Literal, Optional, Pattern, Set, Union +from re import Pattern +from typing import Any, Literal, Optional, Union import pystac from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension @@ -16,7 +17,7 @@ GRID_SQUARE_PROP: str = PREFIX + "grid_square" # required UTM_ZONE_PROP: str = PREFIX + "utm_zone" -LATITUDE_BANDS: FrozenSet[str] = frozenset( +LATITUDE_BANDS: frozenset[str] = frozenset( { "C", "D", @@ -41,7 +42,7 @@ } ) -UTM_ZONES: FrozenSet[int] = frozenset( +UTM_ZONES: frozenset[int] = frozenset( { 1, 2, @@ -160,7 +161,7 @@ class MgrsExtension( item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" def __init__(self, item: pystac.Item): @@ -168,7 +169,7 @@ def __init__(self, item: pystac.Item): self.properties = item.properties def __repr__(self) -> str: - return "".format(self.item.id) + return f"" def apply( self, @@ -242,7 +243,7 @@ def ext(cls, obj: pystac.Item, add_if_missing: bool = False) -> "MgrsExtension": class MgrsExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set() + prev_extension_ids: set[str] = set() stac_object_types = {pystac.STACObjectType.ITEM} diff --git a/pystac/extensions/pointcloud.py b/pystac/extensions/pointcloud.py index 50af76789..b82566e74 100644 --- a/pystac/extensions/pointcloud.py +++ b/pystac/extensions/pointcloud.py @@ -2,14 +2,11 @@ from __future__ import annotations +from collections.abc import Iterable from typing import ( Any, - Dict, Generic, - Iterable, - List, Literal, - Optional, TypeVar, Union, cast, @@ -66,9 +63,9 @@ class Schema: properties. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]) -> None: + def __init__(self, properties: dict[str, Any]) -> None: self.properties = properties def apply(self, name: str, size: int, type: SchemaType) -> None: @@ -106,7 +103,7 @@ def size(self) -> int: @size.setter def size(self, v: int) -> None: if not isinstance(v, int): - raise pystac.STACError("size must be an int! Invalid input: {}".format(v)) + raise pystac.STACError(f"size must be an int! Invalid input: {v}") self.properties["size"] = v @@ -136,7 +133,7 @@ def __repr__(self) -> str: self.properties.get("type"), ) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this schema as a dictionary.""" return self.properties @@ -147,21 +144,21 @@ class Statistic: Use :meth:`Statistic.create` to create a new instance of ``Statistic`` from property values.""" - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]) -> None: + def __init__(self, properties: dict[str, Any]) -> None: self.properties = properties def apply( self, name: str, - position: Optional[int] = None, - average: Optional[float] = None, - count: Optional[int] = None, - maximum: Optional[float] = None, - minimum: Optional[float] = None, - stddev: Optional[float] = None, - variance: Optional[float] = None, + position: int | None = None, + average: float | None = None, + count: int | None = None, + maximum: float | None = None, + minimum: float | None = None, + stddev: float | None = None, + variance: float | None = None, ) -> None: """Sets the properties for this Statistic. @@ -188,13 +185,13 @@ def apply( def create( cls, name: str, - position: Optional[int] = None, - average: Optional[float] = None, - count: Optional[int] = None, - maximum: Optional[float] = None, - minimum: Optional[float] = None, - stddev: Optional[float] = None, - variance: Optional[float] = None, + position: int | None = None, + average: float | None = None, + count: int | None = None, + maximum: float | None = None, + minimum: float | None = None, + stddev: float | None = None, + variance: float | None = None, ) -> Statistic: """Creates a new Statistic class. @@ -234,93 +231,93 @@ def name(self, v: str) -> None: self.properties.pop("name", None) @property - def position(self) -> Optional[int]: + def position(self) -> int | None: """Gets or sets the position property.""" return self.properties.get("position") @position.setter - def position(self, v: Optional[int]) -> None: + def position(self, v: int | None) -> None: if v is not None: self.properties["position"] = v else: self.properties.pop("position", None) @property - def average(self) -> Optional[float]: + def average(self) -> float | None: """Gets or sets the average property.""" return self.properties.get("average") @average.setter - def average(self, v: Optional[float]) -> None: + def average(self, v: float | None) -> None: if v is not None: self.properties["average"] = v else: self.properties.pop("average", None) @property - def count(self) -> Optional[int]: + def count(self) -> int | None: """Gets or sets the count property.""" return self.properties.get("count") @count.setter - def count(self, v: Optional[int]) -> None: + def count(self, v: int | None) -> None: if v is not None: self.properties["count"] = v else: self.properties.pop("count", None) @property - def maximum(self) -> Optional[float]: + def maximum(self) -> float | None: """Gets or sets the maximum property.""" return self.properties.get("maximum") @maximum.setter - def maximum(self, v: Optional[float]) -> None: + def maximum(self, v: float | None) -> None: if v is not None: self.properties["maximum"] = v else: self.properties.pop("maximum", None) @property - def minimum(self) -> Optional[float]: + def minimum(self) -> float | None: """Gets or sets the minimum property.""" return self.properties.get("minimum") @minimum.setter - def minimum(self, v: Optional[float]) -> None: + def minimum(self, v: float | None) -> None: if v is not None: self.properties["minimum"] = v else: self.properties.pop("minimum", None) @property - def stddev(self) -> Optional[float]: + def stddev(self) -> float | None: """Gets or sets the stddev property.""" return self.properties.get("stddev") @stddev.setter - def stddev(self, v: Optional[float]) -> None: + def stddev(self, v: float | None) -> None: if v is not None: self.properties["stddev"] = v else: self.properties.pop("stddev", None) @property - def variance(self) -> Optional[float]: + def variance(self) -> float | None: """Gets or sets the variance property.""" return self.properties.get("variance") @variance.setter - def variance(self, v: Optional[float]) -> None: + def variance(self, v: float | None) -> None: if v is not None: self.properties["variance"] = v else: self.properties.pop("variance", None) def __repr__(self) -> str: - return "".format(str(self.properties)) + return f"" - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this statistic as a dictionary.""" return self.properties @@ -355,11 +352,11 @@ class PointcloudExtension( def apply( self, count: int, - type: Union[PhenomenologyType, str], + type: PhenomenologyType | str, encoding: str, - schemas: List[Schema], - density: Optional[float] = None, - statistics: Optional[List[Statistic]] = None, + schemas: list[Schema], + density: float | None = None, + statistics: list[Statistic] | None = None, ) -> None: """Applies Pointcloud extension properties to the extended Item. @@ -392,12 +389,12 @@ def count(self, v: int) -> None: self._set_property(COUNT_PROP, v, pop_if_none=False) @property - def type(self) -> Union[PhenomenologyType, str]: + def type(self) -> PhenomenologyType | str: """Gets or sets the phenomenology type for the point cloud.""" return get_required(self._get_property(TYPE_PROP, str), self, TYPE_PROP) @type.setter - def type(self, v: Union[PhenomenologyType, str]) -> None: + def type(self, v: PhenomenologyType | str) -> None: self._set_property(TYPE_PROP, v, pop_if_none=False) @property @@ -410,38 +407,38 @@ def encoding(self, v: str) -> None: self._set_property(ENCODING_PROP, v, pop_if_none=False) @property - def schemas(self) -> List[Schema]: + def schemas(self) -> list[Schema]: """Gets or sets the list of :class:`Schema` instances defining dimensions and types for the data. """ result = get_required( - self._get_property(SCHEMAS_PROP, List[Dict[str, Any]]), self, SCHEMAS_PROP + self._get_property(SCHEMAS_PROP, list[dict[str, Any]]), self, SCHEMAS_PROP ) return [Schema(s) for s in result] @schemas.setter - def schemas(self, v: List[Schema]) -> None: + def schemas(self, v: list[Schema]) -> None: self._set_property(SCHEMAS_PROP, [x.to_dict() for x in v], pop_if_none=False) @property - def density(self) -> Optional[float]: + def density(self) -> float | None: """Gets or sets the number of points per square unit area.""" return self._get_property(DENSITY_PROP, float) @density.setter - def density(self, v: Optional[float]) -> None: + def density(self, v: float | None) -> None: self._set_property(DENSITY_PROP, v) @property - def statistics(self) -> Optional[List[Statistic]]: + def statistics(self) -> list[Statistic] | None: """Gets or sets the list of :class:`Statistic` instances describing the pre-channel statistics. Elements in this list map to elements in the :attr:`PointcloudExtension.schemas` list.""" - result = self._get_property(STATISTICS_PROP, List[Dict[str, Any]]) + result = self._get_property(STATISTICS_PROP, list[dict[str, Any]]) return map_opt(lambda stats: [Statistic(s) for s in stats], result) @statistics.setter - def statistics(self, v: Optional[List[Statistic]]) -> None: + def statistics(self, v: list[Statistic] | None) -> None: set_value = map_opt(lambda stats: [s.to_dict() for s in stats], v) self._set_property(STATISTICS_PROP, set_value) @@ -495,14 +492,14 @@ class ItemPointcloudExtension(PointcloudExtension[pystac.Item]): """ item: pystac.Item - properties: Dict[str, Any] + 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) + return f"" class AssetPointcloudExtension(PointcloudExtension[pystac.Asset]): @@ -517,10 +514,10 @@ class AssetPointcloudExtension(PointcloudExtension[pystac.Asset]): asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """If present, this will be a list containing 1 dictionary representing the properties of the owning :class:`~pystac.Item`.""" @@ -538,7 +535,7 @@ def __repr__(self) -> str: class ItemAssetsPointcloudExtension(PointcloudExtension[item_assets.AssetDefinition]): - properties: Dict[str, Any] + properties: dict[str, Any] asset_defn: item_assets.AssetDefinition def __init__(self, item_asset: item_assets.AssetDefinition): @@ -553,46 +550,46 @@ class SummariesPointcloudExtension(SummariesExtension): """ @property - def count(self) -> Optional[RangeSummary[int]]: + def count(self) -> RangeSummary[int] | None: return self.summaries.get_range(COUNT_PROP) @count.setter - def count(self, v: Optional[RangeSummary[int]]) -> None: + def count(self, v: RangeSummary[int] | None) -> None: self._set_summary(COUNT_PROP, v) @property - def type(self) -> Optional[List[Union[PhenomenologyType, str]]]: + def type(self) -> list[PhenomenologyType | str] | None: return self.summaries.get_list(TYPE_PROP) @type.setter - def type(self, v: Optional[List[Union[PhenomenologyType, str]]]) -> None: + def type(self, v: list[PhenomenologyType | str] | None) -> None: self._set_summary(TYPE_PROP, v) @property - def encoding(self) -> Optional[List[str]]: + def encoding(self) -> list[str] | None: return self.summaries.get_list(ENCODING_PROP) @encoding.setter - def encoding(self, v: Optional[List[str]]) -> None: + def encoding(self, v: list[str] | None) -> None: self._set_summary(ENCODING_PROP, v) @property - def density(self) -> Optional[RangeSummary[float]]: + def density(self) -> RangeSummary[float] | None: return self.summaries.get_range(DENSITY_PROP) @density.setter - def density(self, v: Optional[RangeSummary[float]]) -> None: + def density(self, v: RangeSummary[float] | None) -> None: self._set_summary(DENSITY_PROP, v) @property - def statistics(self) -> Optional[List[Statistic]]: + def statistics(self) -> list[Statistic] | None: return map_opt( lambda stats: [Statistic(d) for d in stats], self.summaries.get_list(STATISTICS_PROP), ) @statistics.setter - def statistics(self, v: Optional[List[Statistic]]) -> None: + def statistics(self, v: list[Statistic] | None) -> None: self._set_summary( STATISTICS_PROP, map_opt(lambda stats: [s.to_dict() for s in stats], v), diff --git a/pystac/extensions/projection.py b/pystac/extensions/projection.py index af24903e1..1982e8b55 100644 --- a/pystac/extensions/projection.py +++ b/pystac/extensions/projection.py @@ -4,14 +4,11 @@ import json import warnings +from collections.abc import Iterable from typing import ( Any, - Dict, Generic, - Iterable, - List, Literal, - Optional, TypeVar, Union, cast, @@ -29,7 +26,7 @@ T = TypeVar("T", pystac.Item, pystac.Asset, item_assets.AssetDefinition) SCHEMA_URI: str = "https://stac-extensions.github.io/projection/v1.1.0/schema.json" -SCHEMA_URIS: List[str] = [ +SCHEMA_URIS: list[str] = [ "https://stac-extensions.github.io/projection/v1.0.0/schema.json", SCHEMA_URI, ] @@ -69,14 +66,14 @@ class ProjectionExtension( def apply( self, - epsg: Optional[int], - wkt2: Optional[str] = None, - projjson: Optional[Dict[str, Any]] = None, - geometry: Optional[Dict[str, Any]] = None, - bbox: Optional[List[float]] = None, - centroid: Optional[Dict[str, float]] = None, - shape: Optional[List[int]] = None, - transform: Optional[List[float]] = None, + epsg: int | None, + wkt2: str | None = None, + projjson: dict[str, Any] | None = None, + geometry: dict[str, Any] | None = None, + bbox: list[float] | None = None, + centroid: dict[str, float] | None = None, + shape: list[int] | None = None, + transform: list[float] | None = None, ) -> None: """Applies Projection extension properties to the extended Item. @@ -110,7 +107,7 @@ def apply( self.transform = transform @property - def epsg(self) -> Optional[int]: + def epsg(self) -> int | None: """Get or sets the EPSG code of the datasource. A Coordinate Reference System (CRS) is the data reference system (sometimes @@ -124,11 +121,11 @@ def epsg(self) -> Optional[int]: return self._get_property(EPSG_PROP, int) @epsg.setter - def epsg(self, v: Optional[int]) -> None: + def epsg(self, v: int | None) -> None: self._set_property(EPSG_PROP, v, pop_if_none=False) @property - def wkt2(self) -> Optional[str]: + def wkt2(self) -> str | None: """Get or sets the WKT2 string representing the Coordinate Reference System (CRS) that the ``proj:geometry`` and ``proj:bbox`` fields represent @@ -141,11 +138,11 @@ def wkt2(self) -> Optional[str]: return self._get_property(WKT2_PROP, str) @wkt2.setter - def wkt2(self, v: Optional[str]) -> None: + def wkt2(self, v: str | None) -> None: self._set_property(WKT2_PROP, v) @property - def projjson(self) -> Optional[Dict[str, Any]]: + def projjson(self) -> dict[str, Any] | None: """Get or sets the PROJJSON string representing the Coordinate Reference System (CRS) that the ``proj:geometry`` and ``proj:bbox`` fields represent @@ -159,14 +156,14 @@ def projjson(self) -> Optional[Dict[str, Any]]: The schema for this object can be found `here `_. """ - return self._get_property(PROJJSON_PROP, Dict[str, Any]) + return self._get_property(PROJJSON_PROP, dict[str, Any]) @projjson.setter - def projjson(self, v: Optional[Dict[str, Any]]) -> None: + def projjson(self, v: dict[str, Any] | None) -> None: self._set_property(PROJJSON_PROP, v) @property - def crs_string(self) -> Optional[str]: + def crs_string(self) -> str | None: """Returns the coordinate reference system (CRS) string for the extension. This string can be used to feed, e.g., ``rasterio.crs.CRS.from_string``. @@ -187,7 +184,7 @@ def crs_string(self) -> Optional[str]: return None @property - def geometry(self) -> Optional[Dict[str, Any]]: + def geometry(self) -> dict[str, Any] | None: """Get or sets a Polygon GeoJSON dict representing the footprint of this item. This dict should be formatted according the Polygon object format specified in @@ -197,14 +194,14 @@ def geometry(self) -> Optional[Dict[str, Any]]: Ideally, this will be represented by a Polygon with five coordinates, as the item in the asset data CRS should be a square aligned to the original CRS grid. """ - return self._get_property(GEOM_PROP, Dict[str, Any]) + return self._get_property(GEOM_PROP, dict[str, Any]) @geometry.setter - def geometry(self, v: Optional[Dict[str, Any]]) -> None: + def geometry(self, v: dict[str, Any] | None) -> None: self._set_property(GEOM_PROP, v) @property - def bbox(self) -> Optional[List[float]]: + def bbox(self) -> list[float] | None: """Get or sets the bounding box of the assets represented by this item in the asset data CRS. @@ -216,14 +213,14 @@ def bbox(self) -> Optional[List[float]]: highest]``. The length of the array must be 2*n where n is the number of dimensions. """ - return self._get_property(BBOX_PROP, List[float]) + return self._get_property(BBOX_PROP, list[float]) @bbox.setter - def bbox(self, v: Optional[List[float]]) -> None: + def bbox(self, v: list[float] | None) -> None: self._set_property(BBOX_PROP, v) @property - def centroid(self) -> Optional[Dict[str, float]]: + def centroid(self) -> dict[str, float] | None: """Get or sets coordinates representing the centroid of the item in the asset data CRS. @@ -234,14 +231,14 @@ def centroid(self) -> Optional[Dict[str, float]]: item.ext.proj.centroid = { 'lat': 0.0, 'lon': 0.0 } """ - return self._get_property(CENTROID_PROP, Dict[str, float]) + return self._get_property(CENTROID_PROP, dict[str, float]) @centroid.setter - def centroid(self, v: Optional[Dict[str, float]]) -> None: + def centroid(self, v: dict[str, float] | None) -> None: self._set_property(CENTROID_PROP, v) @property - def shape(self) -> Optional[List[int]]: + def shape(self) -> list[int] | None: """Get or sets the number of pixels in Y and X directions for the default grid. The shape is an array of integers that represents the number of pixels in the @@ -249,14 +246,14 @@ def shape(self) -> Optional[List[int]]: be specified in Y, X order. If the shape is defined in an item's properties it is used as the default shape for all assets that don't have an overriding shape. """ - return self._get_property(SHAPE_PROP, List[int]) + return self._get_property(SHAPE_PROP, list[int]) @shape.setter - def shape(self, v: Optional[List[int]]) -> None: + def shape(self, v: list[int] | None) -> None: self._set_property(SHAPE_PROP, v) @property - def transform(self) -> Optional[List[float]]: + def transform(self) -> list[float] | None: """Get or sets the the affine transformation coefficients for the default grid. The transform is a linear mapping from pixel coordinate space (Pixel, Line) to @@ -268,10 +265,10 @@ def transform(self) -> Optional[List[float]]: or the Rasterio `Transform `_. """ - return self._get_property(TRANSFORM_PROP, List[float]) + return self._get_property(TRANSFORM_PROP, list[float]) @transform.setter - def transform(self, v: Optional[List[float]]) -> None: + def transform(self, v: list[float] | None) -> None: self._set_property(TRANSFORM_PROP, v) @classmethod @@ -279,7 +276,7 @@ def get_schema_uri(cls) -> str: return SCHEMA_URI @classmethod - def get_schema_uris(cls) -> List[str]: + def get_schema_uris(cls) -> list[str]: warnings.warn( "get_schema_uris is deprecated and will be removed in v2", DeprecationWarning, @@ -331,7 +328,7 @@ class ItemProjectionExtension(ProjectionExtension[pystac.Item]): item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" def __init__(self, item: pystac.Item): @@ -339,7 +336,7 @@ def __init__(self, item: pystac.Item): self.properties = item.properties def __repr__(self) -> str: - return "".format(self.item.id) + return f"" class AssetProjectionExtension(ProjectionExtension[pystac.Asset]): @@ -354,10 +351,10 @@ class AssetProjectionExtension(ProjectionExtension[pystac.Asset]): asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """If present, this will be a list containing 1 dictionary representing the properties of the owning :class:`~pystac.Item`.""" @@ -368,11 +365,11 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" class ItemAssetsProjectionExtension(ProjectionExtension[item_assets.AssetDefinition]): - properties: Dict[str, Any] + properties: dict[str, Any] asset_defn: item_assets.AssetDefinition def __init__(self, item_asset: item_assets.AssetDefinition): @@ -387,14 +384,14 @@ class SummariesProjectionExtension(SummariesExtension): """ @property - def epsg(self) -> Optional[List[int]]: + def epsg(self) -> list[int] | None: """Get or sets the summary of :attr:`ProjectionExtension.epsg` values for this Collection. """ return self.summaries.get_list(EPSG_PROP) @epsg.setter - def epsg(self, v: Optional[List[int]]) -> None: + def epsg(self, v: list[int] | None) -> None: self._set_summary(EPSG_PROP, v) diff --git a/pystac/extensions/raster.py b/pystac/extensions/raster.py index 9cba893fb..ab57d7c3e 100644 --- a/pystac/extensions/raster.py +++ b/pystac/extensions/raster.py @@ -3,15 +3,11 @@ from __future__ import annotations import warnings +from collections.abc import Iterable from typing import ( Any, - Dict, Generic, - Iterable, - List, Literal, - Optional, - Set, TypeVar, Union, cast, @@ -74,18 +70,18 @@ class Statistics: Use Statistics.create to create a new Statistics instance. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Optional[float]]) -> None: + def __init__(self, properties: dict[str, float | None]) -> None: self.properties = properties def apply( self, - minimum: Optional[float] = None, - maximum: Optional[float] = None, - mean: Optional[float] = None, - stddev: Optional[float] = None, - valid_percent: Optional[float] = None, + minimum: float | None = None, + maximum: float | None = None, + mean: float | None = None, + stddev: float | None = None, + valid_percent: float | None = None, ) -> None: """ Sets the properties for this raster Band. @@ -106,11 +102,11 @@ def apply( @classmethod def create( cls, - minimum: Optional[float] = None, - maximum: Optional[float] = None, - mean: Optional[float] = None, - stddev: Optional[float] = None, - valid_percent: Optional[float] = None, + minimum: float | None = None, + maximum: float | None = None, + mean: float | None = None, + stddev: float | None = None, + valid_percent: float | None = None, ) -> Statistics: """ Creates a new band. @@ -133,7 +129,7 @@ def create( return b @property - def minimum(self) -> Optional[float]: + def minimum(self) -> float | None: """Get or sets the minimum pixel value Returns: @@ -142,14 +138,14 @@ def minimum(self) -> Optional[float]: return self.properties.get("minimum") @minimum.setter - def minimum(self, v: Optional[float]) -> None: + def minimum(self, v: float | None) -> None: if v is not None: self.properties["minimum"] = v else: self.properties.pop("minimum", None) @property - def maximum(self) -> Optional[float]: + def maximum(self) -> float | None: """Get or sets the maximum pixel value Returns: @@ -158,14 +154,14 @@ def maximum(self) -> Optional[float]: return self.properties.get("maximum") @maximum.setter - def maximum(self, v: Optional[float]) -> None: + def maximum(self, v: float | None) -> None: if v is not None: self.properties["maximum"] = v else: self.properties.pop("maximum", None) @property - def mean(self) -> Optional[float]: + def mean(self) -> float | None: """Get or sets the mean pixel value Returns: @@ -174,14 +170,14 @@ def mean(self) -> Optional[float]: return self.properties.get("mean") @mean.setter - def mean(self, v: Optional[float]) -> None: + def mean(self, v: float | None) -> None: if v is not None: self.properties["mean"] = v else: self.properties.pop("mean", None) @property - def stddev(self) -> Optional[float]: + def stddev(self) -> float | None: """Get or sets the standard deviation pixel value Returns: @@ -190,14 +186,14 @@ def stddev(self) -> Optional[float]: return self.properties.get("stddev") @stddev.setter - def stddev(self, v: Optional[float]) -> None: + def stddev(self, v: float | None) -> None: if v is not None: self.properties["stddev"] = v else: self.properties.pop("stddev", None) @property - def valid_percent(self) -> Optional[float]: + def valid_percent(self) -> float | None: """Get or sets the Percentage of valid (not nodata) pixel Returns: @@ -206,13 +202,13 @@ def valid_percent(self) -> Optional[float]: return self.properties.get("valid_percent") @valid_percent.setter - def valid_percent(self, v: Optional[float]) -> None: + def valid_percent(self, v: float | None) -> None: if v is not None: self.properties["valid_percent"] = v else: self.properties.pop("valid_percent", None) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns these statistics as a dictionary. Returns: @@ -221,7 +217,7 @@ def to_dict(self) -> Dict[str, Any]: return self.properties @staticmethod - def from_dict(d: Dict[str, Any]) -> Statistics: + def from_dict(d: dict[str, Any]) -> Statistics: """Constructs an Statistics from a dict. Returns: @@ -237,9 +233,9 @@ class Histogram: Use Band.create to create a new Band. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]) -> None: + def __init__(self, properties: dict[str, Any]) -> None: self.properties = properties def apply( @@ -247,7 +243,7 @@ def apply( count: int, min: float, max: float, - buckets: List[int], + buckets: list[int], ) -> None: """ Sets the properties for this raster Band. @@ -272,7 +268,7 @@ def create( count: int, min: float, max: float, - buckets: List[int], + buckets: list[int], ) -> Histogram: """ Creates a new band. @@ -335,7 +331,7 @@ def max(self, v: float) -> None: self.properties["max"] = v @property - def buckets(self) -> List[int]: + def buckets(self) -> list[int]: """Get or sets the Array of integer indicating the number of pixels included in the bucket. @@ -345,10 +341,10 @@ def buckets(self) -> List[int]: return get_required(self.properties.get("buckets"), self, "buckets") @buckets.setter - def buckets(self, v: List[int]) -> None: + def buckets(self, v: list[int]) -> None: self.properties["buckets"] = v - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this histogram as a dictionary. Returns: @@ -357,7 +353,7 @@ def to_dict(self) -> Dict[str, Any]: return self.properties @staticmethod - def from_dict(d: Dict[str, Any]) -> Histogram: + def from_dict(d: dict[str, Any]) -> Histogram: """Constructs an Histogram from a dict. Returns: @@ -373,23 +369,23 @@ class RasterBand: Use Band.create to create a new Band. """ - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]) -> None: + def __init__(self, properties: dict[str, Any]) -> None: self.properties = properties def apply( self, - nodata: Optional[Union[float, NoDataStrings]] = None, - sampling: Optional[Sampling] = None, - data_type: Optional[DataType] = None, - bits_per_sample: Optional[float] = None, - spatial_resolution: Optional[float] = None, - statistics: Optional[Statistics] = None, - unit: Optional[str] = None, - scale: Optional[float] = None, - offset: Optional[float] = None, - histogram: Optional[Histogram] = None, + nodata: float | NoDataStrings | None = None, + sampling: Sampling | None = None, + data_type: DataType | None = None, + bits_per_sample: float | None = None, + spatial_resolution: float | None = None, + statistics: Statistics | None = None, + unit: str | None = None, + scale: float | None = None, + offset: float | None = None, + histogram: Histogram | None = None, ) -> None: """ Sets the properties for this raster Band. @@ -430,16 +426,16 @@ def apply( @classmethod def create( cls, - nodata: Optional[Union[float, NoDataStrings]] = None, - sampling: Optional[Sampling] = None, - data_type: Optional[DataType] = None, - bits_per_sample: Optional[float] = None, - spatial_resolution: Optional[float] = None, - statistics: Optional[Statistics] = None, - unit: Optional[str] = None, - scale: Optional[float] = None, - offset: Optional[float] = None, - histogram: Optional[Histogram] = None, + nodata: float | NoDataStrings | None = None, + sampling: Sampling | None = None, + data_type: DataType | None = None, + bits_per_sample: float | None = None, + spatial_resolution: float | None = None, + statistics: Statistics | None = None, + unit: str | None = None, + scale: float | None = None, + offset: float | None = None, + histogram: Histogram | None = None, ) -> RasterBand: """ Creates a new band. @@ -482,7 +478,7 @@ def create( return b @property - def nodata(self) -> Optional[Union[float, NoDataStrings]]: + def nodata(self) -> float | NoDataStrings | None: """Get or sets the nodata pixel value Returns: @@ -491,14 +487,14 @@ def nodata(self) -> Optional[Union[float, NoDataStrings]]: return self.properties.get("nodata") @nodata.setter - def nodata(self, v: Optional[Union[float, NoDataStrings]]) -> None: + def nodata(self, v: float | NoDataStrings | None) -> None: if v is not None: self.properties["nodata"] = v else: self.properties.pop("nodata", None) @property - def sampling(self) -> Optional[Sampling]: + def sampling(self) -> Sampling | None: """Get or sets the property indicating whether a pixel value should be assumed to represent a sampling over the region of the pixel or a point sample at the center of the pixel. @@ -509,14 +505,14 @@ def sampling(self) -> Optional[Sampling]: return self.properties.get("sampling") @sampling.setter - def sampling(self, v: Optional[Sampling]) -> None: + def sampling(self, v: Sampling | None) -> None: if v is not None: self.properties["sampling"] = v else: self.properties.pop("sampling", None) @property - def data_type(self) -> Optional[DataType]: + def data_type(self) -> DataType | None: """Get or sets the data type of the band. Returns: @@ -525,14 +521,14 @@ def data_type(self) -> Optional[DataType]: return self.properties.get("data_type") @data_type.setter - def data_type(self, v: Optional[DataType]) -> None: + def data_type(self, v: DataType | None) -> None: if v is not None: self.properties["data_type"] = v else: self.properties.pop("data_type", None) @property - def bits_per_sample(self) -> Optional[float]: + def bits_per_sample(self) -> float | None: """Get or sets the actual number of bits used for this band. Returns: @@ -541,14 +537,14 @@ def bits_per_sample(self) -> Optional[float]: return self.properties.get("bits_per_sample") @bits_per_sample.setter - def bits_per_sample(self, v: Optional[float]) -> None: + def bits_per_sample(self, v: float | None) -> None: if v is not None: self.properties["bits_per_sample"] = v else: self.properties.pop("bits_per_sample", None) @property - def spatial_resolution(self) -> Optional[float]: + def spatial_resolution(self) -> float | None: """Get or sets the average spatial resolution (in meters) of the pixels in the band. @@ -558,14 +554,14 @@ def spatial_resolution(self) -> Optional[float]: return self.properties.get("spatial_resolution") @spatial_resolution.setter - def spatial_resolution(self, v: Optional[float]) -> None: + def spatial_resolution(self, v: float | None) -> None: if v is not None: self.properties["spatial_resolution"] = v else: self.properties.pop("spatial_resolution", None) @property - def statistics(self) -> Optional[Statistics]: + def statistics(self) -> Statistics | None: """Get or sets the average spatial resolution (in meters) of the pixels in the band. @@ -575,14 +571,14 @@ def statistics(self) -> Optional[Statistics]: return Statistics.from_dict(get_opt(self.properties.get("statistics"))) @statistics.setter - def statistics(self, v: Optional[Statistics]) -> None: + def statistics(self, v: Statistics | None) -> None: if v is not None: self.properties["statistics"] = v.to_dict() else: self.properties.pop("statistics", None) @property - def unit(self) -> Optional[str]: + def unit(self) -> str | None: """Get or sets the unit denomination of the pixel value Returns: @@ -591,14 +587,14 @@ def unit(self) -> Optional[str]: return self.properties.get("unit") @unit.setter - def unit(self, v: Optional[str]) -> None: + def unit(self, v: str | None) -> None: if v is not None: self.properties["unit"] = v else: self.properties.pop("unit", None) @property - def scale(self) -> Optional[float]: + def scale(self) -> float | None: """Get or sets the multiplicator factor of the pixel value to transform into the value (i.e. translate digital number to reflectance). @@ -608,14 +604,14 @@ def scale(self) -> Optional[float]: return self.properties.get("scale") @scale.setter - def scale(self, v: Optional[float]) -> None: + def scale(self, v: float | None) -> None: if v is not None: self.properties["scale"] = v else: self.properties.pop("scale", None) @property - def offset(self) -> Optional[float]: + def offset(self) -> float | None: """Get or sets the number to be added to the pixel value (after scaling) to transform into the value (i.e. translate digital number to reflectance). @@ -625,14 +621,14 @@ def offset(self) -> Optional[float]: return self.properties.get("offset") @offset.setter - def offset(self, v: Optional[float]) -> None: + def offset(self, v: float | None) -> None: if v is not None: self.properties["offset"] = v else: self.properties.pop("offset", None) @property - def histogram(self) -> Optional[Histogram]: + def histogram(self) -> Histogram | None: """Get or sets the histogram distribution information of the pixels values in the band. @@ -642,7 +638,7 @@ def histogram(self) -> Optional[Histogram]: return Histogram.from_dict(get_opt(self.properties.get("histogram"))) @histogram.setter - def histogram(self, v: Optional[Histogram]) -> None: + def histogram(self, v: Histogram | None) -> None: if v is not None: self.properties["histogram"] = v.to_dict() else: @@ -651,7 +647,7 @@ def histogram(self, v: Optional[Histogram]) -> None: def __repr__(self) -> str: return "" - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this band as a dictionary. Returns: @@ -681,10 +677,10 @@ class RasterExtension( name: Literal["raster"] = "raster" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - def apply(self, bands: List[RasterBand]) -> None: + def apply(self, bands: list[RasterBand]) -> None: """Applies raster extension properties to the extended :class:`pystac.Item` or :class:`pystac.Asset`. @@ -695,7 +691,7 @@ def apply(self, bands: List[RasterBand]) -> None: self.bands = bands @property - def bands(self) -> Optional[List[RasterBand]]: + def bands(self) -> list[RasterBand] | None: """Gets or sets a list of available bands where each item is a :class:`~RasterBand` object (or ``None`` if no bands have been set). If not available the field should not be provided. @@ -703,15 +699,15 @@ def bands(self) -> Optional[List[RasterBand]]: return self._get_bands() @bands.setter - def bands(self, v: Optional[List[RasterBand]]) -> None: + def bands(self, v: list[RasterBand] | None) -> None: self._set_property( BANDS_PROP, map_opt(lambda bands: [b.to_dict() for b in bands], v) ) - def _get_bands(self) -> Optional[List[RasterBand]]: + def _get_bands(self) -> list[RasterBand] | None: return map_opt( lambda bands: [RasterBand(b) for b in bands], - self._get_property(BANDS_PROP, List[Dict[str, Any]]), + self._get_property(BANDS_PROP, list[dict[str, Any]]), ) @classmethod @@ -719,7 +715,7 @@ def get_schema_uri(cls) -> str: return SCHEMA_URI @classmethod - def get_schema_uris(cls) -> List[str]: + def get_schema_uris(cls) -> list[str]: warnings.warn( "get_schema_uris is deprecated and will be removed in v2", DeprecationWarning, @@ -758,10 +754,10 @@ class AssetRasterExtension(RasterExtension[pystac.Asset]): asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """If present, this will be a list containing 1 dictionary representing the properties of the owning :class:`~pystac.Item`.""" @@ -772,7 +768,7 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" class ItemAssetsRasterExtension(RasterExtension[item_assets.AssetDefinition]): @@ -780,7 +776,7 @@ class ItemAssetsRasterExtension(RasterExtension[item_assets.AssetDefinition]): """A reference to the :class:`~pystac.extensions.item_assets.AssetDefinition` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.extensions.item_assets.AssetDefinition` fields, including extension properties.""" @@ -801,7 +797,7 @@ class SummariesRasterExtension(SummariesExtension): """ @property - def bands(self) -> Optional[List[RasterBand]]: + def bands(self) -> list[RasterBand] | None: """Get or sets a list of :class:`~pystac.Band` objects that represent the available bands. """ @@ -811,13 +807,13 @@ def bands(self) -> Optional[List[RasterBand]]: ) @bands.setter - def bands(self, v: Optional[List[RasterBand]]) -> None: + def bands(self, v: list[RasterBand] | None) -> None: self._set_summary(BANDS_PROP, map_opt(lambda x: [b.to_dict() for b in x], v)) class RasterExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = {*[uri for uri in SCHEMA_URIS if uri != SCHEMA_URI]} + prev_extension_ids: set[str] = {*[uri for uri in SCHEMA_URIS if uri != SCHEMA_URI]} stac_object_types = {pystac.STACObjectType.ITEM, pystac.STACObjectType.COLLECTION} diff --git a/pystac/extensions/sar.py b/pystac/extensions/sar.py index 4138c812d..112c63a9a 100644 --- a/pystac/extensions/sar.py +++ b/pystac/extensions/sar.py @@ -2,14 +2,11 @@ from __future__ import annotations +from collections.abc import Iterable from typing import ( Any, - Dict, Generic, - Iterable, - List, Literal, - Optional, TypeVar, Union, cast, @@ -99,17 +96,17 @@ def apply( self, instrument_mode: str, frequency_band: FrequencyBand, - polarizations: List[Polarization], + polarizations: list[Polarization], product_type: str, - center_frequency: Optional[float] = None, - resolution_range: Optional[float] = None, - resolution_azimuth: Optional[float] = None, - pixel_spacing_range: Optional[float] = None, - pixel_spacing_azimuth: Optional[float] = None, - looks_range: Optional[int] = None, - looks_azimuth: Optional[int] = None, - looks_equivalent_number: Optional[float] = None, - observation_direction: Optional[ObservationDirection] = None, + center_frequency: float | None = None, + resolution_range: float | None = None, + resolution_azimuth: float | None = None, + pixel_spacing_range: float | None = None, + pixel_spacing_azimuth: float | None = None, + looks_range: int | None = None, + looks_azimuth: int | None = None, + looks_equivalent_number: float | None = None, + observation_direction: ObservationDirection | None = None, ) -> None: """Applies sar extension properties to the extended Item. @@ -196,19 +193,19 @@ def frequency_band(self, v: FrequencyBand) -> None: self._set_property(FREQUENCY_BAND_PROP, v.value, pop_if_none=False) @property - def polarizations(self) -> List[Polarization]: + def polarizations(self) -> list[Polarization]: """Gets or sets a list of polarizations for the item.""" return get_required( map_opt( lambda values: [Polarization(v) for v in values], - self._get_property(POLARIZATIONS_PROP, List[str]), + self._get_property(POLARIZATIONS_PROP, list[str]), ), self, POLARIZATIONS_PROP, ) @polarizations.setter - def polarizations(self, values: List[Polarization]) -> None: + def polarizations(self, values: list[Polarization]) -> None: if not isinstance(values, list): raise pystac.STACError(f'polarizations must be a list. Invalid "{values}"') self._set_property( @@ -227,86 +224,86 @@ def product_type(self, v: str) -> None: self._set_property(PRODUCT_TYPE_PROP, v, pop_if_none=False) @property - def center_frequency(self) -> Optional[float]: + def center_frequency(self) -> float | None: """Gets or sets a center frequency for the item.""" return self._get_property(CENTER_FREQUENCY_PROP, float) @center_frequency.setter - def center_frequency(self, v: Optional[float]) -> None: + def center_frequency(self, v: float | None) -> None: self._set_property(CENTER_FREQUENCY_PROP, v) @property - def resolution_range(self) -> Optional[float]: + def resolution_range(self) -> float | None: """Gets or sets a resolution range for the item.""" return self._get_property(RESOLUTION_RANGE_PROP, float) @resolution_range.setter - def resolution_range(self, v: Optional[float]) -> None: + def resolution_range(self, v: float | None) -> None: self._set_property(RESOLUTION_RANGE_PROP, v) @property - def resolution_azimuth(self) -> Optional[float]: + def resolution_azimuth(self) -> float | None: """Gets or sets a resolution azimuth for the item.""" return self._get_property(RESOLUTION_AZIMUTH_PROP, float) @resolution_azimuth.setter - def resolution_azimuth(self, v: Optional[float]) -> None: + def resolution_azimuth(self, v: float | None) -> None: self._set_property(RESOLUTION_AZIMUTH_PROP, v) @property - def pixel_spacing_range(self) -> Optional[float]: + def pixel_spacing_range(self) -> float | None: """Gets or sets a pixel spacing range for the item.""" return self._get_property(PIXEL_SPACING_RANGE_PROP, float) @pixel_spacing_range.setter - def pixel_spacing_range(self, v: Optional[float]) -> None: + def pixel_spacing_range(self, v: float | None) -> None: self._set_property(PIXEL_SPACING_RANGE_PROP, v) @property - def pixel_spacing_azimuth(self) -> Optional[float]: + def pixel_spacing_azimuth(self) -> float | None: """Gets or sets a pixel spacing azimuth for the item.""" return self._get_property(PIXEL_SPACING_AZIMUTH_PROP, float) @pixel_spacing_azimuth.setter - def pixel_spacing_azimuth(self, v: Optional[float]) -> None: + def pixel_spacing_azimuth(self, v: float | None) -> None: self._set_property(PIXEL_SPACING_AZIMUTH_PROP, v) @property - def looks_range(self) -> Optional[int]: + def looks_range(self) -> int | None: """Gets or sets a looks range for the item.""" return self._get_property(LOOKS_RANGE_PROP, int) @looks_range.setter - def looks_range(self, v: Optional[int]) -> None: + def looks_range(self, v: int | None) -> None: self._set_property(LOOKS_RANGE_PROP, v) @property - def looks_azimuth(self) -> Optional[int]: + def looks_azimuth(self) -> int | None: """Gets or sets a looks azimuth for the item.""" return self._get_property(LOOKS_AZIMUTH_PROP, int) @looks_azimuth.setter - def looks_azimuth(self, v: Optional[int]) -> None: + def looks_azimuth(self, v: int | None) -> None: self._set_property(LOOKS_AZIMUTH_PROP, v) @property - def looks_equivalent_number(self) -> Optional[float]: + def looks_equivalent_number(self) -> float | None: """Gets or sets a looks equivalent number for the item.""" return self._get_property(LOOKS_EQUIVALENT_NUMBER_PROP, float) @looks_equivalent_number.setter - def looks_equivalent_number(self, v: Optional[float]) -> None: + def looks_equivalent_number(self, v: float | None) -> None: self._set_property(LOOKS_EQUIVALENT_NUMBER_PROP, v) @property - def observation_direction(self) -> Optional[ObservationDirection]: + def observation_direction(self) -> ObservationDirection | None: """Gets or sets an observation direction for the item.""" return map_opt( ObservationDirection, self._get_property(OBSERVATION_DIRECTION_PROP, str) ) @observation_direction.setter - def observation_direction(self, v: Optional[ObservationDirection]) -> None: + def observation_direction(self, v: ObservationDirection | None) -> None: self._set_property(OBSERVATION_DIRECTION_PROP, map_opt(lambda x: x.value, v)) @classmethod @@ -362,7 +359,7 @@ class ItemSarExtension(SarExtension[pystac.Item]): item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" def __init__(self, item: pystac.Item): @@ -370,7 +367,7 @@ def __init__(self, item: pystac.Item): self.properties = item.properties def __repr__(self) -> str: - return "".format(self.item.id) + return f"" class AssetSarExtension(SarExtension[pystac.Asset]): @@ -385,10 +382,10 @@ class AssetSarExtension(SarExtension[pystac.Asset]): asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """If present, this will be a list containing 1 dictionary representing the properties of the owning :class:`~pystac.Item`.""" @@ -399,11 +396,11 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" class ItemAssetsSarExtension(SarExtension[item_assets.AssetDefinition]): - properties: Dict[str, Any] + properties: dict[str, Any] asset_defn: item_assets.AssetDefinition def __init__(self, item_asset: item_assets.AssetDefinition): @@ -418,7 +415,7 @@ class SummariesSarExtension(SummariesExtension): """ @property - def instrument_mode(self) -> Optional[List[str]]: + def instrument_mode(self) -> list[str] | None: """Get or sets the summary of :attr:`SarExtension.instrument_mode` values for this Collection. """ @@ -426,11 +423,11 @@ def instrument_mode(self) -> Optional[List[str]]: return self.summaries.get_list(INSTRUMENT_MODE_PROP) @instrument_mode.setter - def instrument_mode(self, v: Optional[List[str]]) -> None: + def instrument_mode(self, v: list[str] | None) -> None: self._set_summary(INSTRUMENT_MODE_PROP, v) @property - def frequency_band(self) -> Optional[List[FrequencyBand]]: + def frequency_band(self) -> list[FrequencyBand] | None: """Get or sets the summary of :attr:`SarExtension.frequency_band` values for this Collection. """ @@ -438,11 +435,11 @@ def frequency_band(self) -> Optional[List[FrequencyBand]]: return self.summaries.get_list(FREQUENCY_BAND_PROP) @frequency_band.setter - def frequency_band(self, v: Optional[List[FrequencyBand]]) -> None: + def frequency_band(self, v: list[FrequencyBand] | None) -> None: self._set_summary(FREQUENCY_BAND_PROP, v) @property - def center_frequency(self) -> Optional[RangeSummary[float]]: + def center_frequency(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`SarExtension.center_frequency` values for this Collection. """ @@ -450,11 +447,11 @@ def center_frequency(self) -> Optional[RangeSummary[float]]: return self.summaries.get_range(CENTER_FREQUENCY_PROP) @center_frequency.setter - def center_frequency(self, v: Optional[RangeSummary[float]]) -> None: + def center_frequency(self, v: RangeSummary[float] | None) -> None: self._set_summary(CENTER_FREQUENCY_PROP, v) @property - def polarizations(self) -> Optional[List[Polarization]]: + def polarizations(self) -> list[Polarization] | None: """Get or sets the summary of :attr:`SarExtension.polarizations` values for this Collection. """ @@ -462,11 +459,11 @@ def polarizations(self) -> Optional[List[Polarization]]: return self.summaries.get_list(POLARIZATIONS_PROP) @polarizations.setter - def polarizations(self, v: Optional[List[Polarization]]) -> None: + def polarizations(self, v: list[Polarization] | None) -> None: self._set_summary(POLARIZATIONS_PROP, v) @property - def product_type(self) -> Optional[List[str]]: + def product_type(self) -> list[str] | None: """Get or sets the summary of :attr:`SarExtension.product_type` values for this Collection. """ @@ -474,11 +471,11 @@ def product_type(self) -> Optional[List[str]]: return self.summaries.get_list(PRODUCT_TYPE_PROP) @product_type.setter - def product_type(self, v: Optional[List[str]]) -> None: + def product_type(self, v: list[str] | None) -> None: self._set_summary(PRODUCT_TYPE_PROP, v) @property - def resolution_range(self) -> Optional[RangeSummary[float]]: + def resolution_range(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`SarExtension.resolution_range` values for this Collection. """ @@ -486,11 +483,11 @@ def resolution_range(self) -> Optional[RangeSummary[float]]: return self.summaries.get_range(RESOLUTION_RANGE_PROP) @resolution_range.setter - def resolution_range(self, v: Optional[RangeSummary[float]]) -> None: + def resolution_range(self, v: RangeSummary[float] | None) -> None: self._set_summary(RESOLUTION_RANGE_PROP, v) @property - def resolution_azimuth(self) -> Optional[RangeSummary[float]]: + def resolution_azimuth(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`SarExtension.resolution_azimuth` values for this Collection. """ @@ -498,11 +495,11 @@ def resolution_azimuth(self) -> Optional[RangeSummary[float]]: return self.summaries.get_range(RESOLUTION_AZIMUTH_PROP) @resolution_azimuth.setter - def resolution_azimuth(self, v: Optional[RangeSummary[float]]) -> None: + def resolution_azimuth(self, v: RangeSummary[float] | None) -> None: self._set_summary(RESOLUTION_AZIMUTH_PROP, v) @property - def pixel_spacing_range(self) -> Optional[RangeSummary[float]]: + def pixel_spacing_range(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`SarExtension.pixel_spacing_range` values for this Collection. """ @@ -510,11 +507,11 @@ def pixel_spacing_range(self) -> Optional[RangeSummary[float]]: return self.summaries.get_range(PIXEL_SPACING_RANGE_PROP) @pixel_spacing_range.setter - def pixel_spacing_range(self, v: Optional[RangeSummary[float]]) -> None: + def pixel_spacing_range(self, v: RangeSummary[float] | None) -> None: self._set_summary(PIXEL_SPACING_RANGE_PROP, v) @property - def pixel_spacing_azimuth(self) -> Optional[RangeSummary[float]]: + def pixel_spacing_azimuth(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`SarExtension.pixel_spacing_azimuth` values for this Collection. """ @@ -522,11 +519,11 @@ def pixel_spacing_azimuth(self) -> Optional[RangeSummary[float]]: return self.summaries.get_range(PIXEL_SPACING_AZIMUTH_PROP) @pixel_spacing_azimuth.setter - def pixel_spacing_azimuth(self, v: Optional[RangeSummary[float]]) -> None: + def pixel_spacing_azimuth(self, v: RangeSummary[float] | None) -> None: self._set_summary(PIXEL_SPACING_AZIMUTH_PROP, v) @property - def looks_range(self) -> Optional[RangeSummary[int]]: + def looks_range(self) -> RangeSummary[int] | None: """Get or sets the summary of :attr:`SarExtension.looks_range` values for this Collection. """ @@ -534,11 +531,11 @@ def looks_range(self) -> Optional[RangeSummary[int]]: return self.summaries.get_range(LOOKS_RANGE_PROP) @looks_range.setter - def looks_range(self, v: Optional[RangeSummary[int]]) -> None: + def looks_range(self, v: RangeSummary[int] | None) -> None: self._set_summary(LOOKS_RANGE_PROP, v) @property - def looks_azimuth(self) -> Optional[RangeSummary[int]]: + def looks_azimuth(self) -> RangeSummary[int] | None: """Get or sets the summary of :attr:`SarExtension.looks_azimuth` values for this Collection. """ @@ -546,11 +543,11 @@ def looks_azimuth(self) -> Optional[RangeSummary[int]]: return self.summaries.get_range(LOOKS_AZIMUTH_PROP) @looks_azimuth.setter - def looks_azimuth(self, v: Optional[RangeSummary[int]]) -> None: + def looks_azimuth(self, v: RangeSummary[int] | None) -> None: self._set_summary(LOOKS_AZIMUTH_PROP, v) @property - def looks_equivalent_number(self) -> Optional[RangeSummary[float]]: + def looks_equivalent_number(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`SarExtension.looks_equivalent_number` values for this Collection. """ @@ -558,11 +555,11 @@ def looks_equivalent_number(self) -> Optional[RangeSummary[float]]: return self.summaries.get_range(LOOKS_EQUIVALENT_NUMBER_PROP) @looks_equivalent_number.setter - def looks_equivalent_number(self, v: Optional[RangeSummary[float]]) -> None: + def looks_equivalent_number(self, v: RangeSummary[float] | None) -> None: self._set_summary(LOOKS_EQUIVALENT_NUMBER_PROP, v) @property - def observation_direction(self) -> Optional[List[ObservationDirection]]: + def observation_direction(self) -> list[ObservationDirection] | None: """Get or sets the summary of :attr:`SarExtension.observation_direction` values for this Collection. """ @@ -570,7 +567,7 @@ def observation_direction(self) -> Optional[List[ObservationDirection]]: return self.summaries.get_list(OBSERVATION_DIRECTION_PROP) @observation_direction.setter - def observation_direction(self, v: Optional[List[ObservationDirection]]) -> None: + def observation_direction(self, v: list[ObservationDirection] | None) -> None: self._set_summary(OBSERVATION_DIRECTION_PROP, v) @@ -580,7 +577,7 @@ class SarExtensionHooks(ExtensionHooks): stac_object_types = {pystac.STACObjectType.ITEM} def migrate( - self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: if version < "0.9": # Some sar fields became common_metadata diff --git a/pystac/extensions/sat.py b/pystac/extensions/sat.py index 0d286edde..ab5b638fe 100644 --- a/pystac/extensions/sat.py +++ b/pystac/extensions/sat.py @@ -2,15 +2,12 @@ from __future__ import annotations +from collections.abc import Iterable from datetime import datetime from typing import ( Any, - Dict, Generic, - Iterable, - List, Literal, - Optional, TypeVar, Union, cast, @@ -71,11 +68,11 @@ class SatExtension( def apply( self, - orbit_state: Optional[OrbitState] = None, - relative_orbit: Optional[int] = None, - absolute_orbit: Optional[int] = None, - platform_international_designator: Optional[str] = None, - anx_datetime: Optional[datetime] = None, + orbit_state: OrbitState | None = None, + relative_orbit: int | None = None, + absolute_orbit: int | None = None, + platform_international_designator: str | None = None, + anx_datetime: datetime | None = None, ) -> None: """Applies ext extension properties to the extended :class:`~pystac.Item` or class:`~pystac.Asset`. @@ -98,50 +95,50 @@ def apply( self.anx_datetime = anx_datetime @property - def platform_international_designator(self) -> Optional[str]: + def platform_international_designator(self) -> str | None: """Gets or sets the International Designator, also known as COSPAR ID, and NSSDCA ID.""" return self._get_property(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, str) @platform_international_designator.setter - def platform_international_designator(self, v: Optional[str]) -> None: + def platform_international_designator(self, v: str | None) -> None: self._set_property(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, v) @property - def orbit_state(self) -> Optional[OrbitState]: + def orbit_state(self) -> OrbitState | None: """Get or sets an orbit state of the object.""" return map_opt( lambda x: OrbitState(x), self._get_property(ORBIT_STATE_PROP, str) ) @orbit_state.setter - def orbit_state(self, v: Optional[OrbitState]) -> None: + def orbit_state(self, v: OrbitState | None) -> None: self._set_property(ORBIT_STATE_PROP, map_opt(lambda x: x.value, v)) @property - def absolute_orbit(self) -> Optional[int]: + def absolute_orbit(self) -> int | None: """Get or sets a absolute orbit number of the item.""" return self._get_property(ABSOLUTE_ORBIT_PROP, int) @absolute_orbit.setter - def absolute_orbit(self, v: Optional[int]) -> None: + def absolute_orbit(self, v: int | None) -> None: self._set_property(ABSOLUTE_ORBIT_PROP, v) @property - def relative_orbit(self) -> Optional[int]: + def relative_orbit(self) -> int | None: """Get or sets a relative orbit number of the item.""" return self._get_property(RELATIVE_ORBIT_PROP, int) @relative_orbit.setter - def relative_orbit(self, v: Optional[int]) -> None: + def relative_orbit(self, v: int | None) -> None: self._set_property(RELATIVE_ORBIT_PROP, v) @property - def anx_datetime(self) -> Optional[datetime]: + def anx_datetime(self) -> datetime | None: return map_opt(str_to_datetime, self._get_property(ANX_DATETIME_PROP, str)) @anx_datetime.setter - def anx_datetime(self, v: Optional[datetime]) -> None: + def anx_datetime(self, v: datetime | None) -> None: self._set_property(ANX_DATETIME_PROP, map_opt(datetime_to_str, v)) @classmethod @@ -194,7 +191,7 @@ class ItemSatExtension(SatExtension[pystac.Item]): item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" def __init__(self, item: pystac.Item): @@ -202,7 +199,7 @@ def __init__(self, item: pystac.Item): self.properties = item.properties def __repr__(self) -> str: - return "".format(self.item.id) + return f"" class AssetSatExtension(SatExtension[pystac.Asset]): @@ -218,10 +215,10 @@ class AssetSatExtension(SatExtension[pystac.Asset]): asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """If present, this will be a list containing 1 dictionary representing the properties of the owning :class:`~pystac.Item`.""" @@ -232,11 +229,11 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" class ItemAssetsSatExtension(SatExtension[item_assets.AssetDefinition]): - properties: Dict[str, Any] + properties: dict[str, Any] asset_defn: item_assets.AssetDefinition def __init__(self, item_asset: item_assets.AssetDefinition): @@ -251,7 +248,7 @@ class SummariesSatExtension(SummariesExtension): """ @property - def platform_international_designator(self) -> Optional[List[str]]: + def platform_international_designator(self) -> list[str] | None: """Get or sets the summary of :attr:`SatExtension.platform_international_designator` values for this Collection. @@ -260,11 +257,11 @@ def platform_international_designator(self) -> Optional[List[str]]: return self.summaries.get_list(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP) @platform_international_designator.setter - def platform_international_designator(self, v: Optional[List[str]]) -> None: + def platform_international_designator(self, v: list[str] | None) -> None: self._set_summary(PLATFORM_INTERNATIONAL_DESIGNATOR_PROP, v) @property - def orbit_state(self) -> Optional[List[OrbitState]]: + def orbit_state(self) -> list[OrbitState] | None: """Get or sets the summary of :attr:`SatExtension.orbit_state` values for this Collection. """ @@ -272,27 +269,27 @@ def orbit_state(self) -> Optional[List[OrbitState]]: return self.summaries.get_list(ORBIT_STATE_PROP) @orbit_state.setter - def orbit_state(self, v: Optional[List[OrbitState]]) -> None: + def orbit_state(self, v: list[OrbitState] | None) -> None: self._set_summary(ORBIT_STATE_PROP, v) @property - def absolute_orbit(self) -> Optional[RangeSummary[int]]: + def absolute_orbit(self) -> RangeSummary[int] | None: return self.summaries.get_range(ABSOLUTE_ORBIT_PROP) @absolute_orbit.setter - def absolute_orbit(self, v: Optional[RangeSummary[int]]) -> None: + def absolute_orbit(self, v: RangeSummary[int] | None) -> None: self._set_summary(ABSOLUTE_ORBIT_PROP, v) @property - def relative_orbit(self) -> Optional[RangeSummary[int]]: + def relative_orbit(self) -> RangeSummary[int] | None: return self.summaries.get_range(RELATIVE_ORBIT_PROP) @relative_orbit.setter - def relative_orbit(self, v: Optional[RangeSummary[int]]) -> None: + def relative_orbit(self, v: RangeSummary[int] | None) -> None: self._set_summary(RELATIVE_ORBIT_PROP, v) @property - def anx_datetime(self) -> Optional[RangeSummary[datetime]]: + def anx_datetime(self) -> RangeSummary[datetime] | None: return map_opt( lambda s: RangeSummary( str_to_datetime(s.minimum), str_to_datetime(s.maximum) @@ -301,7 +298,7 @@ def anx_datetime(self) -> Optional[RangeSummary[datetime]]: ) @anx_datetime.setter - def anx_datetime(self, v: Optional[RangeSummary[datetime]]) -> None: + def anx_datetime(self, v: RangeSummary[datetime] | None) -> None: self._set_summary( ANX_DATETIME_PROP, map_opt( diff --git a/pystac/extensions/scientific.py b/pystac/extensions/scientific.py index ec576f9dc..0a56097f7 100644 --- a/pystac/extensions/scientific.py +++ b/pystac/extensions/scientific.py @@ -8,7 +8,7 @@ from __future__ import annotations import copy -from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar, Union, cast +from typing import Any, Generic, Literal, TypeVar, Union, cast from urllib import parse import pystac @@ -54,10 +54,10 @@ def doi_to_url(doi: str) -> str: class Publication: """Helper for Publication entries.""" - citation: Optional[str] - doi: Optional[str] + citation: str | None + doi: str | None - def __init__(self, doi: Optional[str], citation: Optional[str]) -> None: + def __init__(self, doi: str | None, citation: str | None) -> None: self.doi = doi self.citation = citation @@ -70,14 +70,14 @@ def __eq__(self, other: Any) -> bool: def __repr__(self) -> str: return f"" - def to_dict(self) -> Dict[str, Optional[str]]: + def to_dict(self) -> dict[str, str | None]: return copy.deepcopy({"doi": self.doi, "citation": self.citation}) @staticmethod - def from_dict(d: Dict[str, str]) -> Publication: + def from_dict(d: dict[str, str]) -> Publication: return Publication(d.get("doi"), d.get("citation")) - def get_link(self) -> Optional[pystac.Link]: + def get_link(self) -> pystac.Link | None: """Gets a :class:`~pystac.Link` for the DOI for this publication. If :attr:`Publication.doi` is ``None``, this method will also return ``None``.""" if self.doi is None: @@ -85,7 +85,7 @@ def get_link(self) -> Optional[pystac.Link]: return pystac.Link(ScientificRelType.CITE_AS, doi_to_url(self.doi)) -def remove_link(links: List[pystac.Link], doi: Optional[str]) -> None: +def remove_link(links: list[pystac.Link], doi: str | None) -> None: if doi is None: return url = doi_to_url(doi) @@ -125,9 +125,9 @@ def __init__(self, obj: pystac.STACObject) -> None: def apply( self, - doi: Optional[str] = None, - citation: Optional[str] = None, - publications: Optional[List[Publication]] = None, + doi: str | None = None, + citation: str | None = None, + publications: list[Publication] | None = None, ) -> None: """Applies scientific extension properties to the extended :class:`~pystac.Item`. @@ -143,7 +143,7 @@ def apply( self.publications = publications @property - def doi(self) -> Optional[str]: + def doi(self) -> str | None: """Get or sets the DOI for the item. This MUST NOT be a DOIs link. For all DOI names respective DOI links SHOULD be @@ -152,7 +152,7 @@ def doi(self) -> Optional[str]: return self._get_property(DOI_PROP, str) @doi.setter - def doi(self, v: Optional[str]) -> None: + def doi(self, v: str | None) -> None: if DOI_PROP in self.properties: if v == self.properties[DOI_PROP]: return @@ -164,7 +164,7 @@ def doi(self, v: Optional[str]) -> None: self.obj.add_link(pystac.Link(ScientificRelType.CITE_AS, url)) @property - def citation(self) -> Optional[str]: + def citation(self) -> str | None: """Get or sets the recommended human-readable reference (citation) to be used by publications citing the data. @@ -174,20 +174,20 @@ def citation(self) -> Optional[str]: return self._get_property(CITATION_PROP, str) @citation.setter - def citation(self, v: Optional[str]) -> None: + def citation(self, v: str | None) -> None: self._set_property(CITATION_PROP, v) @property - def publications(self) -> Optional[List[Publication]]: + def publications(self) -> list[Publication] | None: """Get or sets the list of relevant publications referencing and describing the data.""" return map_opt( lambda pubs: [Publication.from_dict(pub) for pub in pubs], - self._get_property(PUBLICATIONS_PROP, List[Dict[str, Any]]), + self._get_property(PUBLICATIONS_PROP, list[dict[str, Any]]), ) @publications.setter - def publications(self, v: Optional[List[Publication]]) -> None: + def publications(self, v: list[Publication] | None) -> None: self._set_property( PUBLICATIONS_PROP, map_opt(lambda pubs: [pub.to_dict() for pub in pubs], v) ) @@ -198,7 +198,7 @@ def publications(self, v: Optional[List[Publication]]) -> None: self.obj.add_link(pub_link) # None for publication will clear all. - def remove_publication(self, publication: Optional[Publication] = None) -> None: + def remove_publication(self, publication: Publication | None = None) -> None: """Removes the given :class:`Publication` from the extended :class:`~pystac.Item`. If the ``publication`` argument is ``None``, all publications will be removed from the :class:`~pystac.Item`.""" @@ -268,10 +268,10 @@ class CollectionScientificExtension(ScientificExtension[pystac.Collection]): collection: pystac.Collection """The :class:`~pystac.Collection` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Collection` properties, including extension properties.""" - links: List[pystac.Link] + links: list[pystac.Link] """The list of :class:`~pystac.Link` objects associated with the :class:`~pystac.Collection` being extended, including links added by this extension. """ @@ -301,10 +301,10 @@ class ItemScientificExtension(ScientificExtension[pystac.Item]): item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" - links: List[pystac.Link] + links: list[pystac.Link] """The list of :class:`~pystac.Link` objects associated with the :class:`~pystac.Item` being extended, including links added by this extension. """ @@ -316,7 +316,7 @@ def __init__(self, item: pystac.Item): super().__init__(self.item) def __repr__(self) -> str: - return "".format(self.item.id) + return f"" class SummariesScientificExtension(SummariesExtension): @@ -326,25 +326,25 @@ class SummariesScientificExtension(SummariesExtension): """ @property - def citation(self) -> Optional[List[str]]: + def citation(self) -> list[str] | None: """Get or sets the summary of :attr:`ScientificExtension.citation` values for this Collection. """ return self.summaries.get_list(CITATION_PROP) @citation.setter - def citation(self, v: Optional[List[str]]) -> None: + def citation(self, v: list[str] | None) -> None: self._set_summary(CITATION_PROP, v) @property - def doi(self) -> Optional[List[str]]: + def doi(self) -> list[str] | None: """Get or sets the summary of :attr:`ScientificExtension.citation` values for this Collection. """ return self.summaries.get_list(DOI_PROP) @doi.setter - def doi(self, v: Optional[List[str]]) -> None: + def doi(self, v: list[str] | None) -> None: self._set_summary(DOI_PROP, v) diff --git a/pystac/extensions/storage.py b/pystac/extensions/storage.py index 4e5345efd..0b0aa0a1a 100644 --- a/pystac/extensions/storage.py +++ b/pystac/extensions/storage.py @@ -5,15 +5,11 @@ from __future__ import annotations +from collections.abc import Iterable from typing import ( Any, - Dict, Generic, - Iterable, - List, Literal, - Optional, - Set, TypeVar, Union, cast, @@ -75,10 +71,10 @@ class StorageExtension( def apply( self, - platform: Optional[CloudPlatform] = None, - region: Optional[str] = None, - requester_pays: Optional[bool] = None, - tier: Optional[str] = None, + platform: CloudPlatform | None = None, + region: str | None = None, + requester_pays: bool | None = None, + tier: str | None = None, ) -> None: """Applies Storage Extension properties to the extended :class:`~pystac.Item` or :class:`~pystac.Asset`. @@ -97,7 +93,7 @@ def apply( self.tier = tier @property - def platform(self) -> Optional[CloudPlatform]: + def platform(self) -> CloudPlatform | None: """Get or sets the cloud provider where data is stored. Returns: @@ -106,34 +102,34 @@ def platform(self) -> Optional[CloudPlatform]: return self._get_property(PLATFORM_PROP, CloudPlatform) @platform.setter - def platform(self, v: Optional[CloudPlatform]) -> None: + def platform(self, v: CloudPlatform | None) -> None: self._set_property(PLATFORM_PROP, v) @property - def region(self) -> Optional[str]: + def region(self) -> str | None: """Gets or sets the region where the data is stored. Relevant to speed of access and inter-region egress costs (as defined by PaaS provider).""" return self._get_property(REGION_PROP, str) @region.setter - def region(self, v: Optional[str]) -> None: + def region(self, v: str | None) -> None: self._set_property(REGION_PROP, v) @property - def requester_pays(self) -> Optional[bool]: + def requester_pays(self) -> bool | None: # This value "defaults to false", according to the extension spec. return self._get_property(REQUESTER_PAYS_PROP, bool) @requester_pays.setter - def requester_pays(self, v: Optional[bool]) -> None: + def requester_pays(self, v: bool | None) -> None: self._set_property(REQUESTER_PAYS_PROP, v) @property - def tier(self) -> Optional[str]: + def tier(self) -> str | None: return self._get_property(TIER_PROP, str) @tier.setter - def tier(self, v: Optional[str]) -> None: + def tier(self, v: str | None) -> None: self._set_property(TIER_PROP, v) @classmethod @@ -185,7 +181,7 @@ class ItemStorageExtension(StorageExtension[pystac.Item]): item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" def __init__(self, item: pystac.Item): @@ -193,7 +189,7 @@ def __init__(self, item: pystac.Item): self.properties = item.properties def __repr__(self) -> str: - return "".format(self.item.id) + return f"" class AssetStorageExtension(StorageExtension[pystac.Asset]): @@ -208,10 +204,10 @@ class AssetStorageExtension(StorageExtension[pystac.Asset]): asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """If present, this will be a list containing 1 dictionary representing the properties of the owning :class:`~pystac.Item`.""" @@ -222,11 +218,11 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" class ItemAssetsStorageExtension(StorageExtension[item_assets.AssetDefinition]): - properties: Dict[str, Any] + properties: dict[str, Any] asset_defn: item_assets.AssetDefinition def __init__(self, item_asset: item_assets.AssetDefinition): @@ -241,53 +237,53 @@ class SummariesStorageExtension(SummariesExtension): """ @property - def platform(self) -> Optional[List[CloudPlatform]]: + def platform(self) -> list[CloudPlatform] | None: """Get or sets the summary of :attr:`StorageExtension.platform` values for this Collection. """ return self.summaries.get_list(PLATFORM_PROP) @platform.setter - def platform(self, v: Optional[List[CloudPlatform]]) -> None: + def platform(self, v: list[CloudPlatform] | None) -> None: self._set_summary(PLATFORM_PROP, v) @property - def region(self) -> Optional[List[str]]: + def region(self) -> list[str] | None: """Get or sets the summary of :attr:`StorageExtension.region` values for this Collection. """ return self.summaries.get_list(REGION_PROP) @region.setter - def region(self, v: Optional[List[str]]) -> None: + def region(self, v: list[str] | None) -> None: self._set_summary(REGION_PROP, v) @property - def requester_pays(self) -> Optional[List[bool]]: + def requester_pays(self) -> list[bool] | None: """Get or sets the summary of :attr:`StorageExtension.requester_pays` values for this Collection. """ return self.summaries.get_list(REQUESTER_PAYS_PROP) @requester_pays.setter - def requester_pays(self, v: Optional[List[bool]]) -> None: + def requester_pays(self, v: list[bool] | None) -> None: self._set_summary(REQUESTER_PAYS_PROP, v) @property - def tier(self) -> Optional[List[str]]: + def tier(self) -> list[str] | None: """Get or sets the summary of :attr:`StorageExtension.tier` values for this Collection. """ return self.summaries.get_list(TIER_PROP) @tier.setter - def tier(self, v: Optional[List[str]]) -> None: + def tier(self, v: list[str] | None) -> None: self._set_summary(TIER_PROP, v) class StorageExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set() + prev_extension_ids: set[str] = set() stac_object_types = { pystac.STACObjectType.COLLECTION, pystac.STACObjectType.ITEM, diff --git a/pystac/extensions/table.py b/pystac/extensions/table.py index c99a56f99..782d741a2 100644 --- a/pystac/extensions/table.py +++ b/pystac/extensions/table.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar, Union, cast +from typing import Any, Generic, Literal, TypeVar, Union, cast import pystac from pystac.extensions import item_assets @@ -36,9 +36,9 @@ class Column: """Object representing a column of a table.""" - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]): + def __init__(self, properties: dict[str, Any]): self.properties = properties @property @@ -53,32 +53,32 @@ def name(self, v: str) -> None: self.properties[COL_NAME_PROP] = v @property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Detailed multi-line description to explain the column. `CommonMark 0.29 `__ syntax MAY be used for rich text representation.""" return self.properties.get(COL_DESCRIPTION_PROP) @description.setter - def description(self, v: Optional[str]) -> None: + def description(self, v: str | None) -> None: if v is None: self.properties.pop(COL_DESCRIPTION_PROP, None) else: self.properties[COL_DESCRIPTION_PROP] = v @property - def col_type(self) -> Optional[str]: + def col_type(self) -> str | None: """Data type of the column. If using a file format with a type system (like Parquet), we recommend you use those types""" return self.properties.get(COL_TYPE_PROP) @col_type.setter - def col_type(self, v: Optional[str]) -> None: + def col_type(self, v: str | None) -> None: if v is None: self.properties.pop(COL_TYPE_PROP, None) else: self.properties[COL_TYPE_PROP] = v - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns a dictionary representing this ``Column``.""" return self.properties @@ -86,9 +86,9 @@ def to_dict(self) -> Dict[str, Any]: class Table: """Object containing a high-level summary about a table""" - properties: Dict[str, Any] + properties: dict[str, Any] - def __init__(self, properties: Dict[str, Any]): + def __init__(self, properties: dict[str, Any]): self.properties = properties @property @@ -101,19 +101,19 @@ def name(self, v: str) -> None: self.properties[COL_NAME_PROP] = v @property - def description(self) -> Optional[str]: + def description(self) -> str | None: """Detailed multi-line description to explain the table. `CommonMark 0.29 `__ syntax MAY be used for rich text representation.""" return self.properties.get(COL_DESCRIPTION_PROP) @description.setter - def description(self, v: Optional[str]) -> None: + def description(self, v: str | None) -> None: if v is None: self.properties.pop(COL_DESCRIPTION_PROP, None) else: self.properties[COL_DESCRIPTION_PROP] = v - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns a dictionary representing this ``Table``.""" return self.properties @@ -172,7 +172,7 @@ def ext(cls, obj: T, add_if_missing: bool = False) -> TableExtension[T]: raise pystac.ExtensionTypeError(cls._ext_error_message(obj)) @property - def columns(self) -> Optional[List[Column]]: + def columns(self) -> list[Column] | None: """A list of :class:`Column` objects describing each column""" v = self.properties.get(COLUMNS_PROP) if v is None: @@ -180,28 +180,28 @@ def columns(self) -> Optional[List[Column]]: return [Column(x) for x in v] @columns.setter - def columns(self, v: Optional[List[Column]]) -> None: + def columns(self, v: list[Column] | None) -> None: self._set_property(COLUMNS_PROP, v) @property - def primary_geometry(self) -> Optional[str]: + def primary_geometry(self) -> str | None: """The primary geometry column name""" return self._get_property(PRIMARY_GEOMETRY_PROP, str) @primary_geometry.setter - def primary_geometry(self, v: Optional[str]) -> None: + def primary_geometry(self, v: str | None) -> None: if v is None: self.properties.pop(PRIMARY_GEOMETRY_PROP, None) else: self.properties[PRIMARY_GEOMETRY_PROP] = v @property - def row_count(self) -> Optional[int]: + def row_count(self) -> int | None: """The number of rows in the dataset""" return self._get_property(ROW_COUNT_PROP, int) @row_count.setter - def row_count(self, v: Optional[int]) -> None: + def row_count(self, v: int | None) -> None: if v is None: self.properties.pop(ROW_COUNT_PROP, None) else: @@ -218,23 +218,23 @@ class CollectionTableExtension(TableExtension[pystac.Collection]): """ collection: pystac.Collection - properties: Dict[str, Any] + properties: dict[str, Any] def __init__(self, collection: pystac.Collection): self.collection = collection self.properties = collection.extra_fields @property - def tables(self) -> Dict[str, Table]: + def tables(self) -> dict[str, Table]: """A mapping of table names to table objects""" return get_required(self.properties.get(TABLES_PROP), self, TABLES_PROP) @tables.setter - def tables(self, v: Dict[str, Table]) -> None: + def tables(self, v: dict[str, Table]) -> None: self.properties[TABLES_PROP] = v def __repr__(self) -> str: - return "".format(self.collection.id) + return f"" class ItemTableExtension(TableExtension[pystac.Item]): @@ -247,14 +247,14 @@ class ItemTableExtension(TableExtension[pystac.Item]): """ item: pystac.Item - properties: Dict[str, Any] + 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) + return f"" class AssetTableExtension(TableExtension[pystac.Asset]): @@ -267,8 +267,8 @@ class AssetTableExtension(TableExtension[pystac.Asset]): """ asset_href: str - properties: Dict[str, Any] - additional_read_properties: Optional[List[Dict[str, Any]]] + properties: dict[str, Any] + additional_read_properties: list[dict[str, Any]] | None def __init__(self, asset: pystac.Asset): self.asset_href = asset.href @@ -279,23 +279,23 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = None @property - def storage_options(self) -> Optional[Dict[str, Any]]: + def storage_options(self) -> dict[str, Any] | None: """Additional keywords for opening the dataset""" return self.properties.get(STORAGE_OPTIONS_PROP) @storage_options.setter - def storage_options(self, v: Optional[Dict[str, Any]]) -> Any: + def storage_options(self, v: dict[str, Any] | None) -> Any: if v is None: self.properties.pop(STORAGE_OPTIONS_PROP, None) else: self.properties[STORAGE_OPTIONS_PROP] = v def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" class ItemAssetsTableExtension(TableExtension[item_assets.AssetDefinition]): - properties: Dict[str, Any] + properties: dict[str, Any] asset_defn: item_assets.AssetDefinition def __init__(self, item_asset: item_assets.AssetDefinition): diff --git a/pystac/extensions/timestamps.py b/pystac/extensions/timestamps.py index b84943f73..de0a02f8d 100644 --- a/pystac/extensions/timestamps.py +++ b/pystac/extensions/timestamps.py @@ -2,8 +2,9 @@ from __future__ import annotations +from collections.abc import Iterable from datetime import datetime -from typing import Any, Dict, Generic, Iterable, Literal, Optional, TypeVar, Union, cast +from typing import Any, Generic, Literal, TypeVar, Union, cast import pystac from pystac.extensions.base import ( @@ -47,9 +48,9 @@ class TimestampsExtension( def apply( self, - published: Optional[datetime] = None, - expires: Optional[datetime] = None, - unpublished: Optional[datetime] = None, + published: datetime | None = None, + expires: datetime | None = None, + unpublished: datetime | None = None, ) -> None: """Applies timestamps extension properties to the extended Item. @@ -66,7 +67,7 @@ def apply( self.unpublished = unpublished @property - def published(self) -> Optional[datetime]: + def published(self) -> datetime | None: """Gets or sets a datetime object that represents the date and time that the corresponding data was published the first time. @@ -78,11 +79,11 @@ def published(self) -> Optional[datetime]: return map_opt(str_to_datetime, self._get_property(PUBLISHED_PROP, str)) @published.setter - def published(self, v: Optional[datetime]) -> None: + def published(self, v: datetime | None) -> None: self._set_property(PUBLISHED_PROP, map_opt(datetime_to_str, v)) @property - def expires(self) -> Optional[datetime]: + def expires(self) -> datetime | None: """Gets or sets a datetime object that represents the date and time the corresponding data expires (is not valid any longer). @@ -94,11 +95,11 @@ def expires(self) -> Optional[datetime]: return map_opt(str_to_datetime, self._get_property(EXPIRES_PROP, str)) @expires.setter - def expires(self, v: Optional[datetime]) -> None: + def expires(self, v: datetime | None) -> None: self._set_property(EXPIRES_PROP, map_opt(datetime_to_str, v)) @property - def unpublished(self) -> Optional[datetime]: + def unpublished(self) -> datetime | None: """Gets or sets a datetime object that represents the date and time the corresponding data was unpublished. @@ -110,7 +111,7 @@ def unpublished(self) -> Optional[datetime]: return map_opt(str_to_datetime, self._get_property(UNPUBLISHED_PROP, str)) @unpublished.setter - def unpublished(self, v: Optional[datetime]) -> None: + def unpublished(self, v: datetime | None) -> None: self._set_property(UNPUBLISHED_PROP, map_opt(datetime_to_str, v)) @classmethod @@ -159,7 +160,7 @@ class ItemTimestampsExtension(TimestampsExtension[pystac.Item]): item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" def __init__(self, item: pystac.Item): @@ -167,7 +168,7 @@ def __init__(self, item: pystac.Item): self.properties = item.properties def __repr__(self) -> str: - return "".format(self.item.id) + return f"" class AssetTimestampsExtension(TimestampsExtension[pystac.Asset]): @@ -182,10 +183,10 @@ class AssetTimestampsExtension(TimestampsExtension[pystac.Asset]): asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """If present, this will be a list containing 1 dictionary representing the properties of the owning :class:`~pystac.Item`.""" @@ -196,7 +197,7 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" class SummariesTimestampsExtension(SummariesExtension): @@ -206,7 +207,7 @@ class SummariesTimestampsExtension(SummariesExtension): """ @property - def published(self) -> Optional[RangeSummary[datetime]]: + def published(self) -> RangeSummary[datetime] | None: """Get or sets the summary of :attr:`TimestampsExtension.published` values for this Collection. """ @@ -219,7 +220,7 @@ def published(self) -> Optional[RangeSummary[datetime]]: ) @published.setter - def published(self, v: Optional[RangeSummary[datetime]]) -> None: + def published(self, v: RangeSummary[datetime] | None) -> None: self._set_summary( PUBLISHED_PROP, map_opt( @@ -231,7 +232,7 @@ def published(self, v: Optional[RangeSummary[datetime]]) -> None: ) @property - def expires(self) -> Optional[RangeSummary[datetime]]: + def expires(self) -> RangeSummary[datetime] | None: """Get or sets the summary of :attr:`TimestampsExtension.expires` values for this Collection. """ @@ -244,7 +245,7 @@ def expires(self) -> Optional[RangeSummary[datetime]]: ) @expires.setter - def expires(self, v: Optional[RangeSummary[datetime]]) -> None: + def expires(self, v: RangeSummary[datetime] | None) -> None: self._set_summary( EXPIRES_PROP, map_opt( @@ -256,7 +257,7 @@ def expires(self, v: Optional[RangeSummary[datetime]]) -> None: ) @property - def unpublished(self) -> Optional[RangeSummary[datetime]]: + def unpublished(self) -> RangeSummary[datetime] | None: """Get or sets the summary of :attr:`TimestampsExtension.unpublished` values for this Collection. """ @@ -269,7 +270,7 @@ def unpublished(self) -> Optional[RangeSummary[datetime]]: ) @unpublished.setter - def unpublished(self, v: Optional[RangeSummary[datetime]]) -> None: + def unpublished(self, v: RangeSummary[datetime] | None) -> None: self._set_summary( UNPUBLISHED_PROP, map_opt( diff --git a/pystac/extensions/version.py b/pystac/extensions/version.py index 1bb6729f4..56490d072 100644 --- a/pystac/extensions/version.py +++ b/pystac/extensions/version.py @@ -2,15 +2,12 @@ from __future__ import annotations import warnings +from collections.abc import Generator from contextlib import contextmanager from typing import ( Any, - Dict, - Generator, Generic, - List, Literal, - Optional, TypeVar, Union, cast, @@ -80,10 +77,10 @@ def __init__(self, obj: pystac.STACObject) -> None: def apply( self, version: str, - deprecated: Optional[bool] = None, - latest: Optional[T] = None, - predecessor: Optional[T] = None, - successor: Optional[T] = None, + deprecated: bool | None = None, + latest: T | None = None, + predecessor: T | None = None, + successor: T | None = None, ) -> None: """Applies version extension properties to the extended :class:`~pystac.Item` or :class:`~pystac.Collection`. @@ -119,7 +116,7 @@ def version(self, v: str) -> None: self._set_property(VERSION, v, pop_if_none=False) @property - def deprecated(self) -> Optional[bool]: + def deprecated(self) -> bool | None: """Get or sets whether the item is deprecated. A value of ``True`` specifies that the Collection or Item is deprecated with the @@ -141,11 +138,11 @@ def deprecated(self) -> Optional[bool]: return self._get_property(DEPRECATED, bool) @deprecated.setter - def deprecated(self, v: Optional[bool]) -> None: + def deprecated(self, v: bool | None) -> None: self._set_property(DEPRECATED, v) @property - def latest(self) -> Optional[T]: + def latest(self) -> T | None: """Gets or sets the :class:`~pystac.Link` to the :class:`~pystac.Item` representing the most recent version. """ @@ -155,7 +152,7 @@ def latest(self) -> Optional[T]: ) @latest.setter - def latest(self, item_or_collection: Optional[T]) -> None: + def latest(self, item_or_collection: T | None) -> None: self.obj.clear_links(VersionRelType.LATEST) if item_or_collection is not None: self.obj.add_link( @@ -165,7 +162,7 @@ def latest(self, item_or_collection: Optional[T]) -> None: ) @property - def predecessor(self) -> Optional[T]: + def predecessor(self) -> T | None: """Gets or sets the :class:`~pystac.Link` to the :class:`~pystac.Item` representing the resource containing the predecessor version in the version history. @@ -176,7 +173,7 @@ def predecessor(self) -> Optional[T]: ) @predecessor.setter - def predecessor(self, item_or_collection: Optional[T]) -> None: + def predecessor(self, item_or_collection: T | None) -> None: self.obj.clear_links(VersionRelType.PREDECESSOR) if item_or_collection is not None: self.obj.add_link( @@ -188,7 +185,7 @@ def predecessor(self, item_or_collection: Optional[T]) -> None: ) @property - def successor(self) -> Optional[T]: + def successor(self) -> T | None: """Gets or sets the :class:`~pystac.Link` to the :class:`~pystac.Item` representing the resource containing the successor version in the version history. @@ -199,7 +196,7 @@ def successor(self) -> Optional[T]: ) @successor.setter - def successor(self, item_or_collection: Optional[T]) -> None: + def successor(self, item_or_collection: T | None) -> None: self.obj.clear_links(VersionRelType.SUCCESSOR) if item_or_collection is not None: self.obj.add_link( @@ -245,8 +242,8 @@ class CollectionVersionExtension(VersionExtension[pystac.Collection]): """ collection: pystac.Collection - links: List[pystac.Link] - properties: Dict[str, Any] + links: list[pystac.Link] + properties: dict[str, Any] def __init__(self, collection: pystac.Collection): self.collection = collection @@ -255,7 +252,7 @@ def __init__(self, collection: pystac.Collection): super().__init__(self.collection) def __repr__(self) -> str: - return "".format(self.collection.id) + return f"" class ItemVersionExtension(VersionExtension[pystac.Item]): @@ -268,8 +265,8 @@ class ItemVersionExtension(VersionExtension[pystac.Item]): """ item: pystac.Item - links: List[pystac.Link] - properties: Dict[str, Any] + links: list[pystac.Link] + properties: dict[str, Any] def __init__(self, item: pystac.Item): self.item = item @@ -278,7 +275,7 @@ def __init__(self, item: pystac.Item): super().__init__(self.item) def __repr__(self) -> str: - return "".format(self.item.id) + return f"" class VersionExtensionHooks(ExtensionHooks): @@ -286,7 +283,7 @@ class VersionExtensionHooks(ExtensionHooks): prev_extension_ids = {"version"} stac_object_types = {pystac.STACObjectType.COLLECTION, pystac.STACObjectType.ITEM} - def get_object_links(self, so: pystac.STACObject) -> Optional[List[str]]: + def get_object_links(self, so: pystac.STACObject) -> list[str] | None: if isinstance(so, pystac.Collection) or isinstance(so, pystac.Item): return [ VersionRelType.LATEST, diff --git a/pystac/extensions/view.py b/pystac/extensions/view.py index 139008506..3a91249eb 100644 --- a/pystac/extensions/view.py +++ b/pystac/extensions/view.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Any, Dict, Generic, Iterable, Literal, Optional, TypeVar, Union, cast +from collections.abc import Iterable +from typing import Any, Generic, Literal, TypeVar, Union, cast import pystac from pystac.extensions import item_assets @@ -49,11 +50,11 @@ class ViewExtension( def apply( self, - off_nadir: Optional[float] = None, - incidence_angle: Optional[float] = None, - azimuth: Optional[float] = None, - sun_azimuth: Optional[float] = None, - sun_elevation: Optional[float] = None, + off_nadir: float | None = None, + incidence_angle: float | None = None, + azimuth: float | None = None, + sun_azimuth: float | None = None, + sun_elevation: float | None = None, ) -> None: """Applies View Geometry extension properties to the extended :class:`~pystac.Item`. @@ -82,18 +83,18 @@ def apply( self.sun_elevation = sun_elevation @property - def off_nadir(self) -> Optional[float]: + def off_nadir(self) -> float | None: """Get or sets the angle from the sensor between nadir (straight down) and the scene center. Measured in degrees (0-90). """ return self._get_property(OFF_NADIR_PROP, float) @off_nadir.setter - def off_nadir(self, v: Optional[float]) -> None: + def off_nadir(self, v: float | None) -> None: self._set_property(OFF_NADIR_PROP, v) @property - def incidence_angle(self) -> Optional[float]: + def incidence_angle(self) -> float | None: """Get or sets the incidence angle is the angle between the vertical (normal) to the intercepting surface and the line of sight back to the satellite at the scene center. Measured in degrees (0-90). @@ -101,11 +102,11 @@ def incidence_angle(self) -> Optional[float]: return self._get_property(INCIDENCE_ANGLE_PROP, float) @incidence_angle.setter - def incidence_angle(self, v: Optional[float]) -> None: + def incidence_angle(self, v: float | None) -> None: self._set_property(INCIDENCE_ANGLE_PROP, v) @property - def azimuth(self) -> Optional[float]: + def azimuth(self) -> float | None: """Get or sets the viewing azimuth angle. The angle measured from the sub-satellite @@ -115,11 +116,11 @@ def azimuth(self) -> Optional[float]: return self._get_property(AZIMUTH_PROP, float) @azimuth.setter - def azimuth(self, v: Optional[float]) -> None: + def azimuth(self, v: float | None) -> None: self._set_property(AZIMUTH_PROP, v) @property - def sun_azimuth(self) -> Optional[float]: + def sun_azimuth(self) -> float | None: """Get or sets the sun azimuth angle. From the scene center point on the ground, this @@ -129,18 +130,18 @@ def sun_azimuth(self) -> Optional[float]: return self._get_property(SUN_AZIMUTH_PROP, float) @sun_azimuth.setter - def sun_azimuth(self, v: Optional[float]) -> None: + def sun_azimuth(self, v: float | None) -> None: self._set_property(SUN_AZIMUTH_PROP, v) @property - def sun_elevation(self) -> Optional[float]: + def sun_elevation(self) -> float | None: """Get or sets the sun elevation angle. The angle from the tangent of the scene center point to the sun. Measured from the horizon in degrees (0-90). """ return self._get_property(SUN_ELEVATION_PROP, float) @sun_elevation.setter - def sun_elevation(self, v: Optional[float]) -> None: + def sun_elevation(self, v: float | None) -> None: self._set_property(SUN_ELEVATION_PROP, v) @classmethod @@ -192,7 +193,7 @@ class ItemViewExtension(ViewExtension[pystac.Item]): item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" def __init__(self, item: pystac.Item): @@ -200,7 +201,7 @@ def __init__(self, item: pystac.Item): self.properties = item.properties def __repr__(self) -> str: - return "".format(self.item.id) + return f"" class AssetViewExtension(ViewExtension[pystac.Asset]): @@ -215,10 +216,10 @@ class AssetViewExtension(ViewExtension[pystac.Asset]): asset_href: str """The ``href`` value of the :class:`~pystac.Asset` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Asset` fields, including extension properties.""" - additional_read_properties: Optional[Iterable[Dict[str, Any]]] = None + additional_read_properties: Iterable[dict[str, Any]] | None = None """If present, this will be a list containing 1 dictionary representing the properties of the owning :class:`~pystac.Item`.""" @@ -229,11 +230,11 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = [asset.owner.properties] def __repr__(self) -> str: - return "".format(self.asset_href) + return f"" class ItemAssetsViewExtension(ViewExtension[item_assets.AssetDefinition]): - properties: Dict[str, Any] + properties: dict[str, Any] asset_defn: item_assets.AssetDefinition def __init__(self, item_asset: item_assets.AssetDefinition): @@ -248,58 +249,58 @@ class SummariesViewExtension(SummariesExtension): """ @property - def off_nadir(self) -> Optional[RangeSummary[float]]: + def off_nadir(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`ViewExtension.off_nadir` values for this Collection. """ return self.summaries.get_range(OFF_NADIR_PROP) @off_nadir.setter - def off_nadir(self, v: Optional[RangeSummary[float]]) -> None: + def off_nadir(self, v: RangeSummary[float] | None) -> None: self._set_summary(OFF_NADIR_PROP, v) @property - def incidence_angle(self) -> Optional[RangeSummary[float]]: + def incidence_angle(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`ViewExtension.incidence_angle` values for this Collection. """ return self.summaries.get_range(INCIDENCE_ANGLE_PROP) @incidence_angle.setter - def incidence_angle(self, v: Optional[RangeSummary[float]]) -> None: + def incidence_angle(self, v: RangeSummary[float] | None) -> None: self._set_summary(INCIDENCE_ANGLE_PROP, v) @property - def azimuth(self) -> Optional[RangeSummary[float]]: + def azimuth(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`ViewExtension.azimuth` values for this Collection. """ return self.summaries.get_range(AZIMUTH_PROP) @azimuth.setter - def azimuth(self, v: Optional[RangeSummary[float]]) -> None: + def azimuth(self, v: RangeSummary[float] | None) -> None: self._set_summary(AZIMUTH_PROP, v) @property - def sun_azimuth(self) -> Optional[RangeSummary[float]]: + def sun_azimuth(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`ViewExtension.sun_azimuth` values for this Collection. """ return self.summaries.get_range(SUN_AZIMUTH_PROP) @sun_azimuth.setter - def sun_azimuth(self, v: Optional[RangeSummary[float]]) -> None: + def sun_azimuth(self, v: RangeSummary[float] | None) -> None: self._set_summary(SUN_AZIMUTH_PROP, v) @property - def sun_elevation(self) -> Optional[RangeSummary[float]]: + def sun_elevation(self) -> RangeSummary[float] | None: """Get or sets the summary of :attr:`ViewExtension.sun_elevation` values for this Collection. """ return self.summaries.get_range(SUN_ELEVATION_PROP) @sun_elevation.setter - def sun_elevation(self, v: Optional[RangeSummary[float]]) -> None: + def sun_elevation(self, v: RangeSummary[float] | None) -> None: self._set_summary(SUN_ELEVATION_PROP, v) diff --git a/pystac/extensions/xarray_assets.py b/pystac/extensions/xarray_assets.py index 346c66637..917eb8b02 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, Literal, Optional, TypeVar, Union +from typing import Any, Generic, Literal, TypeVar, Union import pystac from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension @@ -78,14 +78,14 @@ class CollectionXarrayAssetsExtension(XarrayAssetsExtension[pystac.Collection]): """ collection: pystac.Collection - properties: Dict[str, Any] + 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) + return f"" class ItemXarrayAssetsExtension(XarrayAssetsExtension[pystac.Item]): @@ -98,14 +98,14 @@ class ItemXarrayAssetsExtension(XarrayAssetsExtension[pystac.Item]): """ item: pystac.Item - properties: Dict[str, Any] + 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) + return f"" class AssetXarrayAssetsExtension(XarrayAssetsExtension[pystac.Asset]): @@ -118,8 +118,8 @@ class AssetXarrayAssetsExtension(XarrayAssetsExtension[pystac.Asset]): """ asset: pystac.Asset - properties: Dict[str, Any] - additional_read_properties: Optional[List[Dict[str, Any]]] = None + properties: dict[str, Any] + additional_read_properties: list[dict[str, Any]] | None = None def __init__(self, asset: pystac.Asset): self.asset = asset @@ -128,31 +128,31 @@ def __init__(self, asset: pystac.Asset): self.additional_read_properties = [asset.owner.properties] @property - def storage_options(self) -> Optional[Dict[str, Any]]: + def storage_options(self) -> dict[str, Any] | None: """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: + def storage_options(self, v: dict[str, Any] | None) -> 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]]: + def open_kwargs(self) -> dict[str, Any] | None: """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: + def open_kwargs(self, v: dict[str, Any] | None) -> 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) + return f"" class XarrayAssetsExtensionHooks(ExtensionHooks): diff --git a/pystac/html/jinja_env.py b/pystac/html/jinja_env.py index 5b8ec51a4..80cf57675 100644 --- a/pystac/html/jinja_env.py +++ b/pystac/html/jinja_env.py @@ -1,7 +1,7 @@ from functools import lru_cache -@lru_cache() +@lru_cache def get_jinja_env(): # type: ignore try: from jinja2 import Environment, PackageLoader, select_autoescape diff --git a/pystac/item.py b/pystac/item.py index a8ff5de09..dae00be5f 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -2,7 +2,7 @@ import warnings from copy import copy, deepcopy -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, TypeVar, Union, cast +from typing import TYPE_CHECKING, Any, TypeVar, cast import pystac from pystac import RelType, STACError, STACObjectType @@ -67,30 +67,30 @@ class Item(STACObject): :attr:`~pystac.Asset.owner` attribute set to the created Item. """ - assets: Dict[str, Asset] + assets: dict[str, Asset] """Dictionary of :class:`~pystac.Asset` objects, each with a unique key.""" - bbox: Optional[List[float]] + bbox: list[float] | None """Bounding Box of the asset represented by this item using either 2D or 3D geometries. The length of the array is 2*n where n is the number of dimensions. Could also be None in the case of a null geometry.""" - collection: Optional[Collection] + collection: Collection | None """:class:`~pystac.Collection` to which this Item belongs, if any.""" - collection_id: Optional[str] + collection_id: str | None """The Collection ID that this item belongs to, if any.""" - datetime: Optional[Datetime] + datetime: Datetime | None """Datetime associated with this item. If ``None``, then :attr:`~pystac.CommonMetadata.start_datetime` and :attr:`~pystac.CommonMetadata.end_datetime` in :attr:`~pystac.Item.common_metadata` will supply the datetime range of the Item.""" - extra_fields: Dict[str, Any] + extra_fields: dict[str, Any] """Extra fields that are part of the top-level JSON fields the Item.""" - geometry: Optional[Dict[str, Any]] + geometry: dict[str, Any] | None """Defines the full footprint of the asset represented by this item, formatted according to `RFC 7946, section 3.1 (GeoJSON) `_.""" @@ -98,14 +98,14 @@ class Item(STACObject): id: str """Provider identifier. Unique within the STAC.""" - links: List[Link] + links: list[Link] """A list of :class:`~pystac.Link` objects representing all links associated with this Item.""" - properties: Dict[str, Any] + properties: dict[str, Any] """A dictionary of additional metadata for the Item.""" - stac_extensions: List[str] + stac_extensions: list[str] """List of extensions the Item implements.""" STAC_OBJECT_TYPE = STACObjectType.ITEM @@ -113,17 +113,17 @@ class Item(STACObject): def __init__( self, id: str, - geometry: Optional[Dict[str, Any]], - bbox: Optional[List[float]], - datetime: Optional[Datetime], - properties: Dict[str, Any], - start_datetime: Optional[Datetime] = None, - end_datetime: Optional[Datetime] = None, - stac_extensions: Optional[List[str]] = None, - href: Optional[str] = None, - collection: Optional[Union[str, Collection]] = None, - extra_fields: Optional[Dict[str, Any]] = None, - assets: Optional[Dict[str, Asset]] = None, + geometry: dict[str, Any] | None, + bbox: list[float] | None, + datetime: Datetime | None, + properties: dict[str, Any], + start_datetime: Datetime | None = None, + end_datetime: Datetime | None = None, + stac_extensions: list[str] | None = None, + href: str | None = None, + collection: str | Collection | None = None, + extra_fields: dict[str, Any] | None = None, + assets: dict[str, Asset] | None = None, ): super().__init__(stac_extensions or []) @@ -136,9 +136,9 @@ def __init__( else: self.extra_fields = extra_fields - self.assets: Dict[str, Asset] = {} + self.assets: dict[str, Asset] = {} - self.datetime: Optional[Datetime] = None + self.datetime: Datetime | None = None if start_datetime: properties["start_datetime"] = datetime_to_str(start_datetime) if end_datetime: @@ -157,7 +157,7 @@ def __init__( if href is not None: self.set_self_href(href) - self.collection_id: Optional[str] = None + self.collection_id: str | None = None if collection is not None: if isinstance(collection, Collection): self.set_collection(collection) @@ -170,9 +170,9 @@ def __init__( self.add_asset(k, asset) def __repr__(self) -> str: - return "".format(self.id) + return f"" - def set_self_href(self, href: Optional[str]) -> None: + def set_self_href(self, href: str | None) -> None: """Sets the absolute HREF that is represented by the ``rel == 'self'`` :class:`~pystac.Link`. @@ -202,7 +202,7 @@ def set_self_href(self, href: Optional[str]) -> None: new_relative_href = make_relative_href(abs_href, new_href) asset.href = new_relative_href - def get_datetime(self, asset: Optional[Asset] = None) -> Optional[Datetime]: + def get_datetime(self, asset: Asset | None = None) -> Datetime | None: """Gets an Item or an Asset datetime. If an Asset is supplied and the Item property exists on the Asset, @@ -220,7 +220,7 @@ def get_datetime(self, asset: Optional[Asset] = None) -> Optional[Datetime]: else: return str_to_datetime(asset_dt) - def set_datetime(self, datetime: Datetime, asset: Optional[Asset] = None) -> None: + def set_datetime(self, datetime: Datetime, asset: Asset | None = None) -> None: """Set an Item or an Asset datetime. If an Asset is supplied, sets the property on the Asset. @@ -233,9 +233,9 @@ def set_datetime(self, datetime: Datetime, asset: Optional[Asset] = None) -> Non def get_assets( self, - media_type: Optional[Union[str, pystac.MediaType]] = None, - role: Optional[str] = None, - ) -> Dict[str, Asset]: + media_type: str | pystac.MediaType | None = None, + role: str | None = None, + ) -> dict[str, Asset]: """Get this item's assets. Args: @@ -319,7 +319,7 @@ def make_asset_hrefs_absolute(self) -> Item: asset.href = make_absolute_href(asset.href, self_href) return self - def set_collection(self, collection: Optional[Collection]) -> Item: + def set_collection(self, collection: Collection | None) -> Item: """Set the collection of this item. This method will replace any existing Collection link and attribute for @@ -340,7 +340,7 @@ def set_collection(self, collection: Optional[Collection]) -> Item: return self - def get_collection(self) -> Optional[Collection]: + def get_collection(self) -> Collection | None: """Gets the collection of this item, if one exists. Returns: @@ -355,7 +355,7 @@ def get_collection(self) -> Optional[Collection]: Collection, collection_link.resolve_stac_object(self.get_root()).target ) - def add_derived_from(self, *items: Union[Item, str]) -> Item: + def add_derived_from(self, *items: Item | str) -> Item: """Add one or more items that this is derived from. This method will add to any existing "derived_from" links. @@ -378,7 +378,7 @@ def remove_derived_from(self, item_id: str) -> None: Args: item_id : ID of item to remove from derived_from links. """ - new_links: List[pystac.Link] = [] + new_links: list[pystac.Link] = [] for link in self.links: if link.rel != pystac.RelType.DERIVED_FROM: @@ -394,7 +394,7 @@ def remove_derived_from(self, item_id: str) -> None: new_links.append(link) self.links = new_links - def get_derived_from(self) -> List[Item]: + def get_derived_from(self) -> list[Item]: """Get the items that this is derived from. Returns: @@ -410,7 +410,7 @@ def get_derived_from(self) -> List[Item]: def to_dict( self, include_self_link: bool = True, transform_hrefs: bool = True - ) -> Dict[str, Any]: + ) -> dict[str, Any]: links = self.links if not include_self_link: links = [x for x in links if x.rel != pystac.RelType.SELF] @@ -422,7 +422,7 @@ def to_dict( else: self.properties["datetime"] = None - d: Dict[str, Any] = { + d: dict[str, Any] = { "type": "Feature", "stac_version": pystac.get_stac_version(), "id": self.id, @@ -463,7 +463,7 @@ def clone(self) -> Item: return clone - def _object_links(self) -> List[Union[str, pystac.RelType]]: + def _object_links(self) -> list[str | pystac.RelType]: return [ pystac.RelType.COLLECTION, *pystac.EXTENSION_HOOKS.get_extended_object_links(self), @@ -471,10 +471,10 @@ def _object_links(self) -> List[Union[str, pystac.RelType]]: @classmethod def from_dict( - cls: Type[T], - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional[Catalog] = None, + cls: type[T], + d: dict[str, Any], + href: str | None = None, + root: Catalog | None = None, migrate: bool = False, preserve_dict: bool = True, ) -> T: @@ -554,19 +554,19 @@ def common_metadata(self) -> pystac.CommonMetadata: return pystac.CommonMetadata(self) def full_copy( - self, root: Optional[Catalog] = None, parent: Optional[Catalog] = None + self, root: Catalog | None = None, parent: Catalog | None = None ) -> Item: return cast(Item, super().full_copy(root, parent)) @classmethod - def matches_object_type(cls, d: Dict[str, Any]) -> bool: + def matches_object_type(cls, d: dict[str, Any]) -> bool: for field in ("type", "stac_version"): if field not in d: raise pystac.STACTypeError(d, cls, f"'{field}' is missing.") return identify_stac_object_type(d) == STACObjectType.ITEM @property - def __geo_interface__(self) -> Dict[str, Any]: + def __geo_interface__(self) -> dict[str, Any]: """Returns this item as a dictionary. This just calls `to_dict` without self links or transforming any hrefs. diff --git a/pystac/item_collection.py b/pystac/item_collection.py index f5f3c4c68..d793b300e 100644 --- a/pystac/item_collection.py +++ b/pystac/item_collection.py @@ -1,16 +1,10 @@ from __future__ import annotations +from collections.abc import Collection, Iterable, Iterator from copy import deepcopy from html import escape from typing import ( Any, - Collection, - Dict, - Iterable, - Iterator, - List, - Optional, - Type, TypeVar, Union, ) @@ -21,7 +15,7 @@ from pystac.serialization.identify import identify_stac_object_type from pystac.utils import HREF, is_absolute_href, make_absolute_href, make_posix_style -ItemLike = Union[pystac.Item, Dict[str, Any]] +ItemLike = Union[pystac.Item, dict[str, Any]] C = TypeVar("C", bound="ItemCollection") @@ -86,17 +80,17 @@ class ItemCollection(Collection[pystac.Item]): # If an item is present in both ItemCollections it will occur twice """ - items: List[pystac.Item] + items: list[pystac.Item] """List of :class:`pystac.Item` instances contained in this ``ItemCollection``.""" - extra_fields: Dict[str, Any] + extra_fields: dict[str, Any] """Dictionary of additional top-level fields for the GeoJSON FeatureCollection.""" def __init__( self, items: Iterable[ItemLike], - extra_fields: Optional[Dict[str, Any]] = None, + extra_fields: dict[str, Any] | None = None, clone_items: bool = True, ): def map_item(item_or_dict: ItemLike) -> pystac.Item: @@ -121,14 +115,14 @@ def __len__(self) -> int: def __contains__(self, __x: object) -> bool: return __x in self.items - def __add__(self, other: object) -> "ItemCollection": + def __add__(self, other: object) -> ItemCollection: if not isinstance(other, ItemCollection): return NotImplemented combined = [*self.items, *other.items] return ItemCollection(items=combined) - def to_dict(self, transform_hrefs: bool = False) -> Dict[str, Any]: + def to_dict(self, transform_hrefs: bool = False) -> dict[str, Any]: """Serializes an :class:`ItemCollection` instance to a dictionary. Args: @@ -167,10 +161,10 @@ def clone(self) -> ItemCollection: @classmethod def from_dict( - cls: Type[C], - d: Dict[str, Any], + cls: type[C], + d: dict[str, Any], preserve_dict: bool = True, - root: Optional[pystac.Catalog] = None, + root: pystac.Catalog | None = None, ) -> C: """Creates a :class:`ItemCollection` instance from a dictionary. @@ -194,9 +188,7 @@ def from_dict( return cls(items=items, extra_fields=extra_fields) @classmethod - def from_file( - cls: Type[C], href: HREF, stac_io: Optional[pystac.StacIO] = None - ) -> C: + def from_file(cls: type[C], href: HREF, stac_io: pystac.StacIO | None = None) -> C: """Reads a :class:`ItemCollection` from a JSON file. Arguments: @@ -217,7 +209,7 @@ def from_file( def save_object( self, dest_href: str, - stac_io: Optional[pystac.StacIO] = None, + stac_io: pystac.StacIO | None = None, ) -> None: """Saves this instance to the ``dest_href`` location. @@ -232,7 +224,7 @@ def save_object( stac_io.save_json(dest_href, self.to_dict()) @staticmethod - def is_item_collection(d: Dict[str, Any]) -> bool: + def is_item_collection(d: dict[str, Any]) -> bool: """Checks if the given dictionary represents a valid :class:`ItemCollection`. Args: diff --git a/pystac/layout.py b/pystac/layout.py index 8ec776009..08e889872 100644 --- a/pystac/layout.py +++ b/pystac/layout.py @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from collections import OrderedDict from string import Formatter -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any, Callable import pystac @@ -95,19 +95,17 @@ class LayoutTemplate: template: str """The template string to use.""" - defaults: Dict[str, str] + defaults: dict[str, str] """A dictionary of template vars to values. These values will be used in case a value cannot be derived from a stac object.""" - template_vars: List[str] + template_vars: list[str] """List of template vars to use when templating.""" # Special template vars specific to Items ITEM_TEMPLATE_VARS = ["date", "year", "month", "day", "collection"] - def __init__( - self, template: str, defaults: Optional[Dict[str, str]] = None - ) -> None: + def __init__(self, template: str, defaults: dict[str, str] | None = None) -> None: self.template = template self.defaults = defaults or {} @@ -117,7 +115,7 @@ def __init__( v = formatter_parse_result[1] if v is not None: if formatter_parse_result[2] != "": - v = "{}:{}".format(v, formatter_parse_result[2]) + v = f"{v}:{formatter_parse_result[2]}" template_vars.append(v) self.template_vars = template_vars @@ -162,7 +160,7 @@ def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any # Allow dot-notation properties for arbitrary object values. props = template_var.split(".") - prop_source: Optional[Union[pystac.STACObject, Dict[str, Any]]] = None + prop_source: pystac.STACObject | dict[str, Any] | None = None error = pystac.TemplateError( "Cannot find property {} on {} for template {}".format( template_var, stac_object, self.template @@ -174,12 +172,12 @@ def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any prop_source = stac_object if prop_source is None and hasattr(stac_object, "properties"): - obj_props: Optional[Dict[str, Any]] = stac_object.properties + obj_props: dict[str, Any] | None = stac_object.properties if obj_props is not None and props[0] in obj_props: prop_source = obj_props if prop_source is None and hasattr(stac_object, "extra_fields"): - extra_fields: Optional[Dict[str, Any]] = stac_object.extra_fields + extra_fields: dict[str, Any] | None = stac_object.extra_fields if extra_fields is not None and props[0] in extra_fields: prop_source = extra_fields @@ -203,7 +201,7 @@ def _get_template_value(self, stac_object: STACObject, template_var: str) -> Any return v - def get_template_values(self, stac_object: STACObject) -> Dict[str, Any]: + def get_template_values(self, stac_object: STACObject) -> dict[str, Any]: """Gets a dictionary of template variables to values derived from the given stac_object. If the template vars cannot be found in the stac object, and defaults was supplied to this template, a default @@ -250,7 +248,7 @@ def substitute(self, stac_object: STACObject) -> str: s = self.template for key, value in parts.items(): - s = s.replace("${" + "{}".format(key) + "}", "{}".format(value)) + s = s.replace("${" + f"{key}" + "}", f"{value}") return s @@ -267,7 +265,7 @@ def get_href( elif isinstance(stac_object, pystac.Catalog): return self.get_catalog_href(stac_object, parent_dir, is_root) else: - raise pystac.STACError("Unknown STAC object type {}".format(stac_object)) + raise pystac.STACError(f"Unknown STAC object type {stac_object}") @abstractmethod def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: @@ -305,13 +303,13 @@ class CustomLayoutStrategy(HrefLayoutStrategy): :class:`~pystac.layout.BestPracticesLayoutStrategy` """ - catalog_func: Optional[Callable[[Catalog, str, bool], str]] + catalog_func: Callable[[Catalog, str, bool], str] | None """A function that takes a :class:`~pystac.Catalog`, a parent directory, and a boolean specifying whether or not this Catalog is the root. If it is the root, it is usually best to not create a subdirectory and put the Catalog file directly in the parent directory. Must return the string path.""" - collection_func: Optional[Callable[[Collection, str, bool], str]] + collection_func: Callable[[Collection, str, bool], str] | None """A function that is used for collections in the same manner as :attr:`~catalog_func`. This takes the same parameters.""" @@ -319,16 +317,16 @@ class CustomLayoutStrategy(HrefLayoutStrategy): """The fallback strategy to use if a function is not provided for a stac object type. Defaults to :class:`~pystac.layout.BestPracticesLayoutStrategy`.""" - item_func: Optional[Callable[[Item, str], str]] + item_func: Callable[[Item, str], str] | None """An optional function that takes an :class:`~pystac.Item` and a parent directory and returns the path to be used for the Item.""" def __init__( self, - catalog_func: Optional[Callable[[Catalog, str, bool], str]] = None, - collection_func: Optional[Callable[[Collection, str, bool], str]] = None, - item_func: Optional[Callable[[Item, str], str]] = None, - fallback_strategy: Optional[HrefLayoutStrategy] = None, + catalog_func: Callable[[Catalog, str, bool], str] | None = None, + collection_func: Callable[[Collection, str, bool], str] | None = None, + item_func: Callable[[Item, str], str] | None = None, + fallback_strategy: HrefLayoutStrategy | None = None, ): self.item_func = item_func self.collection_func = collection_func @@ -386,11 +384,11 @@ class TemplateLayoutStrategy(HrefLayoutStrategy): :class:`~pystac.layout.BestPracticesLayoutStrategy` """ - catalog_template: Optional[LayoutTemplate] + catalog_template: LayoutTemplate | None """The template string to use for catalog paths. Must be a valid template string that can be used by :class:`~pystac.layout.LayoutTemplate`.""" - collection_template: Optional[LayoutTemplate] + collection_template: LayoutTemplate | None """The template string to use for collection paths. Must be a valid template string that can be used by :class:`~pystac.layout.LayoutTemplate`.""" @@ -398,16 +396,16 @@ class TemplateLayoutStrategy(HrefLayoutStrategy): """The fallback strategy to use if a template is not provided. Defaults to :class:`~pystac.layout.BestPracticesLayoutStrategy`.""" - item_template: Optional[LayoutTemplate] + item_template: LayoutTemplate | None """The template string to use for item paths. Must be a valid template string that can be used by :class:`~pystac.layout.LayoutTemplate`.""" def __init__( self, - catalog_template: Optional[str] = None, - collection_template: Optional[str] = None, - item_template: Optional[str] = None, - fallback_strategy: Optional[HrefLayoutStrategy] = None, + catalog_template: str | None = None, + collection_template: str | None = None, + item_template: str | None = None, + fallback_strategy: HrefLayoutStrategy | None = None, ): self.catalog_template = ( LayoutTemplate(catalog_template) if catalog_template is not None else None @@ -453,7 +451,7 @@ def get_item_href(self, item: Item, parent_dir: str) -> str: else: template_path = self.item_template.substitute(item) if not template_path.endswith(".json"): - template_path = posixpath.join(template_path, "{}.json".format(item.id)) + template_path = posixpath.join(template_path, f"{item.id}.json") return posixpath.join(parent_dir, template_path) @@ -477,7 +475,7 @@ def get_catalog_href(self, cat: Catalog, parent_dir: str, is_root: bool) -> str: if is_root: cat_root = parent_dir else: - cat_root = posixpath.join(parent_dir, "{}".format(cat.id)) + cat_root = posixpath.join(parent_dir, f"{cat.id}") return posixpath.join(cat_root, cat.DEFAULT_FILE_NAME) @@ -487,14 +485,14 @@ def get_collection_href( if is_root: col_root = parent_dir else: - col_root = posixpath.join(parent_dir, "{}".format(col.id)) + col_root = posixpath.join(parent_dir, f"{col.id}") return posixpath.join(col_root, col.DEFAULT_FILE_NAME) def get_item_href(self, item: Item, parent_dir: str) -> str: - item_root = posixpath.join(parent_dir, "{}".format(item.id)) + item_root = posixpath.join(parent_dir, f"{item.id}") - return posixpath.join(item_root, "{}.json".format(item.id)) + return posixpath.join(item_root, f"{item.id}.json") class AsIsLayoutStrategy(HrefLayoutStrategy): diff --git a/pystac/link.py b/pystac/link.py index 9bc6e0282..3732f5a19 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -3,7 +3,7 @@ import os from copy import copy from html import escape -from typing import TYPE_CHECKING, Any, Dict, Optional, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar import pystac from pystac.html.jinja_env import get_jinja_env @@ -69,34 +69,34 @@ class Link(PathLike): object JSON. """ - rel: Union[str, pystac.RelType] + rel: str | pystac.RelType """The relation of the link (e.g. 'child', 'item'). Registered rel Types are preferred. See :class:`~pystac.RelType` for common media types.""" - media_type: Optional[Union[str, pystac.MediaType]] + media_type: str | pystac.MediaType | None """Optional description of the media type. Registered Media Types are preferred. See :class:`~pystac.MediaType` for common media types.""" - extra_fields: Dict[str, Any] + extra_fields: dict[str, Any] """Optional, additional fields for this link. This is used by extensions as a way to serialize and deserialize properties on link object JSON.""" - owner: Optional[STACObject] + owner: STACObject | None """The owner of this link. The link will use its owner's root catalog :class:`~pystac.resolved_object_cache.ResolvedObjectCache` to resolve objects, and will create absolute HREFs from relative HREFs against the owner's self HREF.""" - _target_href: Optional[str] - _target_object: Optional[STACObject] - _title: Optional[str] + _target_href: str | None + _target_object: STACObject | None + _title: str | None def __init__( self, - rel: Union[str, pystac.RelType], - target: Union[str, STACObject], - media_type: Optional[Union[str, pystac.MediaType]] = None, - title: Optional[str] = None, - extra_fields: Optional[Dict[str, Any]] = None, + rel: str | pystac.RelType, + target: str | STACObject, + media_type: str | pystac.MediaType | None = None, + title: str | None = None, + extra_fields: dict[str, Any] | None = None, ) -> None: self.rel = rel if isinstance(target, str): @@ -113,7 +113,7 @@ def __init__( self.extra_fields = extra_fields or {} self.owner = None - def set_owner(self, owner: Optional[STACObject]) -> Link: + def set_owner(self, owner: STACObject | None) -> Link: """Sets the owner of this link. Args: @@ -123,7 +123,7 @@ def set_owner(self, owner: Optional[STACObject]) -> Link: return self @property - def title(self) -> Optional[str]: + def title(self) -> str | None: """Optional title for this link. If not provided during instantiation, this will attempt to get the title from the STAC object that the link references.""" if self._title is not None: @@ -135,7 +135,7 @@ def title(self) -> Optional[str]: return None @title.setter - def title(self, v: Optional[str]) -> None: + def title(self, v: str | None) -> None: self._title = v @property @@ -150,7 +150,7 @@ def href(self) -> str: raise ValueError(f"{self} does not have an HREF set.") return result - def get_href(self, transform_href: bool = True) -> Optional[str]: + def get_href(self, transform_href: bool = True) -> str | None: """Gets the HREF for this link. Args: @@ -205,7 +205,7 @@ def absolute_href(self) -> str: raise ValueError(f"{self} does not have an HREF set.") return result - def get_absolute_href(self) -> Optional[str]: + def get_absolute_href(self) -> str | None: """Gets the absolute href for this link, if possible. Returns: @@ -224,7 +224,7 @@ def get_absolute_href(self) -> Optional[str]: return href @property - def target(self) -> Union[str, STACObject]: + def target(self) -> str | STACObject: """The target of the link. If the link is unresolved, or the link is to something that is not a STACObject, the target is an HREF. If resolved, the target is a STACObject.""" @@ -236,7 +236,7 @@ def target(self) -> Union[str, STACObject]: raise ValueError("No target defined for link.") @target.setter - def target(self, target: Union[str, STACObject]) -> None: + def target(self, target: str | STACObject) -> None: """Sets this link's target to a string or a STAC object.""" if isinstance(target, str): self._target_href = target @@ -245,7 +245,7 @@ def target(self, target: Union[str, STACObject]) -> None: self._target_href = None self._target_object = target - def get_target_str(self) -> Optional[str]: + def get_target_str(self) -> str | None: """Returns this link's target as a string. If a string href was provided, returns that. If not, tries to resolve @@ -266,7 +266,7 @@ def __fspath__(self) -> str: return self.absolute_href def __repr__(self) -> str: - return "".format(self.rel, self.target) + return f"" def _repr_html_(self) -> str: jinja_env = get_jinja_env() @@ -276,7 +276,7 @@ def _repr_html_(self) -> str: else: return escape(repr(self)) - def resolve_stac_object(self, root: Optional[Catalog] = None) -> Link: + def resolve_stac_object(self, root: Catalog | None = None) -> Link: """Resolves a STAC object from the HREF of this link, if the link is not already resolved. @@ -308,7 +308,7 @@ def resolve_stac_object(self, root: Optional[Catalog] = None) -> Link: target_href = make_absolute_href(target_href, start_href) obj = None - stac_io: Optional[pystac.StacIO] = None + stac_io: pystac.StacIO | None = None if root is not None: obj = root._resolved_objects.get_by_href(target_href) @@ -368,7 +368,7 @@ def is_hierarchical(self) -> bool: """ return self.rel in HIERARCHICAL_LINKS - def to_dict(self, transform_href: bool = True) -> Dict[str, Any]: + def to_dict(self, transform_href: bool = True) -> dict[str, Any]: """Returns this link as a dictionary. Args: @@ -381,7 +381,7 @@ def to_dict(self, transform_href: bool = True) -> Dict[str, Any]: dict : A serialization of the Link. """ - d: Dict[str, Any] = { + d: dict[str, Any] = { "rel": str(self.rel), "href": self.get_href(transform_href=transform_href), } @@ -415,7 +415,7 @@ def clone(self) -> Link: ) @classmethod - def from_dict(cls: Type[L], d: Dict[str, Any]) -> L: + def from_dict(cls: type[L], d: dict[str, Any]) -> L: """Deserializes a Link from a dict. Args: @@ -443,44 +443,42 @@ def from_dict(cls: Type[L], d: Dict[str, Any]) -> L: ) @classmethod - def root(cls: Type[L], c: Catalog) -> L: + def root(cls: type[L], c: Catalog) -> L: """Creates a link to a root Catalog or Collection.""" return cls(pystac.RelType.ROOT, c, media_type=pystac.MediaType.JSON) @classmethod - def parent(cls: Type[L], c: Catalog) -> L: + def parent(cls: type[L], c: Catalog) -> L: """Creates a link to a parent Catalog or Collection.""" return cls(pystac.RelType.PARENT, c, media_type=pystac.MediaType.JSON) @classmethod - def collection(cls: Type[L], c: Collection) -> L: + def collection(cls: type[L], c: Collection) -> L: """Creates a link to an item's Collection.""" return cls(pystac.RelType.COLLECTION, c, media_type=pystac.MediaType.JSON) @classmethod - def self_href(cls: Type[L], href: HREF) -> L: + def self_href(cls: type[L], href: HREF) -> L: """Creates a self link to a file's location.""" href_str = str(os.fspath(href)) return cls(pystac.RelType.SELF, href_str, media_type=pystac.MediaType.JSON) @classmethod - def child(cls: Type[L], c: Catalog, title: Optional[str] = None) -> L: + def child(cls: type[L], c: Catalog, title: str | None = None) -> L: """Creates a link to a child Catalog or Collection.""" return cls( pystac.RelType.CHILD, c, title=title, media_type=pystac.MediaType.JSON ) @classmethod - def item(cls: Type[L], item: Item, title: Optional[str] = None) -> L: + def item(cls: type[L], item: Item, title: str | None = None) -> L: """Creates a link to an Item.""" return cls( pystac.RelType.ITEM, item, title=title, media_type=pystac.MediaType.JSON ) @classmethod - def derived_from( - cls: Type[L], item: Union[Item, str], title: Optional[str] = None - ) -> L: + def derived_from(cls: type[L], item: Item | str, title: str | None = None) -> L: """Creates a link to a derived_from Item.""" return cls( pystac.RelType.DERIVED_FROM, @@ -491,9 +489,9 @@ def derived_from( @classmethod def canonical( - cls: Type[L], - item_or_collection: Union[Item, Collection], - title: Optional[str] = None, + cls: type[L], + item_or_collection: Item | Collection, + title: str | None = None, ) -> L: """Creates a canonical link to an Item or Collection.""" return cls( diff --git a/pystac/provider.py b/pystac/provider.py index e161e8054..09be4df8d 100644 --- a/pystac/provider.py +++ b/pystac/provider.py @@ -1,5 +1,5 @@ from html import escape -from typing import Any, Dict, List, Optional +from typing import Any, Optional from pystac.html.jinja_env import get_jinja_env from pystac.utils import StringEnum @@ -41,7 +41,7 @@ class Provider: information such as processing details for processors and producers, hosting details for hosts or basic contact information.""" - roles: Optional[List[ProviderRole]] + roles: Optional[list[ProviderRole]] """Optional roles of the provider. Any of licensor, producer, processor or host.""" @@ -49,7 +49,7 @@ class Provider: """Optional homepage on which the provider describes the dataset and publishes contact information.""" - extra_fields: Dict[str, Any] + extra_fields: dict[str, Any] """Dictionary containing additional top-level fields defined on the Provider object.""" @@ -57,9 +57,9 @@ def __init__( self, name: str, description: Optional[str] = None, - roles: Optional[List[ProviderRole]] = None, + roles: Optional[list[ProviderRole]] = None, url: Optional[str] = None, - extra_fields: Optional[Dict[str, Any]] = None, + extra_fields: Optional[dict[str, Any]] = None, ): self.name = name self.description = description @@ -80,13 +80,13 @@ def _repr_html_(self) -> str: else: return escape(repr(self)) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: """Returns this provider as a dictionary. Returns: dict: A serialization of the Provider. """ - d: Dict[str, Any] = {"name": self.name} + d: dict[str, Any] = {"name": self.name} if self.description is not None: d["description"] = self.description if self.roles is not None: @@ -99,7 +99,7 @@ def to_dict(self) -> Dict[str, Any]: return d @staticmethod - def from_dict(d: Dict[str, Any]) -> "Provider": + def from_dict(d: dict[str, Any]) -> "Provider": """Constructs an Provider from a dict. Returns: diff --git a/pystac/serialization/common_properties.py b/pystac/serialization/common_properties.py index 8ebcc7c27..46c1b9218 100644 --- a/pystac/serialization/common_properties.py +++ b/pystac/serialization/common_properties.py @@ -1,4 +1,5 @@ -from typing import Any, Dict, Iterable, List, Optional, Union, cast +from collections.abc import Iterable +from typing import Any, Optional, Union, cast import pystac from pystac.cache import CollectionCache @@ -7,7 +8,7 @@ def merge_common_properties( - item_dict: Dict[str, Any], + item_dict: dict[str, Any], collection_cache: Optional[CollectionCache] = None, json_href: Optional[str] = None, ) -> bool: @@ -28,7 +29,7 @@ def merge_common_properties( """ properties_merged = False - collection: Optional[Union[pystac.Collection, Dict[str, Any]]] = None + collection: Optional[Union[pystac.Collection, dict[str, Any]]] = None collection_href: Optional[str] = None stac_version = item_dict.get("stac_version") @@ -59,9 +60,9 @@ def merge_common_properties( if collection is None: # Account for 0.5 links, which were dicts if isinstance(item_dict["links"], dict): - links = list(cast(Iterable[Dict[str, Any]], item_dict["links"].values())) + links = list(cast(Iterable[dict[str, Any]], item_dict["links"].values())) else: - links = cast(List[Dict[str, Any]], item_dict["links"]) + links = cast(list[dict[str, Any]], item_dict["links"]) collection_link = next( (link for link in links if link["rel"] == pystac.RelType.COLLECTION), None @@ -78,7 +79,7 @@ def merge_common_properties( collection = pystac.StacIO.default().read_json(collection_href) if collection is not None: - collection_props: Optional[Dict[str, Any]] = None + collection_props: Optional[dict[str, Any]] = None if isinstance(collection, pystac.Collection): collection_id = collection.id collection_props = collection.extra_fields.get("properties") diff --git a/pystac/serialization/identify.py b/pystac/serialization/identify.py index c4680043d..2b8f5b6ef 100644 --- a/pystac/serialization/identify.py +++ b/pystac/serialization/identify.py @@ -2,7 +2,7 @@ from enum import Enum from functools import total_ordering -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any import pystac from pystac.version import STACVersion @@ -41,7 +41,7 @@ class STACVersionID: version_string: str version_core: str - version_prerelease: Optional[str] + version_prerelease: str | None def __init__(self, version_string: str) -> None: self.version_string = version_string @@ -87,8 +87,8 @@ class STACVersionRange: def __init__( self, - min_version: Union[str, STACVersionID] = "0.8.0", - max_version: Optional[Union[str, STACVersionID]] = None, + min_version: str | STACVersionID = "0.8.0", + max_version: str | STACVersionID | None = None, ): if isinstance(min_version, str): self.min_version = STACVersionID(min_version) @@ -124,7 +124,7 @@ def set_to_single(self, v: STACVersionID) -> None: def latest_valid_version(self) -> STACVersionID: return self.max_version - def contains(self, v: Union[str, STACVersionID]) -> bool: + def contains(self, v: str | STACVersionID) -> bool: if isinstance(v, str): v = STACVersionID(v) return self.min_version <= v <= self.max_version @@ -132,18 +132,18 @@ def contains(self, v: Union[str, STACVersionID]) -> bool: def is_single_version(self) -> bool: return self.min_version >= self.max_version - def is_earlier_than(self, v: Union[str, STACVersionID]) -> bool: + def is_earlier_than(self, v: str | STACVersionID) -> bool: if isinstance(v, str): v = STACVersionID(v) return self.max_version < v - def is_later_than(self, v: Union[str, STACVersionID]) -> bool: + 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 "".format(self.min_version, self.max_version) + return f"" class STACJSONDescription: @@ -160,13 +160,13 @@ class STACJSONDescription: object_type: STACObjectType version_range: STACVersionRange - extensions: List[str] + extensions: list[str] def __init__( self, object_type: STACObjectType, version_range: STACVersionRange, - extensions: List[str], + extensions: list[str], ) -> None: self.object_type = object_type self.version_range = version_range @@ -178,7 +178,7 @@ def __repr__(self) -> str: ) -def identify_stac_object_type(json_dict: Dict[str, Any]) -> Optional[STACObjectType]: +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``. @@ -235,7 +235,7 @@ def identify_stac_object_type(json_dict: Dict[str, Any]) -> Optional[STACObjectT return None -def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription: +def identify_stac_object(json_dict: dict[str, Any]) -> STACJSONDescription: """Determines the STACJSONDescription of the provided JSON dict. Args: diff --git a/pystac/serialization/migrate.py b/pystac/serialization/migrate.py index 0353979fe..58ef8f3da 100644 --- a/pystac/serialization/migrate.py +++ b/pystac/serialization/migrate.py @@ -1,7 +1,7 @@ from __future__ import annotations from copy import deepcopy -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Any, Callable import pystac from pystac.serialization.identify import ( @@ -16,13 +16,13 @@ def _migrate_catalog( - d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + d: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: d["type"] = pystac.STACObjectType.CATALOG def _migrate_collection_summaries( - d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + d: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: if version < "1.0.0-rc.1": for prop, summary in d.get("summaries", {}).items(): @@ -34,14 +34,14 @@ def _migrate_collection_summaries( def _migrate_collection( - d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + d: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: d["type"] = pystac.STACObjectType.COLLECTION _migrate_collection_summaries(d, version, info) def _migrate_item( - d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + d: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: # No migrations necessary for supported STAC versions (>=0.8) pass @@ -49,8 +49,8 @@ def _migrate_item( # Extensions def _migrate_item_assets( - d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription -) -> Optional[Set[str]]: + d: dict[str, Any], version: STACVersionID, info: STACJSONDescription +) -> set[str] | None: if version < "1.0.0-beta.2": if info.object_type == pystac.STACObjectType.COLLECTION: if "assets" in d: @@ -60,8 +60,8 @@ def _migrate_item_assets( def _migrate_datetime_range( - d: Dict[str, Any], version: STACVersionID, info: STACJSONDescription -) -> Optional[Set[str]]: + d: dict[str, Any], version: STACVersionID, info: STACJSONDescription +) -> set[str] | None: if version < "0.9": # Datetime range was removed if ( @@ -82,7 +82,7 @@ def _migrate_datetime_range( def _get_object_migrations() -> ( - Dict[str, Callable[[Dict[str, Any], STACVersionID, STACJSONDescription], None]] + dict[str, Callable[[dict[str, Any], STACVersionID, STACJSONDescription], None]] ): return { pystac.STACObjectType.CATALOG: _migrate_catalog, @@ -92,16 +92,17 @@ def _get_object_migrations() -> ( def _get_removed_extension_migrations() -> ( - Dict[ + dict[ str, - Tuple[ - Optional[List[STACObjectType]], - Optional[ + tuple[ + list[STACObjectType] | None, + None + | ( Callable[ - [Dict[str, Any], STACVersionID, STACJSONDescription], - Optional[Set[str]], + [dict[str, Any], STACVersionID, STACJSONDescription], + set[str] | None, ] - ], + ), ], ] ): @@ -157,13 +158,13 @@ def _get_removed_extension_migrations() -> ( # TODO: Item Assets -def _get_extension_renames() -> Dict[str, str]: +def _get_extension_renames() -> dict[str, str]: return {"asset": "item-assets"} def migrate_to_latest( - json_dict: Dict[str, Any], info: STACJSONDescription -) -> Dict[str, Any]: + json_dict: dict[str, Any], info: STACJSONDescription +) -> dict[str, Any]: """Migrates the STAC JSON to the latest version Args: diff --git a/pystac/stac_io.py b/pystac/stac_io.py index 0ef77e8e9..58698a118 100644 --- a/pystac/stac_io.py +++ b/pystac/stac_io.py @@ -4,7 +4,7 @@ import logging import os from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, Callable from urllib.error import HTTPError from urllib.request import Request, urlopen @@ -40,9 +40,9 @@ class StacIO(ABC): - _default_io: Optional[Callable[[], StacIO]] = None + _default_io: Callable[[], StacIO] | None = None - def __init__(self, headers: Optional[Dict[str, str]] = None): + def __init__(self, headers: dict[str, str] | None = None): self.headers = headers or {} @abstractmethod @@ -90,7 +90,7 @@ def write_text( """ raise NotImplementedError - def json_loads(self, txt: str, *args: Any, **kwargs: Any) -> Dict[str, Any]: + def json_loads(self, txt: str, *args: Any, **kwargs: Any) -> dict[str, Any]: """Method used internally by :class:`StacIO` instances to deserialize a dictionary from a JSON string. @@ -103,14 +103,14 @@ def json_loads(self, txt: str, *args: Any, **kwargs: Any) -> Dict[str, Any]: txt : The JSON string to deserialize to a dictionary. """ - result: Dict[str, Any] + result: dict[str, Any] if orjson is not None: result = orjson.loads(txt) else: result = json.loads(txt, *args, **kwargs) return result - def json_dumps(self, json_dict: Dict[str, Any], *args: Any, **kwargs: Any) -> str: + def json_dumps(self, json_dict: dict[str, Any], *args: Any, **kwargs: Any) -> str: """Method used internally by :class:`StacIO` instances to serialize a dictionary to a JSON string. @@ -132,9 +132,9 @@ def json_dumps(self, json_dict: Dict[str, Any], *args: Any, **kwargs: Any) -> st def stac_object_from_dict( self, - d: Dict[str, Any], - href: Optional[HREF] = None, - root: Optional[Catalog] = None, + d: dict[str, Any], + href: HREF | None = None, + root: Catalog | None = None, preserve_dict: bool = True, ) -> STACObject: """Deserializes a :class:`~pystac.STACObject` sub-class instance from a @@ -185,7 +185,7 @@ def stac_object_from_dict( raise ValueError(f"Unknown STAC object type {info.object_type}") - def read_json(self, source: HREF, *args: Any, **kwargs: Any) -> Dict[str, Any]: + def read_json(self, source: HREF, *args: Any, **kwargs: Any) -> dict[str, Any]: """Read a dict from the given source. See :func:`StacIO.read_text ` for usage of @@ -208,7 +208,7 @@ def read_json(self, source: HREF, *args: Any, **kwargs: Any) -> Dict[str, Any]: def read_stac_object( self, source: HREF, - root: Optional[Catalog] = None, + root: Catalog | None = None, *args: Any, **kwargs: Any, ) -> STACObject: @@ -239,7 +239,7 @@ def read_stac_object( def save_json( self, dest: HREF, - json_dict: Dict[str, Any], + json_dict: dict[str, Any], *args: Any, **kwargs: Any, ) -> None: @@ -300,7 +300,7 @@ def read_text_from_href(self, href: str) -> str: with urlopen(req) as f: href_contents = f.read().decode("utf-8") except HTTPError as e: - raise Exception("Could not read uri {}".format(href)) from e + raise Exception(f"Could not read uri {href}") from e else: with open(href, encoding="utf-8") as f: href_contents = f.read() @@ -342,7 +342,7 @@ class DuplicateKeyReportingMixin(StacIO): See https://github.com/stac-utils/pystac/issues/313 """ - def json_loads(self, txt: str, *_: Any, **__: Any) -> Dict[str, Any]: + def json_loads(self, txt: str, *_: Any, **__: Any) -> dict[str, Any]: """Overwrites :meth:`StacIO.json_loads ` as the internal method used by :class:`DuplicateKeyReportingMixin` for deserializing a JSON string to a dictionary while checking for duplicate object keys. @@ -351,12 +351,12 @@ def json_loads(self, txt: str, *_: Any, **__: Any) -> Dict[str, Any]: pystac.DuplicateObjectKeyError : If a duplicate object key is found. """ - result: Dict[str, Any] = json.loads( + result: dict[str, Any] = json.loads( txt, object_pairs_hook=self._report_duplicate_object_names ) return result - def read_json(self, source: HREF, *args: Any, **kwargs: Any) -> Dict[str, Any]: + def read_json(self, source: HREF, *args: Any, **kwargs: Any) -> dict[str, Any]: """Overwrites :meth:`StacIO.read_json ` for deserializing a JSON file to a dictionary while checking for duplicate object keys. @@ -375,9 +375,9 @@ def read_json(self, source: HREF, *args: Any, **kwargs: Any) -> Dict[str, Any]: @staticmethod def _report_duplicate_object_names( - object_pairs: List[Tuple[str, Any]] - ) -> Dict[str, Any]: - result: Dict[str, Any] = {} + object_pairs: list[tuple[str, Any]] + ) -> dict[str, Any]: + result: dict[str, Any] = {} for key, value in object_pairs: if key in result: raise pystac.DuplicateObjectKeyError( @@ -420,8 +420,8 @@ class RetryStacIO(DefaultStacIO): def __init__( self, - headers: Optional[Dict[str, str]] = None, - retry: Optional[Retry] = None, + headers: dict[str, str] | None = None, + retry: Retry | None = None, ): super().__init__(headers) self.retry = retry or Retry() @@ -442,6 +442,6 @@ def read_text_from_href(self, href: str) -> str: ) return cast(str, response.data.decode("utf-8")) except HTTPError as e: - raise Exception("Could not read uri {}".format(href)) from e + raise Exception(f"Could not read uri {href}") from e else: return super().read_text_from_href(href) diff --git a/pystac/stac_object.py b/pystac/stac_object.py index 143f3f90e..a9fea132f 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -1,19 +1,13 @@ from __future__ import annotations from abc import ABC, abstractmethod +from collections.abc import Iterable from html import escape from typing import ( TYPE_CHECKING, Any, Callable, - Dict, - Iterable, - List, - Optional, - Set, - Type, TypeVar, - Union, cast, ) @@ -51,11 +45,11 @@ class STACObject(ABC): id: str """The ID of the STAC Object.""" - links: List[Link] + links: list[Link] """A list of :class:`~pystac.Link` objects representing all links associated with this STAC Object.""" - stac_extensions: List[str] + stac_extensions: list[str] """A list of schema URIs for STAC Extensions implemented by this STAC Object.""" STAC_OBJECT_TYPE: STACObjectType @@ -63,11 +57,11 @@ class STACObject(ABC): _allow_parent_to_override_href: bool = True """Private attribute for whether parent objects should override on normalization""" - def __init__(self, stac_extensions: List[str]) -> None: + def __init__(self, stac_extensions: list[str]) -> None: self.links = [] self.stac_extensions = stac_extensions - def validate(self) -> List[Any]: + def validate(self) -> list[Any]: """Validate this STACObject. Returns a list of validation results, which depends on the validation @@ -90,7 +84,7 @@ def add_link(self, link: Link) -> None: link.set_owner(self) self.links.append(link) - def add_links(self, links: List[Link]) -> None: + def add_links(self, links: list[Link]) -> None: """Add links to this object's set of links. Args: @@ -100,7 +94,7 @@ def add_links(self, links: List[Link]) -> None: for link in links: self.add_link(link) - def remove_links(self, rel: Union[str, pystac.RelType]) -> None: + def remove_links(self, rel: str | pystac.RelType) -> None: """Remove links to this object's set of links that match the given ``rel``. Args: @@ -109,7 +103,7 @@ def remove_links(self, rel: Union[str, pystac.RelType]) -> None: self.links = [link for link in self.links if link.rel != rel] - def remove_hierarchical_links(self, add_canonical: bool = False) -> List[Link]: + def remove_hierarchical_links(self, add_canonical: bool = False) -> list[Link]: """Removes all hierarchical links from this object. See :py:const:`pystac.link.HIERARCHICAL_LINKS` for a list of all @@ -138,7 +132,7 @@ def remove_hierarchical_links(self, add_canonical: bool = False) -> List[Link]: self.links = keep return remove - def target_in_hierarchy(self, target: Union[str, STACObject]) -> bool: + def target_in_hierarchy(self, target: str | STACObject) -> bool: """Determine if target is somewhere in the hierarchical link tree of a STACObject. @@ -150,9 +144,7 @@ def target_in_hierarchy(self, target: Union[str, STACObject]) -> bool: for the current STACObject """ - def traverse( - obj: Union[str, STACObject], visited: Set[Union[str, STACObject]] - ) -> bool: + def traverse(obj: str | STACObject, visited: set[str | STACObject]) -> bool: if obj == target: return True if isinstance(obj, str): @@ -173,13 +165,13 @@ def traverse( return False - return traverse(self, set([self])) + return traverse(self, {self}) def get_single_link( self, - rel: Optional[Union[str, pystac.RelType]] = None, - media_type: Optional[Union[str, pystac.MediaType]] = None, - ) -> Optional[Link]: + rel: str | pystac.RelType | None = None, + media_type: str | pystac.MediaType | None = None, + ) -> Link | None: """Get a single :class:`~pystac.Link` instance associated with this object. @@ -208,9 +200,9 @@ def get_single_link( def get_links( self, - rel: Optional[Union[str, pystac.RelType]] = None, - media_type: Optional[Union[str, pystac.MediaType]] = None, - ) -> List[Link]: + rel: str | pystac.RelType | None = None, + media_type: str | pystac.MediaType | None = None, + ) -> list[Link]: """Gets the :class:`~pystac.Link` instances associated with this object. Args: @@ -234,7 +226,7 @@ def get_links( and (media_type is None or link.media_type == media_type) ] - def clear_links(self, rel: Optional[Union[str, pystac.RelType]] = None) -> None: + def clear_links(self, rel: str | pystac.RelType | None = None) -> None: """Clears all :class:`~pystac.Link` instances associated with this object. Args: @@ -245,7 +237,7 @@ def clear_links(self, rel: Optional[Union[str, pystac.RelType]] = None) -> None: else: self.links = [] - def get_root_link(self) -> Optional[Link]: + def get_root_link(self) -> Link | None: """Get the :class:`~pystac.Link` representing the root for this object. @@ -269,7 +261,7 @@ def self_href(self) -> str: raise ValueError(f"{self} does not have a self_href set.") return result - def get_self_href(self) -> Optional[str]: + def get_self_href(self) -> str | None: """Gets the absolute HREF that is represented by the ``rel == 'self'`` :class:`~pystac.Link`. @@ -290,7 +282,7 @@ def get_self_href(self) -> Optional[str]: else: return None - def set_self_href(self, href: Optional[str]) -> None: + def set_self_href(self, href: str | None) -> None: """Sets the absolute HREF that is represented by the ``rel == 'self'`` :class:`~pystac.Link`. @@ -311,7 +303,7 @@ def set_self_href(self, href: Optional[str]) -> None: if root_link is not None and root_link.is_resolved(): cast(pystac.Catalog, root_link.target)._resolved_objects.cache(self) - def get_root(self) -> Optional[Catalog]: + def get_root(self) -> Catalog | None: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to the root for this object. The root is represented by a :class:`~pystac.Link` with ``rel == 'root'``. @@ -330,7 +322,7 @@ def get_root(self) -> Optional[Catalog]: else: return None - def set_root(self, root: Optional[Catalog]) -> None: + def set_root(self, root: Catalog | None) -> None: """Sets the root :class:`~pystac.Catalog` or :class:`~pystac.Collection` for this object. @@ -366,7 +358,7 @@ def set_root(self, root: Optional[Catalog]) -> None: self.add_link(new_root_link) root._resolved_objects.cache(self) - def get_parent(self) -> Optional[Catalog]: + def get_parent(self) -> Catalog | None: """Get the :class:`~pystac.Catalog` or :class:`~pystac.Collection` to the parent for this object. The root is represented by a :class:`~pystac.Link` with ``rel == 'parent'``. @@ -382,7 +374,7 @@ def get_parent(self) -> Optional[Catalog]: else: return None - def set_parent(self, parent: Optional[Catalog]) -> None: + def set_parent(self, parent: Catalog | None) -> None: """Sets the parent :class:`~pystac.Catalog` or :class:`~pystac.Collection` for this object. @@ -397,9 +389,9 @@ def set_parent(self, parent: Optional[Catalog]) -> None: def get_stac_objects( self, - rel: Union[str, pystac.RelType], - typ: Optional[Type[STACObject]] = None, - modify_links: Optional[Callable[[List[Link]], List[Link]]] = None, + rel: str | pystac.RelType, + typ: type[STACObject] | None = None, + modify_links: Callable[[list[Link]], list[Link]] | None = None, ) -> Iterable[STACObject]: """Gets the :class:`~pystac.STACObject` instances that are linked to by links with their ``rel`` property matching the passed in argument. @@ -432,8 +424,8 @@ def get_stac_objects( def save_object( self, include_self_link: bool = True, - dest_href: Optional[str] = None, - stac_io: Optional[pystac.StacIO] = None, + dest_href: str | None = None, + stac_io: pystac.StacIO | None = None, ) -> None: """Saves this STAC Object to it's 'self' HREF. @@ -478,8 +470,8 @@ def save_object( def full_copy( self, - root: Optional[Catalog] = None, - parent: Optional[Catalog] = None, + root: Catalog | None = None, + parent: Catalog | None = None, ) -> STACObject: """Create a full copy of this STAC object and any stac objects linked to by this object. @@ -553,7 +545,7 @@ def resolve_links(self) -> None: link.resolve_stac_object(root=self.get_root()) @abstractmethod - def _object_links(self) -> List[str]: + def _object_links(self) -> list[str]: """Inherited classes return a list of link 'rel' types that represent STACObjects linked to by this object (not including root, parent or self). This can include optional relations (which may not be present). @@ -563,7 +555,7 @@ def _object_links(self) -> List[str]: @abstractmethod def to_dict( self, include_self_link: bool = True, transform_hrefs: bool = True - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Returns this object as a dictionary. Args: @@ -603,9 +595,7 @@ def clone(self) -> STACObject: raise NotImplementedError @classmethod - def from_file( - cls: Type[S], href: HREF, stac_io: Optional[pystac.StacIO] = None - ) -> S: + def from_file(cls: type[S], href: HREF, stac_io: pystac.StacIO | None = None) -> S: """Reads a STACObject implementation from a file. Args: @@ -642,10 +632,10 @@ def from_file( @classmethod @abstractmethod def from_dict( - cls: Type[S], - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional[Catalog] = None, + cls: type[S], + d: dict[str, Any], + href: str | None = None, + root: Catalog | None = None, migrate: bool = False, preserve_dict: bool = True, ) -> S: @@ -673,7 +663,7 @@ def from_dict( @classmethod @abstractmethod - def matches_object_type(cls, d: Dict[str, Any]) -> bool: + def matches_object_type(cls, d: dict[str, Any]) -> bool: """Returns a boolean indicating whether the given dictionary represents a valid instance of this :class:`~STACObject` sub-class. diff --git a/pystac/summaries.py b/pystac/summaries.py index 5e2c0c2d9..1beb7044d 100644 --- a/pystac/summaries.py +++ b/pystac/summaries.py @@ -4,17 +4,14 @@ import json import numbers from abc import abstractmethod +from collections.abc import Iterable from copy import deepcopy from enum import Enum from functools import lru_cache from typing import ( TYPE_CHECKING, Any, - Dict, Generic, - Iterable, - List, - Optional, Protocol, TypeVar, Union, @@ -51,7 +48,7 @@ class _Comparable_x(Protocol): """ @abstractmethod - def __lt__(self: "T", x: "T") -> bool: + def __lt__(self: T, x: T) -> bool: return NotImplemented @@ -63,7 +60,7 @@ class _Comparable_other(Protocol): """ @abstractmethod - def __lt__(self: "T", other: "T") -> bool: + def __lt__(self: T, other: T) -> bool: return NotImplemented @@ -78,7 +75,7 @@ def __init__(self, minimum: T, maximum: T): self.minimum = minimum self.maximum = maximum - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return {"minimum": self.minimum, "maximum": self.maximum} def update_with_value(self, v: T) -> None: @@ -86,7 +83,7 @@ def update_with_value(self, v: T) -> None: self.maximum = max(self.maximum, v) @classmethod - def from_dict(cls, d: Dict[str, Any]) -> RangeSummary[T]: + def from_dict(cls, d: dict[str, Any]) -> RangeSummary[T]: minimum: T = get_required(d.get("minimum"), "RangeSummary", "minimum") maximum: T = get_required(d.get("maximum"), "RangeSummary", "maximum") return cls(minimum=minimum, maximum=maximum) @@ -102,11 +99,11 @@ def __repr__(self) -> str: @lru_cache(maxsize=None) -def _get_fields_json(url: Optional[str]) -> Dict[str, Any]: +def _get_fields_json(url: str | None) -> dict[str, Any]: if url is None: # Every time pystac is released this file gets pulled from # https://cdn.jsdelivr.net/npm/@radiantearth/stac-fields/fields-normalized.json - jsonfields: Dict[str, Any] = json.loads( + jsonfields: dict[str, Any] = json.loads( importlib.resources.files("pystac.static") .joinpath("fields-normalized.json") .read_text() @@ -148,16 +145,16 @@ class Summarizer: If nothing is passed, a default file with field descriptions will be used. """ - summaryfields: Dict[str, SummaryStrategy] + summaryfields: dict[str, SummaryStrategy] - def __init__(self, fields: Optional[Union[str, Dict[str, SummaryStrategy]]] = None): + def __init__(self, fields: str | dict[str, SummaryStrategy] | None = None): if isinstance(fields, dict): self._set_field_definitions(fields) else: jsonfields = _get_fields_json(fields) self._set_field_definitions(jsonfields["metadata"]) - def _set_field_definitions(self, fields: Dict[str, Any]) -> None: + def _set_field_definitions(self, fields: dict[str, Any]) -> None: self.summaryfields = {} for name, desc in fields.items(): strategy: SummaryStrategy = SummaryStrategy.DEFAULT @@ -182,7 +179,7 @@ def _update_with_item(self, summaries: Summaries, item: Item) -> None: and isinstance(v, numbers.Number) and not isinstance(v, bool) ): - rangesummary: Optional[RangeSummary[Any]] = summaries.get_range(k) + rangesummary: RangeSummary[Any] | None = summaries.get_range(k) if rangesummary is None: summaries.add(k, RangeSummary(v, v)) else: @@ -190,7 +187,7 @@ def _update_with_item(self, summaries: Summaries, item: Item) -> None: elif strategy == SummaryStrategy.ARRAY or ( strategy == SummaryStrategy.DEFAULT and isinstance(v, list) ): - listsummary: List[Any] = summaries.get_list(k) or [] + listsummary: list[Any] = summaries.get_list(k) or [] if not isinstance(v, list): v = [v] for element in v: @@ -198,12 +195,12 @@ def _update_with_item(self, summaries: Summaries, item: Item) -> None: listsummary.append(element) summaries.add(k, listsummary) else: - summary: List[Any] = summaries.get_list(k) or [] + summary: list[Any] = summaries.get_list(k) or [] if v not in summary: summary.append(v) summaries.add(k, summary) - def summarize(self, source: Union[Collection, Iterable[Item]]) -> Summaries: + def summarize(self, source: Collection | Iterable[Item]) -> Summaries: """Creates summaries from items""" summaries = Summaries.empty() if isinstance(source, pystac.Collection): @@ -220,16 +217,16 @@ def summarize(self, source: Union[Collection, Iterable[Item]]) -> Summaries: class Summaries: - _summaries: Dict[str, Any] + _summaries: dict[str, Any] - lists: Dict[str, List[Any]] - other: Dict[str, Any] - ranges: Dict[str, RangeSummary[Any]] - schemas: Dict[str, Dict[str, Any]] + lists: dict[str, list[Any]] + other: dict[str, Any] + ranges: dict[str, RangeSummary[Any]] + schemas: dict[str, dict[str, Any]] maxcount: int def __init__( - self, summaries: Dict[str, Any], maxcount: int = DEFAULT_MAXCOUNT + self, summaries: dict[str, Any], maxcount: int = DEFAULT_MAXCOUNT ) -> None: self._summaries = summaries self.maxcount = maxcount @@ -242,19 +239,19 @@ def __init__( for prop_key, summary in summaries.items(): self.add(prop_key, summary) - def get_list(self, prop: str) -> Optional[List[Any]]: + def get_list(self, prop: str) -> list[Any] | None: return self.lists.get(prop) - def get_range(self, prop: str) -> Optional[RangeSummary[Any]]: + def get_range(self, prop: str) -> RangeSummary[Any] | None: return self.ranges.get(prop) - def get_schema(self, prop: str) -> Optional[Dict[str, Any]]: + def get_schema(self, prop: str) -> dict[str, Any] | None: return self.schemas.get(prop) def add( self, prop_key: str, - summary: Union[List[Any], RangeSummary[Any], Dict[str, Any]], + summary: list[Any] | RangeSummary[Any] | dict[str, Any], ) -> None: if isinstance(summary, list): self.lists[prop_key] = summary @@ -322,7 +319,7 @@ def clone(self) -> Summaries: summaries.schemas = deepcopy(self.schemas) return summaries - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return { **{k: v for k, v in self.lists.items() if len(v) < self.maxcount}, **{k: v.to_dict() for k, v in self.ranges.items()}, diff --git a/pystac/utils.py b/pystac/utils.py index f7063093e..be5a3196e 100644 --- a/pystac/utils.py +++ b/pystac/utils.py @@ -7,8 +7,6 @@ TYPE_CHECKING, Any, Callable, - Dict, - List, Optional, TypeVar, Union, @@ -59,10 +57,10 @@ def safe_urlparse(href: str) -> URLParseResult: """ parsed = urlparse(href) if parsed.scheme != "" and ( - href.lower().startswith("{}:\\".format(parsed.scheme)) + href.lower().startswith(f"{parsed.scheme}:\\") or ( - href.lower().startswith("{}:/".format(parsed.scheme)) - and not href.lower().startswith("{}://".format(parsed.scheme)) + href.lower().startswith(f"{parsed.scheme}:/") + and not href.lower().startswith(f"{parsed.scheme}://") ) ): return URLParseResult( @@ -396,7 +394,7 @@ def datetime_to_str(dt: datetime, timespec: str = "auto") -> str: timestamp = dt.isoformat(timespec=timespec) zulu = "+00:00" if timestamp.endswith(zulu): - timestamp = "{}Z".format(timestamp[: -len(zulu)]) + timestamp = f"{timestamp[: -len(zulu)]}Z" return timestamp @@ -426,7 +424,7 @@ def now_to_rfc3339_str() -> str: return datetime_to_str(now_in_utc()) -def geometry_to_bbox(geometry: Dict[str, Any]) -> List[float]: +def geometry_to_bbox(geometry: dict[str, Any]) -> list[float]: """Extract the bounding box from a geojson geometry Args: @@ -438,10 +436,10 @@ def geometry_to_bbox(geometry: Dict[str, Any]) -> List[float]: """ coords = geometry["coordinates"] - lats: List[float] = [] - lons: List[float] = [] + lats: list[float] = [] + lons: list[float] = [] - def extract_coords(coords: List[Union[List[float], List[List[Any]]]]) -> None: + def extract_coords(coords: list[Union[list[float], list[list[Any]]]]) -> None: for x in coords: # This handles points if isinstance(x, float): diff --git a/pystac/validation/__init__.py b/pystac/validation/__init__.py index 3375cce25..febe9c3f1 100644 --- a/pystac/validation/__init__.py +++ b/pystac/validation/__init__.py @@ -2,7 +2,7 @@ import warnings from collections.abc import Iterable, Mapping -from typing import TYPE_CHECKING, Any, Optional, Union, cast +from typing import TYPE_CHECKING, Any, cast import pystac from pystac.serialization.identify import STACVersionID, identify_stac_object @@ -43,10 +43,10 @@ def validate(stac_object: STACObject) -> list[Any]: def validate_dict( stac_dict: dict[str, Any], - stac_object_type: Optional[STACObjectType] = None, - stac_version: Optional[str] = None, - extensions: Optional[list[str]] = None, - href: Optional[str] = None, + stac_object_type: STACObjectType | None = None, + stac_version: str | None = None, + extensions: list[str] | None = None, + href: str | None = None, ) -> list[Any]: """Validate a stac object serialized as JSON into a dict. @@ -94,7 +94,7 @@ def validate_dict( # their schemas. if stac_version_id < "1.0.0-rc.2": - def _get_uri(ext: str) -> Optional[str]: + def _get_uri(ext: str) -> str | None: return OldExtensionSchemaUriMap.get_extension_schema_uri( ext, stac_object_type, @@ -109,9 +109,9 @@ def _get_uri(ext: str) -> Optional[str]: def validate_all( - stac_object: Union[STACObject, dict[str, Any]], - href: Optional[str] = None, - stac_io: Optional[pystac.StacIO] = None, + stac_object: STACObject | dict[str, Any], + href: str | None = None, + stac_io: pystac.StacIO | None = None, ) -> None: """Validate a :class:`~pystac.STACObject`, or a STAC object serialized as JSON into a dict. @@ -162,8 +162,8 @@ def validate_all( def validate_all_dict( stac_dict: dict[str, Any], - href: Optional[str], - stac_io: Optional[pystac.StacIO] = None, + href: str | None, + stac_io: pystac.StacIO | None = None, ) -> None: """Validate a stac object serialized as JSON into a dict. @@ -212,7 +212,7 @@ def validate_all_dict( class RegisteredValidator: - _validator: Optional[STACValidator] = None + _validator: STACValidator | None = None @classmethod def get_validator(cls) -> STACValidator: @@ -233,7 +233,7 @@ def get_validator(cls) -> STACValidator: @classmethod def set_validator(cls, validator: STACValidator) -> None: if not issubclass(type(validator), STACValidator): - raise Exception("Validator must be a subclass of {}".format(STACValidator)) + raise Exception(f"Validator must be a subclass of {STACValidator}") cls._validator = validator diff --git a/pystac/validation/local_validator.py b/pystac/validation/local_validator.py index 9aee19287..06dc8ce37 100644 --- a/pystac/validation/local_validator.py +++ b/pystac/validation/local_validator.py @@ -1,7 +1,7 @@ import importlib.resources import json import warnings -from typing import Any, Dict, List, cast +from typing import Any, cast from jsonschema import Draft7Validator, ValidationError from referencing import Registry, Resource @@ -12,14 +12,14 @@ VERSION = STACVersion.DEFAULT_STAC_VERSION -def _read_schema(file_name: str) -> Dict[str, Any]: +def _read_schema(file_name: str) -> dict[str, Any]: with importlib.resources.files("pystac.validation.jsonschemas").joinpath( file_name ).open("r") as f: - return cast(Dict[str, Any], json.load(f)) + return cast(dict[str, Any], json.load(f)) -def get_local_schema_cache() -> Dict[str, Dict[str, Any]]: +def get_local_schema_cache() -> dict[str, dict[str, Any]]: return { **{ ( @@ -90,8 +90,8 @@ def registry(self) -> Any: ) def _validate_from_local( - self, schema_uri: str, stac_dict: Dict[str, Any] - ) -> List[ValidationError]: + self, schema_uri: str, stac_dict: dict[str, Any] + ) -> list[ValidationError]: if schema_uri == _deprecated_ITEM_SCHEMA_URI: validator = self.item_validator(VERSION) elif schema_uri == _deprecated_COLLECTION_SCHEMA_URI: diff --git a/pystac/validation/schema_uri_map.py b/pystac/validation/schema_uri_map.py index 3014c17ff..0eebc9e50 100644 --- a/pystac/validation/schema_uri_map.py +++ b/pystac/validation/schema_uri_map.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod from functools import lru_cache -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, Optional import pystac from pystac.serialization import STACVersionRange @@ -43,10 +43,10 @@ class DefaultSchemaUriMap(SchemaUriMap): # for a particular version uses the base URI associated with the version range which # contains it. If the version it outside of any VersionRange, there is no URI for # the schema. - BASE_URIS: List[Tuple[STACVersionRange, Callable[[str], str]]] = [ + BASE_URIS: list[tuple[STACVersionRange, Callable[[str], str]]] = [ ( STACVersionRange(min_version="1.0.0-beta.2"), - lambda version: "https://schemas.stacspec.org/v{}".format(version), + lambda version: f"https://schemas.stacspec.org/v{version}", ), ( STACVersionRange(min_version="0.8.0", max_version="1.0.0-beta.1"), @@ -64,7 +64,7 @@ class DefaultSchemaUriMap(SchemaUriMap): # is the latest version. If it's a previous version, the stac_version that matches # the listed version range is used, or else the URI from the latest version is used # if there are no overrides for previous versions. - DEFAULT_SCHEMA_MAP: Dict[str, Any] = { + DEFAULT_SCHEMA_MAP: dict[str, Any] = { STACObjectType.CATALOG: ("catalog-spec/json-schema/catalog.json", None), STACObjectType.COLLECTION: ( "collection-spec/json-schema/collection.json", @@ -80,7 +80,7 @@ def _append_base_uri_if_needed(cls, uri: str, stac_version: str) -> Optional[str for version_range, f in cls.BASE_URIS: if version_range.contains(stac_version): base_uri = f(stac_version) - return "{}/{}".format(base_uri, uri) + return f"{base_uri}/{uri}" # We don't have JSON schema validation for this version of PySTAC return None @@ -93,7 +93,7 @@ def get_object_schema_uri( is_latest = stac_version == pystac.get_stac_version() if object_type not in self.DEFAULT_SCHEMA_MAP: - raise KeyError("Unknown STAC object type {}".format(object_type)) + raise KeyError(f"Unknown STAC object type {object_type}") uri = self.DEFAULT_SCHEMA_MAP[object_type][0] if not is_latest: if self.DEFAULT_SCHEMA_MAP[object_type][1]: @@ -118,10 +118,10 @@ class OldExtensionSchemaUriMap: # contains it. If the version it outside of any VersionRange, there is no URI for # the schema. @classmethod - @lru_cache() + @lru_cache def get_base_uris( cls, - ) -> List[Tuple[STACVersionRange, Callable[[STACVersionID], str]]]: + ) -> list[tuple[STACVersionRange, Callable[[STACVersionID], str]]]: return [ ( STACVersionRange(min_version="1.0.0-beta.2"), @@ -144,8 +144,8 @@ def get_base_uris( # that matches the listed version range is used, or else the URI from the latest # version is used if there are no overrides for previous versions. @classmethod - @lru_cache() - def get_schema_map(cls) -> Dict[str, Any]: + @lru_cache + def get_schema_map(cls) -> dict[str, Any]: return { OldExtensionShortIDs.CHECKSUM.value: ( { @@ -316,7 +316,7 @@ def _append_base_uri_if_needed( for version_range, f in cls.get_base_uris(): if version_range.contains(stac_version): base_uri = f(stac_version) - return "{}/{}".format(base_uri, uri) + return f"{base_uri}/{uri}" # No JSON Schema for the old extension return None diff --git a/pystac/validation/stac_validator.py b/pystac/validation/stac_validator.py index 1117e9fd3..afaed7f98 100644 --- a/pystac/validation/stac_validator.py +++ b/pystac/validation/stac_validator.py @@ -2,7 +2,7 @@ import logging import warnings from abc import ABC, abstractmethod -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional import pystac import pystac.utils @@ -36,7 +36,7 @@ class STACValidator(ABC): @abstractmethod def validate_core( self, - stac_dict: Dict[str, Any], + stac_dict: dict[str, Any], stac_object_type: STACObjectType, stac_version: str, href: Optional[str] = None, @@ -57,7 +57,7 @@ def validate_core( @abstractmethod def validate_extension( self, - stac_dict: Dict[str, Any], + stac_dict: dict[str, Any], stac_object_type: STACObjectType, stac_version: str, extension_id: str, @@ -79,12 +79,12 @@ def validate_extension( def validate( self, - stac_dict: Dict[str, Any], + stac_dict: dict[str, Any], stac_object_type: STACObjectType, stac_version: str, - extensions: List[str], + extensions: list[str], href: Optional[str] = None, - ) -> List[Any]: + ) -> list[Any]: """Validate a STAC object JSON. Args: @@ -100,7 +100,7 @@ def validate( core object and any extensions. Element type is specific to the STACValidator implementation. """ - results: List[Any] = [] + results: list[Any] = [] # Pass the dict through JSON serialization and parsing, otherwise # some valid properties can be marked as invalid (e.g. tuples in @@ -140,7 +140,7 @@ class JsonSchemaSTACValidator(STACValidator): """ schema_uri_map: SchemaUriMap - schema_cache: Dict[str, Dict[str, Any]] + schema_cache: dict[str, dict[str, Any]] def __init__(self, schema_uri_map: Optional[SchemaUriMap] = None) -> None: if not HAS_JSONSCHEMA: @@ -153,7 +153,7 @@ def __init__(self, schema_uri_map: Optional[SchemaUriMap] = None) -> None: self.schema_cache = get_local_schema_cache() - def _get_schema(self, schema_uri: str) -> Dict[str, Any]: + def _get_schema(self, schema_uri: str) -> dict[str, Any]: if schema_uri not in self.schema_cache: s = json.loads(pystac.StacIO.default().read_text(schema_uri)) self.schema_cache[schema_uri] = s @@ -164,7 +164,7 @@ def _get_schema(self, schema_uri: str) -> Dict[str, Any]: @property def registry(self) -> Any: - def retrieve(schema_uri: str) -> Resource[Dict[str, Any]]: + def retrieve(schema_uri: str) -> Resource[dict[str, Any]]: return Resource.from_contents(self._get_schema(schema_uri)) return Registry(retrieve=retrieve).with_resources( # type: ignore @@ -173,7 +173,7 @@ def retrieve(schema_uri: str) -> Resource[Dict[str, Any]]: ] # type: ignore ) - def get_schema_from_uri(self, schema_uri: str) -> Tuple[Dict[str, Any], Any]: + def get_schema_from_uri(self, schema_uri: str) -> tuple[dict[str, Any], Any]: """DEPRECATED""" warnings.warn( "get_schema_from_uri is deprecated and will be removed in v2.", @@ -183,7 +183,7 @@ def get_schema_from_uri(self, schema_uri: str) -> Tuple[Dict[str, Any], Any]: def _validate_from_uri( self, - stac_dict: Dict[str, Any], + stac_dict: dict[str, Any], stac_object_type: STACObjectType, schema_uri: str, href: Optional[str] = None, @@ -216,7 +216,7 @@ def _validate_from_uri( def validate_core( self, - stac_dict: Dict[str, Any], + stac_dict: dict[str, Any], stac_object_type: STACObjectType, stac_version: str, href: Optional[str] = None, @@ -255,7 +255,7 @@ def validate_core( def validate_extension( self, - stac_dict: Dict[str, Any], + stac_dict: dict[str, Any], stac_object_type: STACObjectType, stac_version: str, extension_id: str, diff --git a/tests/data-files/get_examples.py b/tests/data-files/get_examples.py index 3a4276c43..9afb167b0 100644 --- a/tests/data-files/get_examples.py +++ b/tests/data-files/get_examples.py @@ -7,17 +7,17 @@ import os import tempfile from subprocess import call -from typing import Any, Dict, List, Optional +from typing import Any, Optional from urllib.error import HTTPError import pystac from pystac.serialization import identify_stac_object -def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: - links: Optional[List[Dict[str, Any]]] = js.get("links") +def remove_bad_collection(js: dict[str, Any]) -> dict[str, Any]: + links: Optional[list[dict[str, Any]]] = js.get("links") if links is not None: - filtered_links: List[Dict[str, Any]] = [] + filtered_links: list[dict[str, Any]] = [] for link in links: rel = link.get("rel") if rel is not None and rel == "collection": @@ -26,7 +26,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: json.loads(pystac.StacIO.default().read_text(href)) filtered_links.append(link) except (HTTPError, FileNotFoundError, json.decoder.JSONDecodeError): - print("===REMOVING UNREADABLE COLLECTION AT {}".format(href)) + print(f"===REMOVING UNREADABLE COLLECTION AT {href}") else: filtered_links.append(link) js["links"] = filtered_links @@ -46,7 +46,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: args = parser.parse_args() stac_repo = "https://github.com/radiantearth/stac-spec" - stac_spec_tag = "v{}".format(pystac.get_stac_version()) + stac_spec_tag = f"v{pystac.get_stac_version()}" examples_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "examples")) @@ -64,11 +64,11 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: ] ) - example_dirs: List[str] = [] + example_dirs: list[str] = [] for root, _, _ in os.walk(tmp_dir): example_dirs.append(os.path.join(root)) - example_csv_lines = set([]) + example_csv_lines = set() for example_dir in example_dirs: for root, _, files in os.walk(example_dir): @@ -77,7 +77,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: path = os.path.join(root, fname) with open(path) as f: try: - js: Dict[str, Any] = json.loads(f.read()) + js: dict[str, Any] = json.loads(f.read()) except json.decoder.JSONDecodeError: # Account for bad examples that can't be parsed. js = {} @@ -88,11 +88,11 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: ): relpath = "{}/{}".format( pystac.get_stac_version(), - path.replace("{}/".format(tmp_dir), ""), + path.replace(f"{tmp_dir}/", ""), ) target_path = os.path.join(examples_dir, relpath) - print("Creating example at {}".format(target_path)) + print(f"Creating example at {target_path}") info = identify_stac_object(js) @@ -109,7 +109,7 @@ def remove_bad_collection(js: Dict[str, Any]) -> Dict[str, Any]: f.write(json.dumps(js, indent=4)) # Add info to the new example-info.csv lines - line_info: List[str] = [ + line_info: list[str] = [ relpath, info.object_type, example_version, diff --git a/tests/extensions/test_classification.py b/tests/extensions/test_classification.py index 236265063..33f139cf3 100644 --- a/tests/extensions/test_classification.py +++ b/tests/extensions/test_classification.py @@ -2,7 +2,7 @@ import logging from copy import deepcopy from datetime import datetime -from typing import Any, Dict, List, cast +from typing import Any, cast import pytest from dateutil.parser import parse @@ -35,9 +35,9 @@ @pytest.fixture -def item_dict() -> Dict[str, Any]: +def item_dict() -> dict[str, Any]: with open(LANDSAT_EXAMPLE_URI) as f: - return cast(Dict[str, Any], json.load(f)) + return cast(dict[str, Any], json.load(f)) @pytest.fixture @@ -136,17 +136,17 @@ def test_apply_bitfields(plain_item: Item) -> None: plain_item.validate() assert ( ClassificationExtension.ext(plain_item).bitfields is not None - and len(cast(List[Bitfield], ClassificationExtension.ext(plain_item).bitfields)) + and len(cast(list[Bitfield], ClassificationExtension.ext(plain_item).bitfields)) == 1 ) assert ( - cast(List[Bitfield], ClassificationExtension.ext(plain_item).bitfields)[ + cast(list[Bitfield], ClassificationExtension.ext(plain_item).bitfields)[ 0 ].offset == 0 ) assert ( - cast(List[Bitfield], ClassificationExtension.ext(plain_item).bitfields)[ + cast(list[Bitfield], ClassificationExtension.ext(plain_item).bitfields)[ 0 ].length == 1 @@ -154,7 +154,7 @@ def test_apply_bitfields(plain_item: Item) -> None: assert ( ClassificationExtension.ext(plain_item).bitfields is not None and len( - cast(List[Bitfield], ClassificationExtension.ext(plain_item).bitfields)[ + cast(list[Bitfield], ClassificationExtension.ext(plain_item).bitfields)[ 0 ].classes ) @@ -226,8 +226,8 @@ def test_color_hint_formatting() -> None: Classification.create(value=0, description="water", color_hint="0000FF") -def test_to_from_dict(item_dict: Dict[str, Any]) -> None: - def _parse_times(a_dict: Dict[str, Any]) -> None: +def test_to_from_dict(item_dict: dict[str, Any]) -> None: + def _parse_times(a_dict: dict[str, Any]) -> None: for k, v in a_dict.items(): if isinstance(v, dict): _parse_times(v) @@ -296,7 +296,7 @@ def test_item_asset_raster_classes(collection: Collection) -> None: item_asset = ItemAssetsExtension.ext(collection, add_if_missing=True).item_assets[ "cloud-mask-raster" ] - raster_bands = cast(List[RasterBand], RasterExtension.ext(item_asset).bands) + raster_bands = cast(list[RasterBand], RasterExtension.ext(item_asset).bands) raster_bands_ext = ClassificationExtension.ext(raster_bands[0]) raster_bands_ext.__repr__() assert raster_bands_ext.classes is not None @@ -313,7 +313,7 @@ def test_item_assets_extension(collection: Collection) -> None: def test_older_extension_version(landsat_item: Item) -> None: - OLD_VERSION = list(set(SUPPORTED_VERSIONS) - set([DEFAULT_VERSION]))[0] + OLD_VERSION = list(set(SUPPORTED_VERSIONS) - {DEFAULT_VERSION})[0] new = SCHEMA_URI_PATTERN.format(version=DEFAULT_VERSION) old = SCHEMA_URI_PATTERN.format(version=OLD_VERSION) diff --git a/tests/extensions/test_custom.py b/tests/extensions/test_custom.py index 777afbb19..36ac2a421 100644 --- a/tests/extensions/test_custom.py +++ b/tests/extensions/test_custom.py @@ -2,7 +2,7 @@ import unittest from datetime import datetime -from typing import Any, Dict, Generic, Optional, Set, TypeVar, Union, cast +from typing import Any, Generic, Optional, TypeVar, Union, cast import pytest @@ -100,19 +100,18 @@ def __init__(self, asset: pystac.Asset) -> None: class CustomExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set( - ["custom", "https://example.com/v1.0/custom-schema.json"] - ) - stac_object_types: Set[pystac.STACObjectType] = set( - [ - pystac.STACObjectType.CATALOG, - pystac.STACObjectType.COLLECTION, - pystac.STACObjectType.ITEM, - ] - ) + prev_extension_ids: set[str] = { + "custom", + "https://example.com/v1.0/custom-schema.json", + } + stac_object_types: set[pystac.STACObjectType] = { + pystac.STACObjectType.CATALOG, + pystac.STACObjectType.COLLECTION, + pystac.STACObjectType.ITEM, + } def migrate( - self, obj: Dict[str, Any], version: STACVersionID, info: STACJSONDescription + self, obj: dict[str, Any], version: STACVersionID, info: STACJSONDescription ) -> None: if version < "1.0.0-rc2" and info.object_type == pystac.STACObjectType.ITEM: if "test:old-prop-name" in obj["properties"]: diff --git a/tests/extensions/test_eo.py b/tests/extensions/test_eo.py index 02f5fb00d..aaa4e302d 100644 --- a/tests/extensions/test_eo.py +++ b/tests/extensions/test_eo.py @@ -1,6 +1,5 @@ import json import unittest -from typing import Dict import pytest @@ -458,7 +457,7 @@ def test_older_extension_version(ext_item: Item) -> None: ({"name": "B4", "common_name": "green"}, 0), ], ) -def test_get_assets(ext_item: pystac.Item, filter: Dict[str, str], count: int) -> None: +def test_get_assets(ext_item: pystac.Item, filter: dict[str, str], count: int) -> None: assets = EOExtension.ext(ext_item).get_assets(**filter) # type:ignore assert len(assets) == count diff --git a/tests/extensions/test_grid.py b/tests/extensions/test_grid.py index 6c22ab1c8..83f6320e8 100644 --- a/tests/extensions/test_grid.py +++ b/tests/extensions/test_grid.py @@ -4,7 +4,7 @@ import unittest from datetime import datetime -from typing import Any, Dict +from typing import Any import pytest @@ -65,7 +65,7 @@ def test_modify(self) -> None: self.item.validate() def test_from_dict(self) -> None: - d: Dict[str, Any] = { + d: dict[str, Any] = { "type": "Feature", "stac_version": "1.0.0", "id": "an/asset", diff --git a/tests/extensions/test_label.py b/tests/extensions/test_label.py index 73f4b2e42..5ab08a8c8 100644 --- a/tests/extensions/test_label.py +++ b/tests/extensions/test_label.py @@ -2,7 +2,7 @@ import os import tempfile import unittest -from typing import List, Union +from typing import Union import pytest @@ -139,7 +139,7 @@ def test_get_sources(self) -> None: cat = TestCases.case_1() items = list(cat.get_items(recursive=True)) - item_ids = set([i.id for i in items]) + item_ids = {i.id for i in items} for li in items: if LabelExtension.has_extension(li): @@ -264,7 +264,7 @@ def test_label_classes(self) -> None: label_item.validate() def test_label_classes_typing(self) -> None: - classes: List[str] = ["foo", "bar"] + classes: list[str] = ["foo", "bar"] LabelClasses.create(classes=classes) @pytest.mark.vcr() @@ -545,7 +545,7 @@ def test_label_type_summary(self) -> None: self.assertListEqual(label_types, label_type_summary_ext) def test_label_task_summary(self) -> None: - label_tasks: List[Union[LabelTask, str]] = [LabelTask.REGRESSION] + label_tasks: list[Union[LabelTask, str]] = [LabelTask.REGRESSION] collection = Collection.from_file(self.EXAMPLE_COLLECTION) label_ext_summaries = LabelExtension.summaries(collection, True) @@ -562,7 +562,7 @@ def test_label_task_summary(self) -> None: self.assertListEqual(label_tasks, label_tasks_summary_ext) def test_label_methods_summary(self) -> None: - label_methods: List[Union[LabelMethod, str]] = [LabelMethod.AUTOMATED] + label_methods: list[Union[LabelMethod, str]] = [LabelMethod.AUTOMATED] collection = Collection.from_file(self.EXAMPLE_COLLECTION) label_ext_summaries = LabelExtension.summaries(collection, True) diff --git a/tests/extensions/test_pointcloud.py b/tests/extensions/test_pointcloud.py index 1bb8aa29a..31be9d4f3 100644 --- a/tests/extensions/test_pointcloud.py +++ b/tests/extensions/test_pointcloud.py @@ -1,6 +1,6 @@ import json import unittest -from typing import Any, Dict +from typing import Any import pytest @@ -176,7 +176,7 @@ def test_density(self) -> None: pc_item.validate() def test_pointcloud_schema(self) -> None: - props: Dict[str, Any] = { + props: dict[str, Any] = { "name": "test", "size": 8, "type": "floating", @@ -208,7 +208,7 @@ def test_pointcloud_schema(self) -> None: getattr(empty_schema, required_prop) def test_pointcloud_statistics(self) -> None: - props: Dict[str, Any] = { + props: dict[str, Any] = { "average": 1, "count": 1, "maximum": 1, diff --git a/tests/extensions/test_projection.py b/tests/extensions/test_projection.py index 32b6dc67c..9f7e52e84 100644 --- a/tests/extensions/test_projection.py +++ b/tests/extensions/test_projection.py @@ -1,7 +1,7 @@ import json import unittest from copy import deepcopy -from typing import Any, Dict +from typing import Any import pytest @@ -291,7 +291,7 @@ def test_geometry(self) -> None: self.assertEqual(asset_prop_geometry["coordinates"][0][0], [0.0, 0.0]) # Set to Asset - asset_value: Dict[str, Any] = {"type": "Point", "coordinates": [1.0, 2.0]} + asset_value: dict[str, Any] = {"type": "Point", "coordinates": [1.0, 2.0]} ProjectionExtension.ext(asset_no_prop).geometry = asset_value self.assertNotEqual( ProjectionExtension.ext(asset_no_prop).geometry, diff --git a/tests/extensions/test_sar.py b/tests/extensions/test_sar.py index e4c251643..51c9b91b6 100644 --- a/tests/extensions/test_sar.py +++ b/tests/extensions/test_sar.py @@ -4,7 +4,6 @@ from datetime import datetime from random import choice from string import ascii_letters -from typing import List import pytest @@ -45,7 +44,7 @@ def test_stac_extensions(self) -> None: def test_required(self) -> None: mode: str = "Nonsense mode" frequency_band: sar.FrequencyBand = sar.FrequencyBand.P - polarizations: List[sar.Polarization] = [ + polarizations: list[sar.Polarization] = [ sar.Polarization.HV, sar.Polarization.VH, ] @@ -72,7 +71,7 @@ def test_required(self) -> None: def test_all(self) -> None: mode: str = "WV" frequency_band: sar.FrequencyBand = sar.FrequencyBand.KA - polarizations: List[sar.Polarization] = [ + polarizations: list[sar.Polarization] = [ sar.Polarization.VV, sar.Polarization.HH, ] diff --git a/tests/extensions/test_sat.py b/tests/extensions/test_sat.py index 44dd8151e..f8421810a 100644 --- a/tests/extensions/test_sat.py +++ b/tests/extensions/test_sat.py @@ -2,7 +2,7 @@ import unittest from datetime import datetime -from typing import Any, Dict +from typing import Any import pytest @@ -138,7 +138,7 @@ def test_modify(self) -> None: def test_from_dict(self) -> None: orbit_state = sat.OrbitState.GEOSTATIONARY relative_orbit = 1001 - d: Dict[str, Any] = { + d: dict[str, Any] = { "type": "Feature", "stac_version": "1.0.0-beta.2", "id": "an/asset", diff --git a/tests/extensions/test_scientific.py b/tests/extensions/test_scientific.py index 4c64d670e..c19a67d4a 100644 --- a/tests/extensions/test_scientific.py +++ b/tests/extensions/test_scientific.py @@ -2,7 +2,7 @@ import unittest from datetime import datetime, timedelta -from typing import List, Optional +from typing import Optional import pytest @@ -245,7 +245,7 @@ def make_collection() -> pystac.Collection: end = start + timedelta(5, 4, 3, 2, 1) bboxes = [[-180.0, -90.0, 180.0, 90.0]] spatial_extent = pystac.SpatialExtent(bboxes) - intervals: List[List[Optional[datetime]]] = [[start, end]] + intervals: list[list[Optional[datetime]]] = [[start, end]] temporal_extent = pystac.TemporalExtent(intervals) extent = pystac.Extent(spatial_extent, temporal_extent) collection = pystac.Collection(asset_id, "desc", extent) diff --git a/tests/extensions/test_version.py b/tests/extensions/test_version.py index 3491070df..1d5641878 100644 --- a/tests/extensions/test_version.py +++ b/tests/extensions/test_version.py @@ -1,8 +1,9 @@ """Tests for pystac.extensions.version.""" import unittest +from collections.abc import Generator from datetime import datetime -from typing import Generator, List, Optional +from typing import Optional import pytest @@ -268,7 +269,7 @@ def make_collection(year: int) -> pystac.Collection: end = datetime(year, 1, 3, 4, 5) bboxes = [[-180.0, -90.0, 180.0, 90.0]] spatial_extent = pystac.SpatialExtent(bboxes) - intervals: List[List[Optional[datetime]]] = [[start, end]] + intervals: list[list[Optional[datetime]]] = [[start, end]] temporal_extent = pystac.TemporalExtent(intervals) extent = pystac.Extent(spatial_extent, temporal_extent) diff --git a/tests/posix_paths/test_posix_paths.py b/tests/posix_paths/test_posix_paths.py index 0f20d58ef..4bc728476 100644 --- a/tests/posix_paths/test_posix_paths.py +++ b/tests/posix_paths/test_posix_paths.py @@ -164,7 +164,7 @@ def test_all_existing_links_converted_to_posix_hrefs() -> None: item = pystac.Item.from_file(href) for link in item.links: check_link(link) - with open(href, "r") as f: + with open(href) as f: item_dict = json.load(f) item2 = pystac.Item.from_dict(item_dict) for link in item2.links: @@ -174,7 +174,7 @@ def test_all_existing_links_converted_to_posix_hrefs() -> None: collection = pystac.Collection.from_file(href) for link in collection.links: check_link(link) - with open(href, "r") as f: + with open(href) as f: collection_dict = json.load(f) collection2 = pystac.Collection.from_dict(collection_dict) for link in collection2.links: @@ -184,7 +184,7 @@ def test_all_existing_links_converted_to_posix_hrefs() -> None: catalog = pystac.Catalog.from_file(href) for link in catalog.links: check_link(link) - with open(href, "r") as f: + with open(href) as f: catalog_dict = json.load(f) catalog2 = pystac.Catalog.from_dict(catalog_dict) for link in catalog2.links: diff --git a/tests/serialization/test_identify.py b/tests/serialization/test_identify.py index e1fe1c18d..3a73b25d1 100644 --- a/tests/serialization/test_identify.py +++ b/tests/serialization/test_identify.py @@ -30,7 +30,7 @@ def test_identify(self, example: ExampleInfo) -> None: str_info = str(actual) assert isinstance(str_info, str) - msg = "Failed {}:".format(path) + msg = f"Failed {path}:" assert actual.object_type == example.object_type, msg version_contained_in_range = actual.version_range.contains(example.stac_version) diff --git a/tests/test_cache.py b/tests/test_cache.py index 4d67a942c..5cf7e1e74 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -1,5 +1,5 @@ import unittest -from typing import Any, Dict +from typing import Any import pystac from pystac.cache import ResolvedObjectCache, ResolvedObjectCollectionCache @@ -9,13 +9,9 @@ def create_catalog(suffix: Any, include_href: bool = True) -> pystac.Catalog: return pystac.Catalog( - id="test {}".format(suffix), - description="test desc {}".format(suffix), - href=( - "http://example.com/catalog_{}.json".format(suffix) - if include_href - else None - ), + id=f"test {suffix}", + description=f"test desc {suffix}", + href=(f"http://example.com/catalog_{suffix}.json" if include_href else None), ) @@ -51,10 +47,10 @@ def test_merge(self) -> None: identical_cat1 = create_catalog(1, include_href=False) identical_cat2 = create_catalog(2) - cached_ids_1: Dict[str, Any] = {cat1.id: cat1} - cached_hrefs_1: Dict[str, Any] = {get_opt(cat2.get_self_href()): cat2} - cached_ids_2: Dict[str, Any] = {cat3.id: cat3, cat1.id: identical_cat1} - cached_hrefs_2: Dict[str, Any] = { + cached_ids_1: dict[str, Any] = {cat1.id: cat1} + cached_hrefs_1: dict[str, Any] = {get_opt(cat2.get_self_href()): cat2} + cached_ids_2: dict[str, Any] = {cat3.id: cat3, cat1.id: identical_cat1} + cached_hrefs_2: dict[str, Any] = { get_opt(cat4.get_self_href()): cat4, get_opt(cat2.get_self_href()): identical_cat2, } @@ -70,12 +66,12 @@ def test_merge(self) -> None: ) self.assertEqual( - set(merged.cached_ids.keys()), set([cat.id for cat in [cat1, cat3]]) + set(merged.cached_ids.keys()), {cat.id for cat in [cat1, cat3]} ) self.assertIs(merged.get_by_id(cat1.id), cat1) self.assertEqual( set(merged.cached_hrefs.keys()), - set([cat.get_self_href() for cat in [cat2, cat4]]), + {cat.get_self_href() for cat in [cat2, cat4]}, ) self.assertIs(merged.get_by_href(get_opt(cat2.get_self_href())), cat2) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index e848e13a8..685b6d99e 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -6,10 +6,11 @@ import tempfile import unittest from collections import defaultdict +from collections.abc import Iterator from copy import deepcopy from datetime import datetime from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional, Tuple, Union, cast +from typing import Any, cast import pytest @@ -83,7 +84,7 @@ def test_create_and_read(self) -> None: cat_dir, catalog_type=CatalogType.ABSOLUTE_PUBLISHED ) - read_catalog = Catalog.from_file("{}/catalog.json".format(cat_dir)) + read_catalog = Catalog.from_file(f"{cat_dir}/catalog.json") collections = catalog.get_children() assert len(list(collections)) == 2 @@ -404,13 +405,13 @@ def test_catalog(cat: Catalog) -> None: actual_catalog_iterations += 1 expected_catalog_iterations += len(list(root.get_children())) - assert set([c.id for c in root.get_children()]) == set( - [c.id for c in children] - ), "Children unequal" + assert {c.id for c in root.get_children()} == { + c.id for c in children + }, "Children unequal" - assert set([c.id for c in root.get_items()]) == set( - [c.id for c in items] - ), "Items unequal" + assert {c.id for c in root.get_items()} == { + c.id for c in items + }, "Items unequal" assert actual_catalog_iterations == expected_catalog_iterations @@ -685,9 +686,9 @@ def test_generate_subcatalogs_works_with_custom_properties(self) -> None: month_cat = catalog.get_child("8", recursive=True) assert month_cat is not None - type_cats = set([cat.id for cat in month_cat.get_children()]) + type_cats = {cat.id for cat in month_cat.get_children()} - assert type_cats == set(["PSScene4Band", "SkySatScene", "PlanetScope"]) + assert type_cats == {"PSScene4Band", "SkySatScene", "PlanetScope"} def test_generate_subcatalogs_does_not_change_item_count(self) -> None: catalog = TestCases.case_7() @@ -707,7 +708,7 @@ def test_generate_subcatalogs_does_not_change_item_count(self) -> None: for child in cat2.get_children(): actual = len(list(child.get_items(recursive=True))) expected = item_counts[child.id] - assert actual == expected, " for child '{}'".format(child.id) + assert actual == expected, f" for child '{child.id}'" def test_generate_subcatalogs_merge_template_elements(self) -> None: catalog = Catalog(id="test", description="Test") @@ -717,7 +718,7 @@ def test_generate_subcatalogs_merge_template_elements(self) -> None: for ni, properties in enumerate(item_properties): catalog.add_item( Item( - id="item{}".format(ni), + id=f"item{ni}", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), @@ -726,10 +727,10 @@ def test_generate_subcatalogs_merge_template_elements(self) -> None: ) result = catalog.generate_subcatalogs("${property1}_${property2}") - actual_subcats = set([cat.id for cat in result]) - expected_subcats = set( - ["{}_{}".format(d["property1"], d["property2"]) for d in item_properties] - ) + actual_subcats = {cat.id for cat in result} + expected_subcats = { + "{}_{}".format(d["property1"], d["property2"]) for d in item_properties + } assert len(result) == len(expected_subcats) assert actual_subcats == expected_subcats @@ -748,7 +749,7 @@ def test_generate_subcatalogs_can_be_applied_multiple_times(self) -> None: for item in catalog.get_items(recursive=True): assert ( item.get_self_href() == expected_hrefs[item.id] - ), " for item '{}'".format(item.id) + ), f" for item '{item.id}'" def test_generate_subcatalogs_works_after_adding_more_items(self) -> None: catalog = Catalog(id="test", description="Test") @@ -794,7 +795,7 @@ def test_generate_subcatalogs_works_for_branched_subcatalogs(self) -> None: for ni, properties in enumerate(item_properties): catalog.add_item( Item( - id="item{}".format(ni), + id=f"item{ni}", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), @@ -804,7 +805,7 @@ def test_generate_subcatalogs_works_for_branched_subcatalogs(self) -> None: result = catalog.generate_subcatalogs("${property1}/${property2}/${property3}") assert len(result) == 9 - actual_subcats = set([cat.id for cat in result]) + actual_subcats = {cat.id for cat in result} expected_subcats = {"A", "B", "1", "2", "i", "j"} assert actual_subcats == expected_subcats @@ -819,7 +820,7 @@ def test_generate_subcatalogs_works_for_subcatalogs_with_same_ids(self) -> None: for ni, properties in enumerate(item_properties): catalog.add_item( Item( - id="item{}".format(ni), + id=f"item{ni}", geometry=ARBITRARY_GEOM, bbox=ARBITRARY_BBOX, datetime=datetime.utcnow(), @@ -837,7 +838,7 @@ def test_generate_subcatalogs_works_for_subcatalogs_with_same_ids(self) -> None: parent_href = item_parent.self_href path_to_parent, _ = os.path.split(parent_href) subcats = [el for el in path_to_parent.split("/") if el] - assert len(subcats) == 2, " for item '{}'".format(item.id) + assert len(subcats) == 2, f" for item '{item.id}'" def test_map_items(self) -> None: def item_mapper(item: pystac.Item) -> pystac.Item: @@ -861,7 +862,7 @@ def item_mapper(item: pystac.Item) -> pystac.Item: assert "ITEM_MAPPER" not in item.properties def test_map_items_multiple(self) -> None: - def item_mapper(item: pystac.Item) -> List[pystac.Item]: + def item_mapper(item: pystac.Item) -> list[pystac.Item]: item2 = item.clone() item2.id = item2.id + "_2" item.properties["ITEM_MAPPER_1"] = "YEP" @@ -927,11 +928,11 @@ def modify_item_title(item: pystac.Item) -> pystac.Item: item.properties["title"] = "Some title" return item - def create_label_item(item: pystac.Item) -> List[pystac.Item]: + def create_label_item(item: pystac.Item) -> list[pystac.Item]: # Assumes the GEOJSON labels are in the # same location as the image img_href = item.assets["ortho"].href - label_href = "{}.geojson".format(os.path.splitext(img_href)[0]) + label_href = f"{os.path.splitext(img_href)[0]}.geojson" label_item = Item( id="Labels", geometry=item.geometry, @@ -992,15 +993,15 @@ def asset_mapper(key: str, asset: pystac.Asset) -> pystac.Asset: assert found def test_map_assets_tup(self) -> None: - changed_assets: List[str] = [] + changed_assets: list[str] = [] def asset_mapper( key: str, asset: pystac.Asset - ) -> Union[pystac.Asset, Tuple[str, pystac.Asset]]: + ) -> pystac.Asset | tuple[str, pystac.Asset]: if asset.media_type and "geotiff" in asset.media_type: asset.title = "NEW TITLE" changed_assets.append(key) - return ("{}-modified".format(key), asset) + return (f"{key}-modified", asset) else: return asset @@ -1033,14 +1034,14 @@ def test_map_assets_multi(self) -> None: def asset_mapper( key: str, asset: pystac.Asset - ) -> Union[pystac.Asset, Dict[str, pystac.Asset]]: + ) -> pystac.Asset | dict[str, pystac.Asset]: if asset.media_type and "geotiff" in asset.media_type: changed_assets.append(key) mod1 = asset.clone() mod1.title = "NEW TITLE 1" mod2 = asset.clone() mod2.title = "NEW TITLE 2" - return {"{}-mod-1".format(key): mod1, "{}-mod-2".format(key): mod2} + return {f"{key}-mod-1": mod1, f"{key}-mod-2": mod2} else: return asset @@ -1275,8 +1276,8 @@ def test_set_hrefs_manually(self) -> None: for item in items: assert item.datetime is not None end = posixpath.join( - "{}-{}".format(item.datetime.year, item.datetime.month), - "{}.json".format(item.id), + f"{item.datetime.year}-{item.datetime.month}", + f"{item.id}.json", ) self_href = item.get_self_href() assert self_href is not None @@ -1288,7 +1289,7 @@ def test_set_hrefs_manually(self) -> None: def test_collections_cache_correctly(self, cat: Catalog) -> None: mock_io = MockStacIO() cat._stac_io = mock_io - expected_collection_reads = set([]) + expected_collection_reads = set() for root, _, items in cat.walk(): if isinstance(root, Collection) and root != cat: expected_collection_reads.add(root.get_self_href()) @@ -1296,7 +1297,7 @@ def test_collections_cache_correctly(self, cat: Catalog) -> None: # Iterate over items to make sure they are read assert list(items) is not None - call_uris: List[Any] = [ + call_uris: list[Any] = [ call[0][0] for call in mock_io.mock.read_text.call_args_list if call[0][0] in expected_collection_reads @@ -1495,7 +1496,7 @@ def test_full_copy_2(self) -> None: for key in ["ortho", "dsm"]: image_item.add_asset( key, - Asset(href="some/{}.tif".format(key), media_type=MediaType.GEOTIFF), + Asset(href=f"some/{key}.tif", media_type=MediaType.GEOTIFF), ) label_item = Item( @@ -1617,9 +1618,9 @@ class CustomCatalog(Catalog): @classmethod def from_dict( cls, - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional[Catalog] = None, + d: dict[str, Any], + href: str | None = None, + root: Catalog | None = None, migrate: bool = False, preserve_dict: bool = True, ) -> CustomCatalog: diff --git a/tests/test_collection.py b/tests/test_collection.py index cb193cfdc..ea51fbd67 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -4,9 +4,10 @@ import os import tempfile import unittest +from collections.abc import Iterator from copy import deepcopy from datetime import datetime -from typing import Any, Dict, Iterator, Optional +from typing import Any import pytest from dateutil import tz @@ -258,7 +259,7 @@ def test_get_assets(self) -> None: def test_removing_optional_attributes(self) -> None: path = TestCases.get_path("data-files/collections/with-assets.json") - with open(path, "r") as file: + with open(path) as file: data = json.load(file) data["title"] = "dummy title" data["stac_extensions"] = ["dummy extension"] @@ -559,9 +560,9 @@ class CustomCollection(Collection): @classmethod def from_dict( cls, - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional[Catalog] = None, + d: dict[str, Any], + href: str | None = None, + root: Catalog | None = None, migrate: bool = False, preserve_dict: bool = True, ) -> CustomCollection: diff --git a/tests/test_common_metadata.py b/tests/test_common_metadata.py index 337926c7d..f7113481c 100644 --- a/tests/test_common_metadata.py +++ b/tests/test_common_metadata.py @@ -1,6 +1,6 @@ import unittest from datetime import datetime -from typing import Any, Dict, List +from typing import Any from pystac import CommonMetadata, Item, Provider, ProviderRole, utils from tests.utils import TestCases @@ -18,7 +18,7 @@ def setUp(self) -> None: ) self.ITEM_2 = Item.from_file(self.URI_2) - self.EXAMPLE_CM_DICT: Dict[str, Any] = { + self.EXAMPLE_CM_DICT: dict[str, Any] = { "start_datetime": "2020-05-21T16:42:24.896Z", "platform": "example platform", "providers": [ @@ -106,7 +106,7 @@ def test_common_metadata_updated(self) -> None: def test_common_metadata_providers(self) -> None: x = self.ITEM_2.clone() - providers_dict_list: List[Dict[str, Any]] = [ + providers_dict_list: list[dict[str, Any]] = [ { "name": "CoolSat", "roles": ["producer", "licensor"], @@ -115,7 +115,7 @@ def test_common_metadata_providers(self) -> None: ] providers_object_list = [Provider.from_dict(d) for d in providers_dict_list] - example_providers_dict_list: List[Dict[str, Any]] = [ + example_providers_dict_list: list[dict[str, Any]] = [ { "name": "ExampleProvider_1", "roles": ["example_role_1", "example_role_2"], diff --git a/tests/test_item.py b/tests/test_item.py index 88d00fc16..ee958128b 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -6,7 +6,7 @@ import unittest from copy import deepcopy from pathlib import Path -from typing import Any, Dict, Optional +from typing import Any import dateutil.relativedelta import pytest @@ -26,10 +26,10 @@ class ItemTest(unittest.TestCase): - def get_example_item_dict(self) -> Dict[str, Any]: + def get_example_item_dict(self) -> dict[str, Any]: m = TestCases.get_path("data-files/item/sample-item.json") with open(m) as f: - item_dict: Dict[str, Any] = json.load(f) + item_dict: dict[str, Any] = json.load(f) return item_dict def test_to_from_dict(self) -> None: @@ -435,9 +435,9 @@ class CustomItem(Item): @classmethod def from_dict( cls, - d: Dict[str, Any], - href: Optional[str] = None, - root: Optional[Catalog] = None, + d: dict[str, Any], + href: str | None = None, + root: Catalog | None = None, migrate: bool = False, preserve_dict: bool = True, ) -> CustomItem: diff --git a/tests/test_layout.py b/tests/test_layout.py index 96585aadf..fee31c042 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -35,7 +35,7 @@ def test_templates_item_datetime(self) -> None: parts = template.get_template_values(item) - self.assertEqual(set(parts), set(["year", "month", "day", "date"])) + self.assertEqual(set(parts), {"year", "month", "day", "date"}) self.assertEqual(parts["year"], year) self.assertEqual(parts["month"], month) @@ -67,7 +67,7 @@ def test_templates_item_start_datetime(self) -> None: parts = template.get_template_values(item) - self.assertEqual(set(parts), set(["year", "month", "day", "date"])) + self.assertEqual(set(parts), {"year", "month", "day", "date"}) self.assertEqual(parts["year"], year) self.assertEqual(parts["month"], month) @@ -91,7 +91,7 @@ def test_templates_item_collection(self) -> None: self.assertEqual(parts["collection"], item.collection_id) path = template.substitute(item) - self.assertEqual(path, "{}/item.json".format(item.collection_id)) + self.assertEqual(path, f"{item.collection_id}/item.json") def test_throws_for_no_collection(self) -> None: template = LayoutTemplate("${collection}/item.json") @@ -121,7 +121,7 @@ def test_nested_properties(self) -> None: parts = template.get_template_values(item) - self.assertEqual(set(parts), set(["test.prop", "ext:extra.test.prop"])) + self.assertEqual(set(parts), {"test.prop", "ext:extra.test.prop"}) self.assertEqual(parts["test.prop"], 4326) self.assertEqual(parts["ext:extra.test.prop"], 3857) @@ -186,7 +186,7 @@ def test_docstring_examples(self) -> None: class CustomLayoutStrategyTest(unittest.TestCase): def get_custom_catalog_func(self) -> Callable[[pystac.Catalog, str, bool], str]: def fn(cat: pystac.Catalog, parent_dir: str, is_root: bool) -> str: - return posixpath.join(parent_dir, "cat/{}/{}.json".format(is_root, cat.id)) + return posixpath.join(parent_dir, f"cat/{is_root}/{cat.id}.json") return fn @@ -194,13 +194,13 @@ def get_custom_collection_func( self, ) -> Callable[[pystac.Collection, str, bool], str]: def fn(col: pystac.Collection, parent_dir: str, is_root: bool) -> str: - return posixpath.join(parent_dir, "col/{}/{}.json".format(is_root, col.id)) + return posixpath.join(parent_dir, f"col/{is_root}/{col.id}.json") return fn def get_custom_item_func(self) -> Callable[[pystac.Item, str], str]: def fn(item: pystac.Item, parent_dir: str) -> str: - return posixpath.join(parent_dir, "item/{}.json".format(item.id)) + return posixpath.join(parent_dir, f"item/{item.id}.json") return fn @@ -228,9 +228,7 @@ def test_produces_layout_for_collection(self) -> None: ) collection = TestCases.case_8() href = strategy.get_href(collection, parent_dir="http://example.com") - self.assertEqual( - href, "http://example.com/col/False/{}.json".format(collection.id) - ) + self.assertEqual(href, f"http://example.com/col/False/{collection.id}.json") def test_produces_fallback_layout_for_collection(self) -> None: fallback = BestPracticesLayoutStrategy() @@ -249,7 +247,7 @@ def test_produces_layout_for_item(self) -> None: collection = TestCases.case_8() item = next(collection.get_items(recursive=True)) href = strategy.get_href(item, parent_dir="http://example.com") - self.assertEqual(href, "http://example.com/item/{}.json".format(item.id)) + self.assertEqual(href, f"http://example.com/item/{item.id}.json") def test_produces_fallback_layout_for_item(self) -> None: fallback = BestPracticesLayoutStrategy() @@ -344,7 +342,7 @@ def test_produces_layout_for_item(self) -> None: href = strategy.get_href(item, parent_dir="http://example.com") self.assertEqual( href, - "http://example.com/item/{}/{}.json".format(item.collection_id, item.id), + f"http://example.com/item/{item.collection_id}/{item.id}.json", ) def test_produces_layout_for_item_without_filename(self) -> None: @@ -355,7 +353,7 @@ def test_produces_layout_for_item_without_filename(self) -> None: href = strategy.get_href(item, parent_dir="http://example.com") self.assertEqual( href, - "http://example.com/item/{}/{}.json".format(item.collection_id, item.id), + f"http://example.com/item/{item.collection_id}/{item.id}.json", ) def test_produces_fallback_layout_for_item(self) -> None: @@ -398,15 +396,13 @@ def test_produces_layout_for_root_collection(self) -> None: def test_produces_layout_for_child_collection(self) -> None: collection = TestCases.case_8() href = self.strategy.get_href(collection, parent_dir="http://example.com") - self.assertEqual( - href, "http://example.com/{}/collection.json".format(collection.id) - ) + self.assertEqual(href, f"http://example.com/{collection.id}/collection.json") def test_produces_layout_for_item(self) -> None: collection = TestCases.case_8() item = next(collection.get_items(recursive=True)) href = self.strategy.get_href(item, parent_dir="http://example.com") - expected = "http://example.com/{}/{}.json".format(item.id, item.id) + expected = f"http://example.com/{item.id}/{item.id}.json" self.assertEqual(href, expected) diff --git a/tests/test_link.py b/tests/test_link.py index e90267234..c7ecdb6cc 100644 --- a/tests/test_link.py +++ b/tests/test_link.py @@ -4,7 +4,7 @@ from datetime import datetime from pathlib import Path from tempfile import TemporaryDirectory -from typing import Any, Dict, List +from typing import Any import pytest @@ -227,7 +227,7 @@ def setUp(self) -> None: ) def test_from_dict_round_trip(self) -> None: - test_cases: List[Dict[str, Any]] = [ + test_cases: list[dict[str, Any]] = [ {"rel": "", "href": ""}, # Not valid, but works. {"rel": "r", "href": "t"}, {"rel": "r", "href": "/t"}, @@ -241,7 +241,7 @@ def test_from_dict_round_trip(self) -> None: self.assertEqual(pystac.Link.from_dict(d).to_dict(), d2) def test_from_dict_failures(self) -> None: - dicts: List[Dict[str, Any]] = [{}, {"href": "t"}, {"rel": "r"}] + dicts: list[dict[str, Any]] = [{}, {"href": "t"}, {"rel": "r"}] for d in dicts: with self.assertRaises(KeyError): pystac.Link.from_dict(d) diff --git a/tests/test_writing.py b/tests/test_writing.py index 672a379bc..90a7b4f72 100644 --- a/tests/test_writing.py +++ b/tests/test_writing.py @@ -1,5 +1,5 @@ import tempfile -from typing import Any, List +from typing import Any import pytest @@ -34,7 +34,7 @@ def validate_catalog(self, catalog: Catalog) -> int: return validated_count - def validate_file(self, path: str, object_type: str) -> List[Any]: + def validate_file(self, path: str, object_type: str) -> list[Any]: d = pystac.StacIO.default().read_json(path) return validate_dict(d, pystac.STACObjectType(object_type)) @@ -55,7 +55,7 @@ def validate_item_link_type( else: assert is_absolute_href(link.href) - rels = set([link["rel"] for link in item_dict["links"]]) + rels = {link["rel"] for link in item_dict["links"]} assert ("self" in rels) == should_include_self def validate_catalog_link_type( @@ -65,7 +65,7 @@ def validate_catalog_link_type( cat = pystac.read_file(href) assert isinstance(cat, Catalog) - rels = set([link["rel"] for link in cat_dict["links"]]) + rels = {link["rel"] for link in cat_dict["links"]} assert ("self" in rels) == should_include_self for child_link in cat.get_child_links(): diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 43bad849a..0f702428e 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -8,7 +8,7 @@ import unittest from copy import deepcopy from datetime import datetime -from typing import Any, Dict, Type +from typing import Any from dateutil.parser import parse @@ -24,10 +24,10 @@ def assert_to_from_dict( test_class: unittest.TestCase, - stac_object_class: Type[pystac.STACObject], - d: Dict[str, Any], + stac_object_class: type[pystac.STACObject], + d: dict[str, Any], ) -> None: - def _parse_times(a_dict: Dict[str, Any]) -> None: + def _parse_times(a_dict: dict[str, Any]) -> None: for k, v in a_dict.items(): if isinstance(v, dict): _parse_times(v) diff --git a/tests/utils/stac_io_mock.py b/tests/utils/stac_io_mock.py index c7ef93e70..3f81f72d1 100644 --- a/tests/utils/stac_io_mock.py +++ b/tests/utils/stac_io_mock.py @@ -41,7 +41,7 @@ def write_text( self.wrapped_stac_io.write_text(dest, txt) -class MockDefaultStacIO(object): +class MockDefaultStacIO: """Context manager for mocking StacIO.""" def __enter__(self) -> MockStacIO: diff --git a/tests/utils/test_cases.py b/tests/utils/test_cases.py index 3b8c31035..91475ad36 100644 --- a/tests/utils/test_cases.py +++ b/tests/utils/test_cases.py @@ -1,7 +1,7 @@ import csv import os from datetime import datetime -from typing import Any, Dict, List +from typing import Any import pystac from pystac import ( @@ -49,7 +49,7 @@ }, } -ARBITRARY_GEOM: Dict[str, Any] = { +ARBITRARY_GEOM: dict[str, Any] = { "type": "Polygon", "coordinates": [ [ @@ -62,7 +62,7 @@ ], } -ARBITRARY_BBOX: List[float] = [ +ARBITRARY_BBOX: list[float] = [ ARBITRARY_GEOM["coordinates"][0][0][0], ARBITRARY_GEOM["coordinates"][0][0][1], ARBITRARY_GEOM["coordinates"][0][1][0], @@ -81,7 +81,7 @@ def __init__( path: str, object_type: pystac.STACObjectType, stac_version: str, - extensions: List[str], + extensions: list[str], valid: bool, ) -> None: self.path = path @@ -99,8 +99,8 @@ def get_path(rel_path: str) -> str: return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", rel_path)) @staticmethod - def get_examples_info() -> List[ExampleInfo]: - examples: List[ExampleInfo] = [] + def get_examples_info() -> list[ExampleInfo]: + examples: list[ExampleInfo] = [] info_path = TestCases.get_path("data-files/examples/example-info.csv") with open(TestCases.get_path("data-files/examples/example-info.csv")) as f: @@ -108,7 +108,7 @@ def get_examples_info() -> List[ExampleInfo]: path = os.path.abspath(os.path.join(os.path.dirname(info_path), row[0])) object_type = row[1] stac_version = row[2] - extensions: List[str] = [] + extensions: list[str] = [] if row[3]: extensions = row[3].split("|") @@ -130,7 +130,7 @@ def get_examples_info() -> List[ExampleInfo]: return examples @staticmethod - def all_test_catalogs() -> List[Catalog]: + def all_test_catalogs() -> list[Catalog]: return [ TestCases.case_1(), TestCases.case_2(), diff --git a/tests/validation/test_validate.py b/tests/validation/test_validate.py index 716a468e9..320d481d0 100644 --- a/tests/validation/test_validate.py +++ b/tests/validation/test_validate.py @@ -3,7 +3,7 @@ import shutil import tempfile from datetime import datetime -from typing import Any, Dict +from typing import Any import jsonschema import pytest @@ -156,7 +156,7 @@ def test_validates_geojson_with_tuple_coordinates(self) -> None: which can be produced by shapely, then the geometry still passes validation. """ - geom: Dict[str, Any] = { + geom: dict[str, Any] = { "type": "Polygon", # Last , is required to ensure tuple creation. "coordinates": (