diff --git a/CHANGELOG.md b/CHANGELOG.md index 254536be..989bd3e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Changed - Write STAC v1.1.0 ([#1427](https://github.com/stac-utils/pystac/pull/1427)) +- Use `__slots__` for objects we expect to exist in large numbers ([#1434](https://github.com/stac-utils/pystac/pull/1434)) ## [v1.11.0] - 2024-09-26 diff --git a/pystac/asset.py b/pystac/asset.py index e42b8237..28120326 100644 --- a/pystac/asset.py +++ b/pystac/asset.py @@ -37,6 +37,16 @@ class Asset: object JSON. """ + __slots__: tuple[str, ...] = ( + "href", + "title", + "description", + "media_type", + "roles", + "owner", + "extra_fields", + ) + href: str """Link to the asset object. Relative and absolute links are both allowed.""" diff --git a/pystac/catalog.py b/pystac/catalog.py index 88228878..e08e0f54 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -133,6 +133,16 @@ class Catalog(STACObject): :class:`~pystac.layout.BestPracticesLayoutStrategy`. """ + __slots__: tuple[str, ...] = STACObject.__slots__ + ( + "catalog_type", + "description", + "extra_fields", + "title", + "_resolved_objects", + "_stac_io", + "strategy", + ) + catalog_type: CatalogType """The catalog type. Defaults to :attr:`CatalogType.ABSOLUTE_PUBLISHED`.""" @@ -159,7 +169,7 @@ class Catalog(STACObject): STAC_OBJECT_TYPE = pystac.STACObjectType.CATALOG - _stac_io: pystac.StacIO | None = None + _stac_io: pystac.StacIO | 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 @@ -207,6 +217,8 @@ def __init__( self._resolved_objects.cache(self) + self._stac_io = None + def __repr__(self) -> str: return f"" diff --git a/pystac/collection.py b/pystac/collection.py index f8e588bf..e394100d 100644 --- a/pystac/collection.py +++ b/pystac/collection.py @@ -479,6 +479,15 @@ class Collection(Catalog, Assets): :class:`~pystac.layout.BestPracticesLayoutStrategy`. """ + __slots__: tuple[str, ...] = Catalog.__slots__ + ( + "extent", + "license", + "keywords", + "providers", + "summaries", + "assets", + ) + description: str """Detailed multi-line description to fully explain the collection.""" diff --git a/pystac/item.py b/pystac/item.py index 054f124d..347339ce 100644 --- a/pystac/item.py +++ b/pystac/item.py @@ -67,6 +67,18 @@ class Item(STACObject, Assets): :attr:`~pystac.Asset.owner` attribute set to the created Item. """ + __slots__: tuple[str, ...] = STACObject.__slots__ + ( + "assets", + "bbox", + "collection", + "collection_id", + "datetime", + "extra_fields", + "geometry", + "links", + "properties", + ) + assets: dict[str, Asset] """Dictionary of :class:`~pystac.Asset` objects, each with a unique key.""" @@ -157,7 +169,8 @@ def __init__( if href is not None: self.set_self_href(href) - self.collection_id: str | None = None + self.collection_id = None + self.collection = None if collection is None: self.collection = None else: @@ -175,30 +188,34 @@ def __repr__(self) -> str: return f"" def __getstate__(self) -> dict[str, Any]: - """Ensure that pystac does not encode too much information when pickling""" - d = self.__dict__.copy() + """Ensure that pystac does not encode too much information when pickling.""" + state = {slot: getattr(self, slot) for slot in self.__slots__} - d["links"] = [ + state["links"] = [ ( link.to_dict(transform_href=False) if link.get_href(transform_href=False) else link ) - for link in d["links"] + for link in state["links"] ] - return d + return state def __setstate__(self, state: dict[str, Any]) -> None: """Ensure that pystac knows how to decode the pickled object""" - d = state.copy() - - d["links"] = [ - Link.from_dict(link).set_owner(self) if isinstance(link, dict) else link - for link in d["links"] - ] + for slot in self.__slots__: + if slot == "links": + value = [ + Link.from_dict(link).set_owner(self) + if isinstance(link, dict) + else link + for link in state["links"] + ] + else: + value = state.get(slot) # type: ignore - self.__dict__ = d + setattr(self, slot, value) def set_self_href(self, href: str | None) -> None: """Sets the absolute HREF that is represented by the ``rel == 'self'`` diff --git a/pystac/link.py b/pystac/link.py index f8806109..15b814a3 100644 --- a/pystac/link.py +++ b/pystac/link.py @@ -70,6 +70,16 @@ class Link(PathLike): object JSON. """ + __slots__: tuple[str, ...] = ( + "rel", + "media_type", + "extra_fields", + "owner", + "_target_href", + "_target_object", + "_title", + ) + 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.""" diff --git a/pystac/stac_object.py b/pystac/stac_object.py index a3a4959d..4215bf9a 100644 --- a/pystac/stac_object.py +++ b/pystac/stac_object.py @@ -41,6 +41,13 @@ class STACObject(ABC): functionality through the implementing classes. """ + __slots__: tuple[str, ...] = ( + "id", + "links", + "stac_extensions", + "_allow_parent_to_override_href", + ) + id: str """The ID of the STAC Object.""" @@ -53,12 +60,13 @@ class STACObject(ABC): STAC_OBJECT_TYPE: STACObjectType - _allow_parent_to_override_href: bool = True + _allow_parent_to_override_href: bool """Private attribute for whether parent objects should override on normalization""" def __init__(self, stac_extensions: list[str]) -> None: self.links = [] self.stac_extensions = stac_extensions + self._allow_parent_to_override_href = True def validate( self,