Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactors architecture to better enable STAC 1.0 extension changes #309

Merged
merged 54 commits into from
May 6, 2021
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d71c303
Add test files for 1.0.0-RC1
lossyrob Mar 11, 2021
d531cc2
Add type annotations to everything
lossyrob Apr 22, 2021
4bb702a
Update code and tests to pass with type annotations
lossyrob Apr 25, 2021
c78f832
Add mypy to test step; fixes based on mypy errors
lossyrob Apr 25, 2021
73fad0d
Remove LinkMixin, implement directly into STACObject
lossyrob Apr 25, 2021
4100dae
Use underscore naming convention for TYPE_CHECKING imports
lossyrob Apr 25, 2021
330e1b7
New extension architecture in place, refactor eo, file and label.
lossyrob Apr 26, 2021
a72413a
Implement collection assets; extension summaries; more fixes.
lossyrob Apr 27, 2021
e968349
Rename examples with consistent version tag.
lossyrob Apr 27, 2021
8b873ad
Modify change_stac_version to perform migration
lossyrob Apr 27, 2021
1b93500
Set schema_uri during eo migration
lossyrob Apr 27, 2021
421788f
More extension refactors
lossyrob Apr 28, 2021
8a2de1d
Merge remote-tracking branch 'origin/main' into feature/rde/1.0-refac…
lossyrob Apr 28, 2021
f70e364
Remove single-file-stac extensions
lossyrob Apr 28, 2021
aa57ae5
More extension refactors
lossyrob Apr 28, 2021
a1f5096
All extensions refactored to new architecture
lossyrob Apr 29, 2021
245a6db
Fixed identify tests; fixing migration teses
lossyrob Apr 29, 2021
d614036
Update version schema to fixed published version
lossyrob Apr 29, 2021
dc80bb5
Migrate checksum -> file
lossyrob Apr 29, 2021
77ea376
Remove collection-assets extension
lossyrob Apr 29, 2021
aa98fde
Update STAC version to 1.0.0-rc.3
lossyrob Apr 29, 2021
9b8e1bc
Set href during migrate
lossyrob Apr 29, 2021
f566aa9
Change extension API to use static method on extension class
lossyrob Apr 30, 2021
f69b305
Migrate test files to 1.0.0-rc.3
lossyrob Apr 30, 2021
7438ee8
Add datacube extension
lossyrob Apr 30, 2021
1e0ae07
Add item_assets extension
lossyrob Apr 30, 2021
aba25aa
Fixe tests
lossyrob Apr 30, 2021
71cba8a
yapf formatting
lossyrob Apr 30, 2021
c073712
Switch to pyright
lossyrob Apr 30, 2021
b2e2441
Flake8 fixes
lossyrob Apr 30, 2021
f6e1716
Remove dataclasses usage as we're still supporting python 3.6
lossyrob Apr 30, 2021
d0c6cc5
Refactor STAC_IO to StacIO
lossyrob Apr 30, 2021
501e98e
Move to black as formatter
lossyrob Apr 30, 2021
aa57019
Reformat with black
lossyrob Apr 30, 2021
a1019e6
Modify flake8 config to match black
lossyrob Apr 30, 2021
3f99821
Fixup linting changes with shorter line length requirement
lossyrob May 1, 2021
07f5cc6
Bump sphinx version
duckontheweb May 2, 2021
a45d5e6
Fix basic sphinx build issues
duckontheweb May 2, 2021
eccdf8c
Add note about Sphinx warnings
duckontheweb May 2, 2021
773a571
Remove import pystac as ps in favor of import pystac
lossyrob May 3, 2021
d3e573b
Move extension exceptions into pystac.errors
lossyrob May 3, 2021
f22b587
Move STACValidationError to pystac.errors
lossyrob May 3, 2021
c53428b
Merge remote-tracking branch 'origin/feature/jd/1.0-refactors-doc-fix…
lossyrob May 3, 2021
aa1cdf8
Issue deprecation warnings for STAC_IO
lossyrob May 3, 2021
5680583
Fix stray docstring escape char breaking CI
lossyrob May 3, 2021
af2a6e4
Fix typo
lossyrob May 3, 2021
3e21956
Use Union[Catalog, Collection] as child type in Catalog.
lossyrob May 4, 2021
6236686
Allow all `from_file` overrides to set a stac_io implementation
lossyrob May 4, 2021
20afd2c
Asset.get_absoulte_href returns None if no abs href possible
lossyrob May 4, 2021
9d1ffee
Add test files for 1.0.0-RC3
lossyrob Mar 11, 2021
4e99310
Ensure stac_extensions is a list for consistency
lossyrob May 4, 2021
c71a716
Add 1.0.0-RC3 examples
lossyrob May 4, 2021
f6ee841
Update CHANGELOG
lossyrob May 4, 2021
63f83f4
Merge remote-tracking branch 'origin/main' into feature/rde/1.0-refac…
lossyrob May 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
15 changes: 2 additions & 13 deletions .flake8
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
[flake8]
max-line-length = 100

