Skip to content

Commit

Permalink
Merge pull request #451 from duckontheweb/fix/gh-410-stac-object-inhe…
Browse files Browse the repository at this point in the history
…ritance

Fix STACObject inheritance
  • Loading branch information
lossyrob authored Jun 17, 2021
2 parents 012833a + a6aa201 commit 2739661
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 82 deletions.
34 changes: 23 additions & 11 deletions pystac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,34 +73,40 @@
)


def read_file(href: str) -> STACObject:
def read_file(href: str, stac_io: Optional[StacIO] = None) -> STACObject:
"""Reads a STAC object from a file.
This method will return either a Catalog, a Collection, or an Item based on what the
file contains.
This method will return either a Catalog, a Collection, or an Item based on what
the file contains.
This is a convenience method for :meth:`STACObject.from_file <pystac.STACObject.from_file>`
This is a convenience method for :meth:`StacIO.read_stac_object
<pystac.StacIO.read_stac_object>`
Args:
href : The HREF to read the object from.
stac_io: Optional :class:`~StacIO` instance to use for I/O operations. If not
provided, will use :meth:`StacIO.default` to create an instance.
Returns:
The specific STACObject implementation class that is represented
by the JSON read from the file located at HREF.
Raises:
STACTypeError : If the file at ``href`` does not represent a valid
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection` is not
a :class:`~pystac.STACObject` and must be read using
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection`
is not a :class:`~pystac.STACObject` and must be read using
:meth:`ItemCollection.from_file <pystac.ItemCollection.from_file>`
"""
return STACObject.from_file(href)
if stac_io is None:
stac_io = StacIO.default()
return stac_io.read_stac_object(href)


def write_file(
obj: STACObject,
include_self_link: bool = True,
dest_href: Optional[str] = None,
stac_io: Optional[StacIO] = None,
) -> None:
"""Writes a STACObject to a file.
Expand All @@ -120,8 +126,14 @@ def write_file(
Otherwise, leave out the self link.
dest_href : Optional HREF to save the file to. If ``None``, the object will be
saved to the object's ``"self"`` href.
stac_io: Optional :class:`~StacIO` instance to use for I/O operations. If not
provided, will use :meth:`StacIO.default` to create an instance.
"""
obj.save_object(include_self_link=include_self_link, dest_href=dest_href)
if stac_io is None:
stac_io = StacIO.default()
obj.save_object(
include_self_link=include_self_link, dest_href=dest_href, stac_io=stac_io
)


