Skip to content

Commit

Permalink
Default layout strategy for catalogs (#1295)
Browse files Browse the repository at this point in the history
* add layout strategy to catalog

* add layout strategy to collection, update docstring

* update documentation

* changelog

* harmonize strategy argument name

* update arg name in documentation
  • Loading branch information
thomas-maschler authored Jan 10, 2024
1 parent 0407be2 commit 8dc1c65
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Changed

- Made item pickles smaller by changing how nested links are stored([#1285](https://github.com/stac-utils/pystac/pull/1285))
- Allow setting a default layout strategy for Catalog ([#1295](https://github.com/stac-utils/pystac/pull/1295))

### Fixed

Expand Down
27 changes: 26 additions & 1 deletion docs/concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ Layouts
~~~~~~~

PySTAC provides a few different strategies for laying out the HREFs of a STAC.
To use them you can pass in a strategy to the normalize_hrefs call.
To use them you can pass in a strategy when instantiating a catalog or when
calling `normalize_hrefs`.

Using templates
'''''''''''''''
Expand Down Expand Up @@ -125,6 +126,30 @@ fallback strategy (which defaults to
:class:`~pystac.layout.BestPracticesLayoutStrategy`) for any stac object type that you
don't supply a function for.


Set a default catalog layout strategy
'''''''''''''''''''''''''''''''''''''

Instead of fixing the HREFs of child objects retrospectively using `normalize_hrefs`,
you can also define a default strategy for a catalog. When instantiating a catalog,
pass in a custom strategy and base href. Consequently, the HREFs of all child
objects and items added to the catalog tree will be set correctly using that strategy.


.. code-block:: python
from pystac import Catalog, Collection, Item
catalog = Catalog(...,
href="/some/location/catalog.json",
strategy=custom_strategy)
collection = Collection(...)
item = Item(...)
catalog.add_child(collection)
collection.add_item(item)
catalog.save()
.. _catalog types:

Catalog Types
Expand Down
52 changes: 42 additions & 10 deletions pystac/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ class Catalog(STACObject):
catalog's self link's HREF.
catalog_type : Optional catalog type for this catalog. Must
be one of the values in :class:`~pystac.CatalogType`.
strategy : The layout strategy to use for setting the
HREFs of the catalog child objections and items.
If not provided, it will default to the strategy of the root and fallback to
:class:`~pystac.layout.BestPracticesLayoutStrategy`.
"""

catalog_type: CatalogType
Expand Down Expand Up @@ -167,6 +171,9 @@ class Catalog(STACObject):
a canonical format.
"""

_fallback_strategy: HrefLayoutStrategy = BestPracticesLayoutStrategy()
"""Fallback layout strategy"""

def __init__(
self,
id: str,
Expand All @@ -176,6 +183,7 @@ def __init__(
extra_fields: dict[str, Any] | None = None,
href: str | None = None,
catalog_type: CatalogType = CatalogType.ABSOLUTE_PUBLISHED,
strategy: HrefLayoutStrategy | None = None,
):
super().__init__(stac_extensions or [])

Expand All @@ -196,6 +204,8 @@ def __init__(

self.catalog_type: CatalogType = catalog_type

self.strategy: HrefLayoutStrategy | None = strategy

self._resolved_objects.cache(self)

def __repr__(self) -> str:
Expand All @@ -221,6 +231,19 @@ def is_relative(self) -> bool:
CatalogType.SELF_CONTAINED,
]

def _get_strategy(self, strategy: HrefLayoutStrategy | None) -> HrefLayoutStrategy:
if strategy is not None:
return strategy
elif self.strategy is not None:
return self.strategy
elif root := self.get_root():
if root.strategy is not None:
return root.strategy
else:
return root._fallback_strategy
else:
return self._fallback_strategy

def add_child(
self,
child: Catalog | Collection,
Expand All @@ -241,6 +264,7 @@ def add_child(
title : Optional title to give to the :class:`~pystac.Link`
strategy : The layout strategy to use for setting the
self href of the child. If not provided, defaults to
the layout strategy of the parent or root and falls back to
:class:`~pystac.layout.BestPracticesLayoutStrategy`.
set_parent : Whether to set the parent on the child as well.
Defaults to True.
Expand All @@ -253,8 +277,7 @@ def add_child(
if isinstance(child, pystac.Item):
raise pystac.STACError("Cannot add item as child. Use add_item instead.")

if strategy is None:
strategy = BestPracticesLayoutStrategy()
strategy = self._get_strategy(strategy)

child.set_root(self.get_root())
if set_parent:
Expand All @@ -272,18 +295,26 @@ def add_child(
self.add_link(child_link)
return child_link

def add_children(self, children: Iterable[Catalog | Collection]) -> list[Link]:
def add_children(
self,
children: Iterable[Catalog | Collection],
strategy: HrefLayoutStrategy | None = None,
) -> 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.
Args:
children : The children to add.
strategy : The layout strategy to use for setting the
self href of the children. If not provided, defaults to
the layout strategy of the parent or root and falls back to
:class:`~pystac.layout.BestPracticesLayoutStrategy`.
Returns:
List[Link]: An array of links created for the children
"""
return [self.add_child(child) for child in children]
return [self.add_child(child, strategy=strategy) for child in children]

def add_item(
self,
Expand All @@ -304,6 +335,7 @@ def add_item(
title : Optional title to give to the :class:`~pystac.Link`
strategy : The layout strategy to use for setting the
self href of the item. If not provided, defaults to
the layout strategy of the parent or root and falls back to
:class:`~pystac.layout.BestPracticesLayoutStrategy`.
set_parent : Whether to set the parent on the item as well.
Defaults to True.
Expand All @@ -316,8 +348,7 @@ def add_item(
if isinstance(item, pystac.Catalog):
raise pystac.STACError("Cannot add catalog as item. Use add_child instead.")

if strategy is None:
strategy = BestPracticesLayoutStrategy()
strategy = self._get_strategy(strategy)

item.set_root(self.get_root())
if set_parent:
Expand Down Expand Up @@ -349,6 +380,7 @@ def add_items(
items : The items to add.
strategy : The layout strategy to use for setting the
self href of the items. If not provided, defaults to
the layout strategy of the parent or root and falls back to
:class:`~pystac.layout.BestPracticesLayoutStrategy`.
Returns:
Expand Down Expand Up @@ -687,6 +719,7 @@ def normalize_and_save(
catalog_type if there is no root catalog.
strategy : The layout strategy to use in setting the
HREFS for this catalog. If not provided, defaults to
the layout strategy of the parent or root and falls back to
:class:`~pystac.layout.BestPracticesLayoutStrategy`
stac_io : Optional instance of :class:`~pystac.StacIO` to use. If not
provided, will use the instance set while reading in the catalog,
Expand Down Expand Up @@ -721,6 +754,7 @@ def normalize_hrefs(
root_href : The absolute HREF that all links will be normalized against.
strategy : The layout strategy to use in setting the HREFS
for this catalog. If not provided, defaults to
the layout strategy of the parent or root and falls back to
:class:`~pystac.layout.BestPracticesLayoutStrategy`
skip_unresolved : Skip unresolved links when normalizing the tree.
Defaults to False.
Expand All @@ -729,10 +763,8 @@ def normalize_hrefs(
:stac-spec:`STAC best practices document <best-practices.md#catalog-layout>`
for the canonical layout of a STAC.
"""
if strategy is None:
_strategy: HrefLayoutStrategy = BestPracticesLayoutStrategy()
else:
_strategy = strategy

_strategy = self._get_strategy(strategy)

# Normalizing requires an absolute path
if not is_absolute_href(root_href):
Expand Down
6 changes: 6 additions & 0 deletions pystac/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,10 @@ class Collection(Catalog, Assets):
assets : A dictionary mapping string keys to :class:`~pystac.Asset` objects. All
:class:`~pystac.Asset` values in the dictionary will have their
:attr:`~pystac.Asset.owner` attribute set to the created Collection.
strategy : The layout strategy to use for setting the
HREFs of the catalog child objections and items.
If not provided, it will default to strategy of the parent and fallback to
:class:`~pystac.layout.BestPracticesLayoutStrategy`.
"""

description: str
Expand Down Expand Up @@ -529,6 +533,7 @@ def __init__(
providers: list[Provider] | None = None,
summaries: Summaries | None = None,
assets: dict[str, Asset] | None = None,
strategy: HrefLayoutStrategy | None = None,
):
super().__init__(
id,
Expand All @@ -538,6 +543,7 @@ def __init__(
extra_fields,
href,
catalog_type or CatalogType.ABSOLUTE_PUBLISHED,
strategy,
)
self.extent = extent
self.license = license
Expand Down
Loading

0 comments on commit 8dc1c65

Please sign in to comment.