From 1a7040240a92b888e1dfe3cbb5d21a3e22bdd3b6 Mon Sep 17 00:00:00 2001 From: Pete Gadomski Date: Thu, 29 Aug 2024 07:56:00 -0600 Subject: [PATCH] feat(python): add migrate --- .github/workflows/ci.yml | 6 ++++-- README.md | 6 ++++-- python/.gitignore | 2 ++ python/CHANGELOG.md | 13 ++++++++++++ python/README.md | 3 +++ python/docs/api.md | 6 +++++- python/mkdocs.yml | 2 +- python/pyproject.toml | 1 + python/requirements-dev.in | 4 ++++ python/requirements-dev.txt | 20 ++++++++++++++++++ python/src/lib.rs | 41 +++++++++++++++++++++++++++++++++++- python/stacrs.pyi | 3 ++- python/tests/test_migrate.py | 8 +++++++ 13 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 python/requirements-dev.in create mode 100644 python/requirements-dev.txt create mode 100644 python/tests/test_migrate.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b795a84..21cff078 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,12 +91,14 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.x - - name: Install maturin and pytest - run: pip install maturin pytest + - name: Install dev requirements + run: pip install -r python/requirements-dev.txt - name: Build run: maturin build --manifest-path python/Cargo.toml --out dist - name: Install stacrs run: pip install stacrs --find-links dist --no-index + - name: Check + run: ruff check python && ruff format --check python && mypy python - name: Test run: pytest python/tests check: diff --git a/README.md b/README.md index 32d481a8..4e308f7f 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,16 @@ This monorepo contains several crates: ## Bindings -This repo includes Python bindings, called **stacrs**. +### Python + +**stacrs** is a small, no-dependency Python library that uses **stac-rs** under the hood. Install with **pip**: ```shell pip install stacrs ``` -See [the README](./python/README.md) for more information. +See [the documentation](https://stacrs.readthedocs.io/) for more information. ## Development diff --git a/python/.gitignore b/python/.gitignore index c8f04429..a54e954c 100644 --- a/python/.gitignore +++ b/python/.gitignore @@ -70,3 +70,5 @@ docs/_build/ # Pyenv .python-version + +site diff --git a/python/CHANGELOG.md b/python/CHANGELOG.md index bd9280f8..62783818 100644 --- a/python/CHANGELOG.md +++ b/python/CHANGELOG.md @@ -4,8 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- `migrate` ([#309](https://github.com/stac-utils/stac-rs/pull/309)) +- `validate` and docs ([#307](https://github.com/stac-utils/stac-rs/pull/307)) + +## [0.0.2] - 2024-08-28 + +Non-functional release to fix releasing from Github actions. + ## [0.0.1] - 2024-08-28 Initial release. +[Unreleased]: https://github.com/stac-utils/stac-rs/compare/python-v0.0.2...main +[0.0.2]: https://github.com/stac-utils/stac-rs/compare/python-v0.0.1...python-v0.0.2 [0.0.1]: https://github.com/stac-utils/stac-rs/releases/tag/python-v0.0.1 diff --git a/python/README.md b/python/README.md index a51e2be5..53d58343 100644 --- a/python/README.md +++ b/python/README.md @@ -2,6 +2,7 @@ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/stac-utils/stac-rs/ci.yml?branch=main&style=for-the-badge)](https://github.com/stac-utils/stac-rs/actions/workflows/ci.yml) [![PyPI - Version](https://img.shields.io/pypi/v/stacrs?style=for-the-badge)](https://pypi.org/project/stacrs) +[![Read the Docs](https://img.shields.io/readthedocs/stacrs?style=for-the-badge)](https://stacrs.readthedocs.io/) ![PyPI - License](https://img.shields.io/pypi/l/stacrs?style=for-the-badge) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg?style=for-the-badge)](./CODE_OF_CONDUCT) @@ -23,6 +24,8 @@ import stacrs stacrs.validate_href("https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/simple-item.json") ``` +See [the documentation](https://stacrs.readthedocs.io/) for more information. + ## Other info This crate is part of the [stac-rs](https://github.com/stac-utils/stac-rs) monorepo, see its README for contributing and license information. diff --git a/python/docs/api.md b/python/docs/api.md index f65ef4e4..7728105a 100644 --- a/python/docs/api.md +++ b/python/docs/api.md @@ -2,7 +2,11 @@ API documentation for **stacrs**. -## Validation +## Migrate + +::: stacrs.migrate + +## Validate ::: stacrs.validate ::: stacrs.validate_href diff --git a/python/mkdocs.yml b/python/mkdocs.yml index 10d6cf97..a2c69f09 100644 --- a/python/mkdocs.yml +++ b/python/mkdocs.yml @@ -19,7 +19,7 @@ plugins: show_root_heading: true show_signature: true show_signature_annotations: true - separate_signature: false + separate_signature: true markdown_extensions: - pymdownx.highlight: anchor_linenums: true diff --git a/python/pyproject.toml b/python/pyproject.toml index 50e97829..6d8d1a8b 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -24,6 +24,7 @@ dynamic = ["version"] [project.urls] Repository = "https://github.com/stac-utils/stac-rs/tree/main/python" +Documentation = "https://stacrs.readthedocs.io/" Issues = "https://github.com/stac-utils/stac-rs/issues" [tool.maturin] diff --git a/python/requirements-dev.in b/python/requirements-dev.in new file mode 100644 index 00000000..0b9db34c --- /dev/null +++ b/python/requirements-dev.in @@ -0,0 +1,4 @@ +maturin +mypy +ruff +pytest diff --git a/python/requirements-dev.txt b/python/requirements-dev.txt new file mode 100644 index 00000000..9236ce53 --- /dev/null +++ b/python/requirements-dev.txt @@ -0,0 +1,20 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile python/requirements-dev.in +iniconfig==2.0.0 + # via pytest +maturin==1.7.1 + # via -r python/requirements-dev.in +mypy==1.11.2 + # via -r python/requirements-dev.in +mypy-extensions==1.0.0 + # via mypy +packaging==24.1 + # via pytest +pluggy==1.5.0 + # via pytest +pytest==8.3.2 + # via -r python/requirements-dev.in +ruff==0.6.2 + # via -r python/requirements-dev.in +typing-extensions==4.12.2 + # via mypy diff --git a/python/src/lib.rs b/python/src/lib.rs index 40f15aa6..66579025 100644 --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -1,9 +1,47 @@ use pyo3::{create_exception, exceptions::PyException, prelude::*, types::PyDict}; -use stac::Value; +use stac::{Migrate, Value}; use stac_validate::Validate; create_exception!(stacrs, StacrsError, PyException, "An error in stacrs"); +/// Migrates a STAC dictionary to another version. +/// +/// Migration can be as simple as updating the `stac_version` attribute, but +/// sometimes can be more complicated. For example, when migrating to v1.1.0, +/// [eo:bands and raster:bands should be consolidated to the new bands +/// structure](https://github.com/radiantearth/stac-spec/releases/tag/v1.1.0-beta.1). +/// +/// See [the stac-rs +/// documentation](https://docs.rs/stac/latest/stac/enum.Version.html) for +/// supported versions. +/// +/// Args: +/// value (dict[str, Any]): The STAC value to migrate +/// version (str | None): The version to migrate to. If not provided, the +/// value will be migrated to the latest stable version. +/// +/// Examples: +/// >>> with open("examples/simple-item.json") as f: +/// >>> item = json.load(f) +/// >>> item = stacrs.migrate(item, "1.1.0-beta.1") +/// >>> assert item["stac_version"] == "1.1.0-beta.1" +#[pyfunction] +#[pyo3(signature = (value, version=None))] +fn migrate<'py>(value: &Bound<'py, PyDict>, version: Option<&str>) -> PyResult> { + let py = value.py(); + let value: Value = pythonize::depythonize(value)?; + let version = version + .map(|version| version.parse()) + .transpose() + .map_err(|err: stac::Error| StacrsError::new_err(err.to_string()))? + .unwrap_or_default(); + let value = value + .migrate(version) + .map_err(|err| StacrsError::new_err(err.to_string()))?; + let value = pythonize::pythonize(py, &value)?; + value.downcast_into().map_err(PyErr::from) +} + /// Validates a single href with json-schema. /// /// Args: @@ -64,6 +102,7 @@ fn validate_value(value: Value) -> PyResult<()> { /// A collection of functions for working with STAC, using Rust under the hood. #[pymodule] fn stacrs(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(migrate, m)?)?; m.add_function(wrap_pyfunction!(validate_href, m)?)?; m.add_function(wrap_pyfunction!(validate, m)?)?; m.add("StacrsError", m.py().get_type_bound::())?; diff --git a/python/stacrs.pyi b/python/stacrs.pyi index e92d4bd8..7d8b4da9 100644 --- a/python/stacrs.pyi +++ b/python/stacrs.pyi @@ -1,6 +1,7 @@ -from typing import Any +from typing import Any, Optional class StacrsError(Exception): ... +def migrate(value: dict[str, Any], version: Optional[str] = None) -> dict[str, Any]: ... def validate_href(href: str) -> None: ... def validate(value: dict[str, Any]) -> None: ... diff --git a/python/tests/test_migrate.py b/python/tests/test_migrate.py new file mode 100644 index 00000000..d7705c7d --- /dev/null +++ b/python/tests/test_migrate.py @@ -0,0 +1,8 @@ +from typing import Any + +import stacrs + + +def test_migrate(item: dict[str, Any]) -> None: + item = stacrs.migrate(item, version="1.1.0-beta.1") + assert item["stac_version"] == "1.1.0-beta.1"