## IGNORES

# E126: yapf conflicts with "continuation line over-indented for hanging indent"

# E127: flake8 reporting incorrect continuation line indent errors
# on multi-line and multi-level indents

# W503: flake8 reports this as incorrect, and scripts/format_code
# changes code to it, so let format_code win.

ignore = E126,E127,W503
max-line-length = 88
extend-ignore = E203, W503, E731, E722
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[mypy]
ignore_missing_imports = True
disallow_untyped_defs = True
108 changes: 66 additions & 42 deletions pystac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,72 @@
"""

# flake8: noqa


class STACError(Exception):
"""A STACError is raised for errors relating to STAC, e.g. for
invalid formats or trying to operate on a STAC that does not have
the required information available.
"""
pass


from pystac.version import (__version__, get_stac_version, set_stac_version)
from pystac.stac_io import STAC_IO
from pystac.extensions import Extensions
from pystac.stac_object import (STACObject, STACObjectType)
from pystac.media_type import MediaType
from pystac.link import (Link, HIERARCHICAL_LINKS)
from pystac.catalog import (Catalog, CatalogType)
from pystac.collection import (Collection, Extent, SpatialExtent, TemporalExtent, Provider)
from pystac.item import (Item, Asset, CommonMetadata)

from pystac.serialization import stac_object_from_dict
from pystac.errors import (
STACError, # type:ignore
STACTypeError, # type:ignore
RequiredPropertyMissing, # type:ignore
)

from typing import Any, Dict, Optional
from pystac.version import (
__version__,
get_stac_version, # type:ignore
set_stac_version, # type:ignore
)
from pystac.stac_io import StacIO # type:ignore
from pystac.stac_object import STACObject, STACObjectType # type:ignore
from pystac.media_type import MediaType # type:ignore
from pystac.link import Link, HIERARCHICAL_LINKS # type:ignore
from pystac.catalog import Catalog, CatalogType # type:ignore
from pystac.collection import (
Collection, # type:ignore
Extent, # type:ignore
SpatialExtent, # type:ignore
TemporalExtent, # type:ignore
Provider, # type:ignore
Summaries, # type:ignore
RangeSummary, # type:ignore
)
from pystac.item import Item, Asset, CommonMetadata # type:ignore

import pystac.validation
from pystac.validation import STACValidationError # type:ignore

STAC_IO.stac_object_from_dict = stac_object_from_dict

from pystac import extensions
import pystac.extensions.hooks
import pystac.extensions.datacube
import pystac.extensions.eo
import pystac.extensions.file
import pystac.extensions.item_assets
import pystac.extensions.label
import pystac.extensions.pointcloud
import pystac.extensions.projection
import pystac.extensions.sar
import pystac.extensions.sat
import pystac.extensions.scientific
import pystac.extensions.single_file_stac
import pystac.extensions.timestamps
import pystac.extensions.version
import pystac.extensions.view
import pystac.extensions.file

STAC_EXTENSIONS = extensions.base.RegisteredSTACExtensions([
extensions.eo.EO_EXTENSION_DEFINITION, extensions.label.LABEL_EXTENSION_DEFINITION,
extensions.pointcloud.POINTCLOUD_EXTENSION_DEFINITION,
extensions.projection.PROJECTION_EXTENSION_DEFINITION, extensions.sar.SAR_EXTENSION_DEFINITION,
extensions.sat.SAT_EXTENSION_DEFINITION, extensions.scientific.SCIENTIFIC_EXTENSION_DEFINITION,
extensions.single_file_stac.SFS_EXTENSION_DEFINITION,
extensions.timestamps.TIMESTAMPS_EXTENSION_DEFINITION,
extensions.version.VERSION_EXTENSION_DEFINITION, extensions.view.VIEW_EXTENSION_DEFINITION,
extensions.file.FILE_EXTENSION_DEFINITION
])


def read_file(href):
EXTENSION_HOOKS = pystac.extensions.hooks.RegisteredExtensionHooks(
[
pystac.extensions.datacube.DATACUBE_EXTENSION_HOOKS,
pystac.extensions.eo.EO_EXTENSION_HOOKS,
pystac.extensions.file.FILE_EXTENSION_HOOKS,
pystac.extensions.item_assets.ITEM_ASSETS_EXTENSION_HOOKS,
pystac.extensions.label.LABEL_EXTENSION_HOOKS,
pystac.extensions.pointcloud.POINTCLOUD_EXTENSION_HOOKS,
pystac.extensions.projection.PROJECTION_EXTENSION_HOOKS,
pystac.extensions.sar.SAR_EXTENSION_HOOKS,
pystac.extensions.sat.SAT_EXTENSION_HOOKS,
pystac.extensions.scientific.SCIENTIFIC_EXTENSION_HOOKS,
pystac.extensions.timestamps.TIMESTAMPS_EXTENSION_HOOKS,
pystac.extensions.version.VERSION_EXTENSION_HOOKS,
pystac.extensions.view.VIEW_EXTENSION_HOOKS,
]
)


def read_file(href: str) -> STACObject:
"""Reads a STAC object from a file.

