From c0bb06ec846ae97f14a21bba1a1699f97fd8290d Mon Sep 17 00:00:00 2001 From: jonhealy1 Date: Tue, 10 Jun 2025 14:52:39 +0800 Subject: [PATCH 1/2] stac-pydantic optional --- CHANGELOG.md | 7 +++++++ setup.py | 4 +++- stac_check/cli.py | 14 ++++++++++++- stac_check/lint.py | 15 ++++++++++++++ tests/test_lint.py | 50 +++++++++++++++++++++++++++++++++++----------- 5 files changed, 76 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bda1ff4..1bd6d56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/) ### Changed +- Made `stac-pydantic` an optional dependency ([#XXX](https://github.com/stac-utils/stac-check/pull/XXX)) + - `stac-validator` is now installed without the `[pydantic]` extra by default + - Added `stac-check[pydantic]` extra for users who need pydantic validation + - Added graceful fallback to JSONSchema validation when pydantic is not available + - Updated tests to handle both scenarios (with and without pydantic installed) + - Added helpful warning messages when pydantic is requested but not installed + - Migrated documentation from Read the Docs to GitHub Pages ([#128](https://github.com/stac-utils/stac-check/pull/128)) - Updated documentation build system to use Sphinx with sphinx_rtd_theme - Added support for Markdown content in documentation using myst-parser diff --git a/setup.py b/setup.py index 975d298..9bffa9e 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ "requests>=2.32.3", "jsonschema>=4.23.0", "click>=8.1.8", - "stac-validator[pydantic]>=3.7.0", + "stac-validator>=3.7.0", "PyYAML", "python-dotenv", ], @@ -29,6 +29,7 @@ "pytest", "requests-mock", "types-setuptools", + "stac-validator[pydantic]", ], "docs": [ "sphinx>=4.0.0", @@ -36,6 +37,7 @@ "myst-parser>=0.18.0", "sphinx-autodoc-typehints>=1.18.0", ], + "pydantic": ["stac-validator[pydantic]"], }, entry_points={"console_scripts": ["stac-check=stac_check.cli:main"]}, author="Jonathan Healy", diff --git a/stac_check/cli.py b/stac_check/cli.py index c8e7f7f..426ef46 100644 --- a/stac_check/cli.py +++ b/stac_check/cli.py @@ -223,12 +223,24 @@ def cli_message(linter: Linter) -> None: @click.option( "--pydantic", is_flag=True, - help="Use stac-pydantic for enhanced validation with Pydantic models.", + help="Use pydantic validation (requires stac-pydantic to be installed).", ) @click.command() @click.argument("file") @click.version_option(version=importlib.metadata.distribution("stac-check").version) def main(file, recursive, max_depth, assets, links, no_assets_urls, header, pydantic): + # Check if pydantic validation is requested but not installed + if pydantic: + try: + importlib.import_module("stac_pydantic") + except ImportError: + click.secho( + "Warning: stac-pydantic is not installed. Pydantic validation will be disabled.\n" + "To enable pydantic validation, install it with: pip install stac-check[pydantic]", + fg="yellow", + ) + pydantic = False + linter = Linter( file, assets=assets, diff --git a/stac_check/lint.py b/stac_check/lint.py index 41fa1ba..dd338f8 100644 --- a/stac_check/lint.py +++ b/stac_check/lint.py @@ -141,6 +141,21 @@ def check_summaries(self) -> bool: pydantic: bool = False def __post_init__(self): + # Check if pydantic validation is requested but not installed + if self.pydantic: + try: + importlib.import_module("stac_pydantic") + except ImportError: + import warnings + + warnings.warn( + "stac-pydantic is not installed. Pydantic validation will be disabled. " + "Install it with: pip install stac-check[pydantic]", + UserWarning, + stacklevel=2, + ) + self.pydantic = False + self.data = self.load_data(self.item) self.message = self.validate_file(self.item) self.config = self.parse_config(self.config_file) diff --git a/tests/test_lint.py b/tests/test_lint.py index dbb66da..3cee1cf 100644 --- a/tests/test_lint.py +++ b/tests/test_lint.py @@ -923,34 +923,60 @@ def test_bbox_antimeridian(): assert linter.check_bbox_antimeridian() == True +# Check if stac-pydantic is available +try: + import importlib + + importlib.import_module("stac_pydantic") + PYDANTIC_AVAILABLE = True +except ImportError: + PYDANTIC_AVAILABLE = False + +# Test decorator for pydantic tests +pytest.mark.pydantic = pytest.mark.skipif( + not PYDANTIC_AVAILABLE, + reason="stac-pydantic is not installed. Run 'pip install -e .[dev]' to install test dependencies.", +) + + +@pytest.mark.pydantic def test_lint_pydantic_validation_valid(): """Test pydantic validation with a valid STAC item.""" file = "sample_files/1.0.0/core-item.json" linter = Linter(file, pydantic=True) - assert linter.valid_stac == True + assert linter.valid_stac is True assert linter.asset_type == "ITEM" - assert "stac-pydantic Item model" in linter.message["schema"] + assert any("stac-pydantic" in schema for schema in linter.message["schema"]) assert linter.message["validation_method"] == "pydantic" +@pytest.mark.pydantic def test_lint_pydantic_validation_invalid(): """Test pydantic validation with an invalid STAC item (missing required fields).""" file = "sample_files/1.0.0/bad-item.json" linter = Linter(file, pydantic=True) - assert linter.valid_stac == False + assert linter.valid_stac is False assert "PydanticValidationError" in linter.message["error_type"] assert "id: Field required" in linter.message["error_message"] - assert linter.message["validation_method"] == "pydantic" -def test_lint_pydantic_validation_recursive(): - """Test pydantic validation with recursive option.""" - file = "sample_files/1.0.0/collection.json" - linter = Linter(file, recursive=True, max_depth=1, pydantic=True) +def test_pydantic_fallback_without_import(monkeypatch): + """Test that pydantic validation falls back to JSONSchema when stac-pydantic is not available.""" + # Skip this test if stac-pydantic is actually installed + if PYDANTIC_AVAILABLE: + pytest.skip("stac-pydantic is installed, skipping fallback test") - assert linter.valid_stac == True - assert linter.asset_type == "COLLECTION" - assert "stac-pydantic Collection model" in linter.message["schema"] - assert linter.message["validation_method"] == "pydantic" + # Test that pydantic=False works without stac-pydantic + file = "sample_files/1.0.0/core-item.json" + linter = Linter(file, pydantic=False) + assert linter.valid_stac is True + assert linter.asset_type == "ITEM" + assert linter.message["validation_method"] == "default" + + # Test that pydantic=True falls back to JSONSchema when stac-pydantic is not available + linter = Linter(file, pydantic=True) + assert linter.valid_stac is True + assert linter.asset_type == "ITEM" + assert linter.message["validation_method"] == "default" From fbdbf94bdcb7410f68bd2d94b47cd204c70c082b Mon Sep 17 00:00:00 2001 From: jonhealy1 Date: Tue, 10 Jun 2025 14:55:41 +0800 Subject: [PATCH 2/2] add pr number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bd6d56..13576ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/) ### Changed -- Made `stac-pydantic` an optional dependency ([#XXX](https://github.com/stac-utils/stac-check/pull/XXX)) +- Made `stac-pydantic` an optional dependency ([#129](https://github.com/stac-utils/stac-check/pull/129)) - `stac-validator` is now installed without the `[pydantic]` extra by default - Added `stac-check[pydantic]` extra for users who need pydantic validation - Added graceful fallback to JSONSchema validation when pydantic is not available