From 71b08a53f0b4d4ae1f644e5cf51c2331af799d5e Mon Sep 17 00:00:00 2001 From: rzmk <30333942+rzmk@users.noreply.github.com> Date: Fri, 22 May 2026 17:48:35 -0400 Subject: [PATCH] feat: add initial Python package --- Cargo.lock | 79 +++++++++++++++++++ Cargo.toml | 5 +- README.md | 10 +-- .../.github/workflows/CI.yml | 73 +++++++++++++++++ ckan_geoconnex_bulk_runner_py/.gitignore | 72 +++++++++++++++++ ckan_geoconnex_bulk_runner_py/Cargo.toml | 14 ++++ ckan_geoconnex_bulk_runner_py/pyproject.toml | 13 +++ ckan_geoconnex_bulk_runner_py/src/lib.rs | 31 ++++++++ 8 files changed, 290 insertions(+), 7 deletions(-) create mode 100644 ckan_geoconnex_bulk_runner_py/.github/workflows/CI.yml create mode 100644 ckan_geoconnex_bulk_runner_py/.gitignore create mode 100644 ckan_geoconnex_bulk_runner_py/Cargo.toml create mode 100644 ckan_geoconnex_bulk_runner_py/pyproject.toml create mode 100644 ckan_geoconnex_bulk_runner_py/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 3a35166..b15dca4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -176,6 +176,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "ckan_geoconnex_bulk_runner_py" +version = "0.1.0" +dependencies = [ + "ckan_geoconnex_bulk_runner", + "pyo3", + "serde_json", +] + [[package]] name = "ckanaction" version = "0.2.0" @@ -1198,6 +1207,12 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "potential_utf" version = "0.1.5" @@ -1226,6 +1241,64 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pyo3" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" +dependencies = [ + "libc", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", +] + +[[package]] +name = "pyo3-build-config" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.45" @@ -1759,6 +1832,12 @@ dependencies = [ "libc", ] +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "tempfile" version = "3.27.0" diff --git a/Cargo.toml b/Cargo.toml index 7d15ef6..227b0d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] - +resolver = "3" members = [ - "ckan_geoconnex_bulk_runner", + "ckan_geoconnex_bulk_runner", + "ckan_geoconnex_bulk_runner_py", ] diff --git a/README.md b/README.md index 0384976..bef8c8b 100644 --- a/README.md +++ b/README.md @@ -13,29 +13,29 @@ This runner is expected to be implemented for a water data hub with the relevant ## Installation and setup ```bash -cargo run --release +cargo run -p ckan_geoconnex_bulk_runner --release ``` To ignore standard error output and only show valid output: ```bash -cargo run --release 2>/dev/null +cargo run -p ckan_geoconnex_bulk_runner --release 2>/dev/null ``` ## Run tests ```bash -cargo test +cargo test -p ckan_geoconnex_bulk_runner ``` To include print statements in test output, run: ```bash -cargo test -- --nocapture +cargo test -p ckan_geoconnex_bulk_runner -- --nocapture ``` If you have the local dump files setup available you can run those tests with: ```bash -cargo test -F local -- --nocapture +cargo test -p ckan_geoconnex_bulk_runner -F local -- --nocapture ``` diff --git a/ckan_geoconnex_bulk_runner_py/.github/workflows/CI.yml b/ckan_geoconnex_bulk_runner_py/.github/workflows/CI.yml new file mode 100644 index 0000000..d01c513 --- /dev/null +++ b/ckan_geoconnex_bulk_runner_py/.github/workflows/CI.yml @@ -0,0 +1,73 @@ +# This file is autogenerated by maturin v1.13.3 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + # TODO: Enable when implementation is ready + # push: + # branches: + # - main + # - master + # tags: + # - '*' + # pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: "3.x" + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v6 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + needs: [linux] + permissions: + # Use to sign the release artifacts + id-token: write + # Used to upload release artifacts + contents: write + # Used to generate artifact attestation + attestations: write + steps: + - uses: actions/download-artifact@v7 + - name: Generate artifact attestation + uses: actions/attest@v4 + with: + subject-path: 'wheels-*/*' + - name: Install uv + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: astral-sh/setup-uv@v7 + - name: Publish to PyPI + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: uv publish 'wheels-*/*' + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} diff --git a/ckan_geoconnex_bulk_runner_py/.gitignore b/ckan_geoconnex_bulk_runner_py/.gitignore new file mode 100644 index 0000000..c8f0442 --- /dev/null +++ b/ckan_geoconnex_bulk_runner_py/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version diff --git a/ckan_geoconnex_bulk_runner_py/Cargo.toml b/ckan_geoconnex_bulk_runner_py/Cargo.toml new file mode 100644 index 0000000..a9d7aef --- /dev/null +++ b/ckan_geoconnex_bulk_runner_py/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ckan_geoconnex_bulk_runner_py" +version = "0.1.0" +edition = "2024" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib"] +name = "ckan_geoconnex_bulk_runner_py" + +[dependencies] +ckan_geoconnex_bulk_runner = { path = "../ckan_geoconnex_bulk_runner" } +pyo3 = "0.28.3" +serde_json = "1.0.149" diff --git a/ckan_geoconnex_bulk_runner_py/pyproject.toml b/ckan_geoconnex_bulk_runner_py/pyproject.toml new file mode 100644 index 0000000..c6b6bfb --- /dev/null +++ b/ckan_geoconnex_bulk_runner_py/pyproject.toml @@ -0,0 +1,13 @@ +[build-system] +requires = ["maturin>=1.13,<2.0"] +build-backend = "maturin" + +[project] +name = "ckan_geoconnex_bulk_runner_py" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] diff --git a/ckan_geoconnex_bulk_runner_py/src/lib.rs b/ckan_geoconnex_bulk_runner_py/src/lib.rs new file mode 100644 index 0000000..f816ca2 --- /dev/null +++ b/ckan_geoconnex_bulk_runner_py/src/lib.rs @@ -0,0 +1,31 @@ +use pyo3::prelude::*; + +/// Python functions for Geoconnex integration that can be used in CKAN extensions. +/// Based on . +#[pymodule] +mod ckan_geoconnex_bulk_runner_py { + use pyo3::{exceptions::PyException, prelude::*}; + + #[pyfunction] + /// Construct Geoconnex-compatible JSON-LD as a string from dataset metadata. + /// + /// Input: Dataset metadata (output of /package_show for a CKAN dataset) as a string. + /// Output: Constructed Geoconnex-compatible JSON-LD as a string. + fn construct_dataset_jsonld_from_metadata(dataset_metadata: String) -> PyResult { + match serde_json::to_value(dataset_metadata) { + Ok(dataset_json) => { + match ckan_geoconnex_bulk_runner::jsonld::construct_dataset_jsonld_from_metadata( + dataset_json, + ) { + Ok(jsonld) => serde_json::to_string(&jsonld).map_err(|e| { + PyException::new_err(format!( + "Error when converting JSON-LD to string: {e}" + )) + }), + Err(e) => Err(PyException::new_err(e.to_string())), + } + } + Err(e) => Err(PyException::new_err(e.to_string())), + } + } +}