This method will return either a Catalog, a Collection, or an Item based on what the
Expand All @@ -73,7 +86,9 @@ def read_file(href):
return STACObject.from_file(href)


def write_file(obj, include_self_link=True, dest_href=None):
def write_file(
obj: STACObject, include_self_link: bool = True, dest_href: Optional[str] = None
) -> None:
"""Writes a STACObject to a file.

This will write only the Catalog, Collection or Item ``obj``. It will not attempt
Expand All @@ -96,7 +111,12 @@ def write_file(obj, include_self_link=True, dest_href=None):
obj.save_object(include_self_link=include_self_link, dest_href=dest_href)


def read_dict(d, href=None, root=None):
def read_dict(
d: Dict[str, Any],
href: Optional[str] = None,
root: Optional[Catalog] = None,
stac_io: Optional[StacIO] = None,
) -> STACObject:
"""Reads a STAC object from a dict representing the serialized JSON version of the
STAC object.

Expand All @@ -112,5 +132,9 @@ def read_dict(d, href=None, root=None):
root (Catalog or Collection): 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.
stac_io: Optional StacIO instance to use for reading. If None, the
default instance will be used.
"""
return stac_object_from_dict(d, href, root)
if stac_io is None:
stac_io = StacIO.default()
return stac_io.stac_object_from_dict(d, href, root)
165 changes: 165 additions & 0 deletions pystac/asset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
from copy import copy
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union

import pystac as ps
from pystac.utils import is_absolute_href, make_absolute_href

if TYPE_CHECKING:
from pystac.collection import Collection as Collection_Type
from pystac.item import Item as Item_Type


class Asset:
"""An object that contains a link to data associated with an Item or Collection that
can be downloaded or streamed.