def read_dict(
Expand All @@ -137,7 +149,7 @@ def read_dict(
or :class`~Item` based on the contents of the dict.
This is a convenience method for either
:meth:`stac_io.stac_object_from_dict <stac_io.stac_object_from_dict>`.
:meth:`StacIO.stac_object_from_dict <pystac.StacIO.stac_object_from_dict>`.
Args:
d : The dict to parse.
Expand All @@ -151,8 +163,8 @@ def read_dict(
Raises:
STACTypeError : If the ``d`` dictionary does not represent a valid
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection` is not
a :class:`~pystac.STACObject` and must be read using
:class:`~pystac.STACObject`. Note that an :class:`~pystac.ItemCollection`
is not a :class:`~pystac.STACObject` and must be read using
:meth:`ItemCollection.from_dict <pystac.ItemCollection.from_dict>`
"""
if stac_io is None:
Expand Down
28 changes: 22 additions & 6 deletions pystac/catalog.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from copy import deepcopy
from enum import Enum
from pystac.errors import STACTypeError
from typing import (
Any,
Callable,
Expand All @@ -15,14 +16,19 @@
)

import pystac
from pystac.stac_object import STACObject
from pystac.stac_object import STACObject, STACObjectType
from pystac.layout import (
BestPracticesLayoutStrategy,
HrefLayoutStrategy,
LayoutTemplate,
)
from pystac.link import Link
from pystac.cache import ResolvedObjectCache
from pystac.serialization import (
identify_stac_object_type,
identify_stac_object,
migrate_to_latest,
)
from pystac.utils import is_absolute_href, make_absolute_href

if TYPE_CHECKING:
Expand Down Expand Up @@ -902,10 +908,11 @@ def from_dict(
migrate: bool = False,
) -> "Catalog":
if migrate:
result = pystac.read_dict(d, href=href, root=root)
if not isinstance(result, Catalog):
raise pystac.STACError(f"{result} is not a Catalog")
return result
info = identify_stac_object(d)
d = migrate_to_latest(d, info)

if not cls.matches_object_type(d):
raise STACTypeError(f"{d} does not represent a {cls.__name__} instance")

catalog_type = CatalogType.determine_type(d)

Expand All @@ -919,7 +926,7 @@ def from_dict(

d.pop("stac_version")

cat = Catalog(
cat = cls(
id=id,
description=description,
title=title,
Expand All @@ -946,7 +953,16 @@ def full_copy(

@classmethod
def from_file(cls, href: str, stac_io: Optional[pystac.StacIO] = None) -> "Catalog":
if stac_io is None:
stac_io = pystac.StacIO.default()

result = super().from_file(href, stac_io)
if not isinstance(result, Catalog):
raise pystac.STACTypeError(f"{result} is not a {Catalog}.")
result._stac_io = stac_io

return result

@classmethod
def matches_object_type(cls, d: Dict[str, Any]) -> bool:
return identify_stac_object_type(d) == STACObjectType.CATALOG
21 changes: 16 additions & 5 deletions pystac/collection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from copy import copy, deepcopy
from datetime import datetime as Datetime
from pystac.errors import STACTypeError
from typing import (
Any,
Dict,
Expand All @@ -23,6 +24,11 @@
from pystac.layout import HrefLayoutStrategy
from pystac.link import Link
from pystac.utils import datetime_to_str
from pystac.serialization import (
identify_stac_object_type,
identify_stac_object,
migrate_to_latest,
)
from pystac.summaries import Summaries

if TYPE_CHECKING:
Expand Down Expand Up @@ -583,10 +589,11 @@ def from_dict(
migrate: bool = False,
) -> "Collection":
if migrate:
result = pystac.read_dict(d, href=href, root=root)
if not isinstance(result, Collection):
raise pystac.STACError(f"{result} is not a Catalog")
return result
info = identify_stac_object(d)
d = migrate_to_latest(d, info)

if not cls.matches_object_type(d):
raise STACTypeError(f"{d} does not represent a {cls.__name__} instance")

catalog_type = CatalogType.determine_type(d)

Expand All @@ -610,7 +617,7 @@ def from_dict(

d.pop("stac_version")

collection = Collection(
collection = cls(
id=id,
description=description,
extent=extent,
Expand Down Expand Up @@ -676,3 +683,7 @@ def from_file(
if not isinstance(result, Collection):
raise pystac.STACTypeError(f"{result} is not a {Collection}.")
return result

@classmethod
def matches_object_type(cls, d: Dict[str, Any]) -> bool:
return identify_stac_object_type(d) == STACObjectType.COLLECTION
20 changes: 16 additions & 4 deletions pystac/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
from pystac import STACError, STACObjectType
from pystac.asset import Asset
from pystac.link import Link
from pystac.serialization import (
identify_stac_object_type,
identify_stac_object,
migrate_to_latest,
)
from pystac.stac_object import STACObject
from pystac.utils import (
is_absolute_href,
Expand Down Expand Up @@ -912,10 +917,13 @@ def from_dict(
migrate: bool = False,
) -> "Item":
if migrate:
result = pystac.read_dict(d, href=href, root=root)
if not isinstance(result, Item):
raise pystac.STACError(f"{result} is not a Catalog")
return result
info = identify_stac_object(d)
d = migrate_to_latest(d, info)

if not cls.matches_object_type(d):
raise pystac.STACTypeError(
f"{d} does not represent a {cls.__name__} instance"
)

d = deepcopy(d)
id = d.pop("id")
Expand Down Expand Up @@ -980,3 +988,7 @@ def from_file(cls, href: str, stac_io: Optional[pystac.StacIO] = None) -> "Item"
if not isinstance(result, Item):
raise pystac.STACTypeError(f"{result} is not a {Item}.")
return result

@classmethod
def matches_object_type(cls, d: Dict[str, Any]) -> bool:
return identify_stac_object_type(d) == STACObjectType.ITEM
46 changes: 0 additions & 46 deletions pystac/serialization/__init__.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,8 @@
# flake8: noqa
from typing import Any, Dict, Optional, TYPE_CHECKING

import pystac
from pystac.serialization.identify import (
STACVersionRange,
identify_stac_object,
identify_stac_object_type,
)
from pystac.serialization.common_properties import merge_common_properties
from pystac.serialization.migrate import migrate_to_latest

if TYPE_CHECKING:
from pystac.stac_object import STACObject
from pystac.catalog import Catalog


def stac_object_from_dict(
d: Dict[str, Any], href: Optional[str] = None, root: Optional["Catalog"] = None
) -> "STACObject":
"""Determines how to deserialize a dictionary into a STAC object.
Args:
d : The dict to parse.
href : Optional href that is the file location of the object being
parsed.
root : Optional root of the catalog for this object.
If provided, the root's resolved object cache can be used to search for
previously resolved instances of the STAC object.
Note: This is used internally in StacIO instances to deserialize STAC Objects.
"""
if identify_stac_object_type(d) == pystac.STACObjectType.ITEM:
collection_cache = None
if root is not None:
collection_cache = root._resolved_objects.as_collection_cache()

# Merge common properties in case this is an older STAC object.
merge_common_properties(d, json_href=href, collection_cache=collection_cache)

info = identify_stac_object(d)

d = migrate_to_latest(d, info)

if info.object_type == pystac.STACObjectType.CATALOG:
return pystac.Catalog.from_dict(d, href=href, root=root, migrate=False)

if info.object_type == pystac.STACObjectType.COLLECTION:
return pystac.Collection.from_dict(d, href=href, root=root, migrate=False)

if info.object_type == pystac.STACObjectType.ITEM:
return pystac.Item.from_dict(d, href=href, root=root, migrate=False)

raise pystac.STACTypeError(f"Unknown STAC object type {info.object_type}")
61 changes: 54 additions & 7 deletions pystac/stac_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@

import pystac
from pystac.utils import safe_urlparse
import pystac.serialization
from pystac.serialization import (
merge_common_properties,
identify_stac_object_type,
identify_stac_object,
migrate_to_latest,
)

# Use orjson if available
try:
Expand Down Expand Up @@ -95,12 +100,31 @@ def stac_object_from_dict(
href: Optional[str] = None,
root: Optional["Catalog_Type"] = None,
) -> "STACObject_Type":
result = pystac.serialization.stac_object_from_dict(d, href, root)
if isinstance(result, pystac.Catalog):
# Set the stac_io instance for usage by io operations
# where this catalog is the root.
if identify_stac_object_type(d) == pystac.STACObjectType.ITEM:
collection_cache = None
if root is not None:
collection_cache = root._resolved_objects.as_collection_cache()

# Merge common properties in case this is an older STAC object.
merge_common_properties(
d, json_href=href, collection_cache=collection_cache
)

info = identify_stac_object(d)
d = migrate_to_latest(d, info)

if info.object_type == pystac.STACObjectType.CATALOG:
result = pystac.Catalog.from_dict(d, href=href, root=root, migrate=False)
result._stac_io = self
return result
return result

if info.object_type == pystac.STACObjectType.COLLECTION:
return pystac.Collection.from_dict(d, href=href, root=root, migrate=False)

if info.object_type == pystac.STACObjectType.ITEM:
return pystac.Item.from_dict(d, href=href, root=root, migrate=False)

raise ValueError(f"Unknown STAC object type {info.object_type}")

def read_json(
self, source: Union[str, "Link_Type"], *args: Any, **kwargs: Any
Expand Down Expand Up @@ -302,7 +326,30 @@ def stac_object_from_dict(
root: Optional["Catalog_Type"] = None,
) -> "STACObject_Type":
STAC_IO.issue_deprecation_warning()
return pystac.serialization.stac_object_from_dict(d, href, root)
if identify_stac_object_type(d) == pystac.STACObjectType.ITEM:
collection_cache = None
if root is not None:
collection_cache = root._resolved_objects.as_collection_cache()

# Merge common properties in case this is an older STAC object.
merge_common_properties(
d, json_href=href, collection_cache=collection_cache
)

info = identify_stac_object(d)

d = migrate_to_latest(d, info)

if info.object_type == pystac.STACObjectType.CATALOG:
return pystac.Catalog.from_dict(d, href=href, root=root, migrate=False)

if info.object_type == pystac.STACObjectType.COLLECTION:
return pystac.Collection.from_dict(d, href=href, root=root, migrate=False)

if info.object_type == pystac.STACObjectType.ITEM:
return pystac.Item.from_dict(d, href=href, root=root, migrate=False)

raise ValueError(f"Unknown STAC object type {info.object_type}")

# This is set in __init__.py
_STAC_OBJECT_CLASSES = None
Expand Down
Loading

0 comments on commit 2739661

Please sign in to comment.