diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5eb1e74a3..16ae25126 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,9 @@ -**Related Issue(s):** # +**Related Issue(s):** +- # **Description:** - **PR Checklist:** - [ ] Code is formatted (run `pre-commit run --all-files`) diff --git a/CHANGELOG.md b/CHANGELOG.md index e45c8a8e6..8374366b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - `RetryStacIO` ([#986](https://github.com/stac-utils/pystac/pull/986)) - `STACObject.remove_hierarchical_links` and `Link.is_hierarchical` ([#999](https://github.com/stac-utils/pystac/pull/999)) - `extra_fields` to `AssetDefinition` in the item assets extension ([#1003](https://github.com/stac-utils/pystac/pull/1003)) +- `Catalog.fully_resolve` ([#1001](https://github.com/stac-utils/pystac/pull/1001)) ### Removed diff --git a/README.md b/README.md index ac6e2fc75..4791950c2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PySTAC -![Build Status](https://github.com/stac-utils/pystac/workflows/CI/badge.svg?branch=main) +[![Build Status](https://github.com/stac-utils/pystac/workflows/CI/badge.svg?branch=main)](https://github.com/stac-utils/pystac/actions/workflows/continuous-integration.yml) [![PyPI version](https://badge.fury.io/py/pystac.svg)](https://badge.fury.io/py/pystac) [![Conda (channel only)](https://img.shields.io/conda/vn/conda-forge/pystac)](https://anaconda.org/conda-forge/pystac) [![Documentation](https://readthedocs.org/projects/pystac/badge/?version=latest)](https://pystac.readthedocs.io/en/latest/) diff --git a/pystac/catalog.py b/pystac/catalog.py index 40174966f..260d23bbb 100644 --- a/pystac/catalog.py +++ b/pystac/catalog.py @@ -911,6 +911,20 @@ def walk( for child in self.get_children(): yield from child.walk() + def fully_resolve(self) -> None: + """Resolves every link in this catalog. + + Useful if, e.g., you'd like to read a catalog from a filesystem, upgrade + every object in the catalog to the latest STAC version, and save it back + to the filesystem. By default, :py:meth:`~pystac.Catalog.save` skips + unresolved links. + """ + for _, _, items in self.walk(): + # items is a generator, so we need to consume it to resolve the + # items + for item in items: + pass + def validate_all(self) -> None: """Validates each catalog, collection contained within this catalog. diff --git a/tests/conftest.py b/tests/conftest.py index fa525042a..58ad09162 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,5 +25,5 @@ def item() -> Item: @pytest.fixture -def label_catalog() -> Catalog: +def test_case_1_catalog() -> Catalog: return TestCases.case_1() diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 6bf42d972..fb984ba6c 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -7,6 +7,7 @@ from collections import defaultdict from copy import deepcopy from datetime import datetime +from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Union, cast import pytest @@ -1462,8 +1463,18 @@ def from_dict( @pytest.mark.parametrize("add_canonical", (True, False)) -def test_remove_hierarchical_links(label_catalog: Catalog, add_canonical: bool) -> None: - label_catalog.remove_hierarchical_links(add_canonical=add_canonical) - for link in label_catalog.links: +def test_remove_hierarchical_links( + test_case_1_catalog: Catalog, add_canonical: bool +) -> None: + test_case_1_catalog.remove_hierarchical_links(add_canonical=add_canonical) + for link in test_case_1_catalog.links: assert not link.is_hierarchical() - assert bool(label_catalog.get_single_link("canonical")) == add_canonical + assert bool(test_case_1_catalog.get_single_link("canonical")) == add_canonical + + +def test_fully_resolve(tmp_path: Path, test_case_1_catalog: Catalog) -> None: + test_case_1_catalog.save(dest_href=str(tmp_path / "before")) + assert len(list((tmp_path / "before").glob("**/*.json"))) == 1 + test_case_1_catalog.fully_resolve() + test_case_1_catalog.save(dest_href=str(tmp_path / "after")) + assert len(list((tmp_path / "after").glob("**/*.json"))) == 15 diff --git a/tests/test_collection.py b/tests/test_collection.py index 8751bcb29..99a0c93f2 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -533,8 +533,10 @@ def from_dict( @pytest.mark.parametrize("add_canonical", (True, False)) -def test_remove_hierarchical_links(label_catalog: Catalog, add_canonical: bool) -> None: - collection = list(label_catalog.get_all_collections())[0] +def test_remove_hierarchical_links( + test_case_1_catalog: Catalog, add_canonical: bool +) -> None: + collection = list(test_case_1_catalog.get_all_collections())[0] collection.remove_hierarchical_links(add_canonical=add_canonical) for link in collection.links: assert not link.is_hierarchical() diff --git a/tests/test_item.py b/tests/test_item.py index 2ac7a8d2c..62d24c567 100644 --- a/tests/test_item.py +++ b/tests/test_item.py @@ -458,8 +458,10 @@ def test_item_from_dict_with_missing_type_raises_useful_error() -> None: @pytest.mark.parametrize("add_canonical", (True, False)) -def test_remove_hierarchical_links(label_catalog: Catalog, add_canonical: bool) -> None: - item = list(label_catalog.get_all_items())[0] +def test_remove_hierarchical_links( + test_case_1_catalog: Catalog, add_canonical: bool +) -> None: + item = list(test_case_1_catalog.get_all_items())[0] item.remove_hierarchical_links(add_canonical=add_canonical) for link in item.links: assert not link.is_hierarchical()