Args:
href (str): Link to the asset object. Relative and absolute links are both
allowed.
title (str): Optional displayed title for clients and users.
description (str): 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 (str): Optional description of the media type. Registered Media Types
are preferred. See :class:`~pystac.MediaType` for common media types.
roles ([str]): Optional, Semantic roles (i.e. thumbnail, overview,
data, metadata) of the asset.
properties (dict): Optional, additional properties for this asset. This is used
by extensions as a way to serialize and deserialize properties on asset
object JSON.

Attributes:
href (str): Link to the asset object. Relative and absolute links are both
allowed.
title (str): Optional displayed title for clients and users.
description (str): 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 (str): Optional description of the media type. Registered Media Types
are preferred. See :class:`~pystac.MediaType` for common media types.
properties (dict): Optional, additional properties for this asset. This is used
by extensions as a way to serialize and deserialize properties on asset
object JSON.
owner: The Item or Collection this asset belongs to, or None if it has no owner.
"""

def __init__(
self,
href: str,
title: Optional[str] = None,
description: Optional[str] = None,
media_type: Optional[str] = None,
roles: Optional[List[str]] = None,
properties: Optional[Dict[str, Any]] = None,
) -> None:
self.href = href
self.title = title
self.description = description
self.media_type = media_type
self.roles = roles

if properties is not None:
self.properties = properties
else:
self.properties = {}

# The Item which owns this Asset.
self.owner: Optional[Union[ps.Item, ps.Collection]] = None

def set_owner(self, obj: Union["Collection_Type", "Item_Type"]) -> None:
"""Sets the owning item of this Asset.

The owning item will be used to resolve relative HREFs of this asset.

Args:
obj: The Collection or Item that owns this asset.
"""
self.owner = obj

def get_absolute_href(self) -> Optional[str]:
"""Gets the absolute href for this asset, if possible.

If this Asset has no associated Item, this will return whatever the
href is (as it cannot determine the absolute path, if the asset
href is relative).

Returns:
str: The absolute HREF of this asset, or a relative HREF is an absolute HREF
cannot be determined.
"""
if not is_absolute_href(self.href):
if self.owner is not None:
return make_absolute_href(self.href, self.owner.get_self_href())

return self.href

def to_dict(self) -> Dict[str, Any]:
"""Generate a dictionary representing the JSON of this Asset.

Returns:
dict: A serialization of the Asset that can be written out as JSON.
"""

d: Dict[str, Any] = {"href": self.href}

if self.media_type is not None:
d["type"] = self.media_type

if self.title is not None:
d["title"] = self.title

if self.description is not None:
d["description"] = self.description

if self.properties is not None and len(self.properties) > 0:
for k, v in self.properties.items():
d[k] = v

if self.roles is not None:
d["roles"] = self.roles

return d

def clone(self) -> "Asset":
"""Clones this asset.

Returns:
Asset: The clone of this asset.
"""
return Asset(
href=self.href,
title=self.title,
description=self.description,
media_type=self.media_type,
roles=self.roles,
properties=self.properties,
)

def __repr__(self) -> str:
return "<Asset href={}>".format(self.href)

@staticmethod
def from_dict(d: Dict[str, Any]) -> "Asset":
"""Constructs an Asset from a dict.

Returns:
Asset: The Asset deserialized from the JSON dict.
"""
d = copy(d)
href = d.pop("href")
media_type = d.pop("type", None)
title = d.pop("title", None)
description = d.pop("description", None)
roles = d.pop("roles", None)
properties = None
if any(d):
properties = d

return Asset(
href=href,
media_type=media_type,
title=title,
description=description,
roles=roles,
properties=properties,
)
Loading