From 24170aeddc29865ace9db5a807e5df492184793e Mon Sep 17 00:00:00 2001 From: behnazh-w Date: Mon, 27 Jul 2026 09:40:42 +1000 Subject: [PATCH] build: improve the packaging metadata and add PR workflow Signed-off-by: behnazh-w --- .../{pypi-publish.yml => pypi-publish.yaml} | 0 .github/workflows/test.yaml | 37 +++++++++++++++++++ .gitignore | 26 +++++++++++++ README.md | 16 +++++++- pyproject.toml | 11 +++++- src/daleq4py/inference.py | 5 ++- tests/test_inference.py | 29 +++++++++++++-- 7 files changed, 118 insertions(+), 6 deletions(-) rename .github/workflows/{pypi-publish.yml => pypi-publish.yaml} (100%) create mode 100644 .github/workflows/test.yaml create mode 100644 .gitignore diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yaml similarity index 100% rename from .github/workflows/pypi-publish.yml rename to .github/workflows/pypi-publish.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..9cef2f0 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,37 @@ +name: Test + +# Run the test suite for every pull request before it is merged. +on: + pull_request: + +# The workflow only needs to read source code. +permissions: + contents: read + +jobs: + # Build an isolated, lockfile-pinned environment and execute the test suite. + test: + name: Test with Python 3.12 + runs-on: ubuntu-latest + steps: + # Make the pull request's exact source revision available to subsequent steps. + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + # Use the minimum Python version supported by the published package. + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.12" + + # Install Poetry independently of the project environment to manage dependencies. + - name: Install Poetry + run: pipx install poetry + + # Recreate the environment from poetry.lock and remove undeclared packages. + - name: Install locked dependencies + run: poetry install --sync + + # Run all tests; tests requiring an unavailable external Soufflé binary skip themselves. + - name: Run tests + run: poetry run pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a25a6a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Python bytecode and tooling caches +__pycache__/ +*.py[cod] +*.so +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ + +# Virtual environments and local Python-version selectors +.venv/ +venv/ +env/ +.python-version + +# Packaging and coverage output +build/ +dist/ +*.egg-info/ +.coverage +coverage.xml +htmlcov/ + +# Generated analysis and experiment output +.souffle/ +.souffle-test/ +sample-data-results/ diff --git a/README.md b/README.md index 6327e36..bfe886f 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,19 @@ A command line tool to establish the equivalence of two alternatively built Python wheels. +> **External dependency:** Installing `daleq4py` from PyPI installs only the +> Python package. The separately installed [Soufflé](https://souffle-lang.github.io/) +> executable is required for inference-based commands; it is not bundled in +> the `py3-none-any` wheel. + ## Requirements - Python >= 3.12 - [Poetry](https://python-poetry.org/) for dependency management and builds +- [Soufflé](https://souffle-lang.github.io/) available on `PATH` for + inference-based commands: `inference`, `compare-wheels`, and + `run-experiments`. Soufflé is a separately installed system executable, not + a Python package dependency. ## Building @@ -25,6 +34,10 @@ poetry build poetry run pytest ``` +## Continuous integration + +Every pull request runs the [test workflow](.github/workflows/test.yaml) on GitHub Actions. It creates a clean Python environment from `poetry.lock`, installs the package and its development dependencies, then runs the complete pytest suite. Tests that require the separately installed Soufflé executable skip when it is not available on the runner. + ## Running ### daleq4py @@ -80,7 +93,8 @@ poetry run inference EDB_DIR | `EDB_DIR` | path to the `edb/` directory produced by `extract-edb` | | `-v`, `--verbose` | enable verbose (debug) logging | -The `souffle` binary must be on `PATH`. +Soufflé must be installed separately and its `souffle` binary must be on +`PATH`. ### project-idb diff --git a/pyproject.toml b/pyproject.toml index cddee74..795d83a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,25 @@ [project] name = "daleq4py" version = "0.1.0" -description = "CLI tool to establish the equivalence of alternatively built python packages." +description = "CLI for comparing alternatively built Python packages; Soufflé is required for inference-based commands." authors = [{ name = "Jens Dietrich", email = "jens.dietrich@gmail.com" }] readme = "README.md" license = "UPL-1.0" license-files = ["LICENSE.md"] requires-python = ">=3.12" dependencies = ["packaging>=24", "lark>=1.1"] +keywords = ["python", "wheel", "reproducible-builds", "supply-chain-security"] +classifiers = [ + "Environment :: Console", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Quality Assurance", +] [project.urls] +Homepage = "https://github.com/binaryeq/daleq4py" Repository = "https://github.com/binaryeq/daleq4py" +Issues = "https://github.com/binaryeq/daleq4py/issues" [project.scripts] daleq4py = "daleq4py.cli:main" diff --git a/src/daleq4py/inference.py b/src/daleq4py/inference.py index 1050e43..6918cd4 100644 --- a/src/daleq4py/inference.py +++ b/src/daleq4py/inference.py @@ -134,7 +134,10 @@ def _rel(p: Path) -> str: if shutil.which(SOUFFLE_EXECUTABLE) is None: raise InferenceError( - f"{SOUFFLE_EXECUTABLE!r} executable not found on PATH" + "Soufflé is required for inference but its 'souffle' executable " + "was not found on PATH. Install Soufflé " + "(https://souffle-lang.github.io/) and ensure 'souffle' is " + "available on PATH." ) logger.info("running souffle on %s (cwd=%s)", MERGED_FILENAME, session_dir) diff --git a/tests/test_inference.py b/tests/test_inference.py index a5bc583..ece93fc 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -1,6 +1,12 @@ # Author: Jens Dietrich (https://github.com/jensdietrich/), co-authored by claude code -"""Tests for souffle inference on extracted EDBs.""" +"""Tests for Soufflé inference on extracted EDBs. + +Input-validation tests and the missing-executable diagnostic are independent +of Soufflé and must pass in every environment. End-to-end tests exercise the +generated Datalog rules with the real ``souffle`` executable; they are skipped +when it is not installed, so contributors can run the Python-only test suite. +""" import shutil @@ -14,6 +20,8 @@ run_inference, ) +# End-to-end tests need the separately installed system executable. Keep the +# marker at module scope so their external prerequisite is explicit and uniform. requires_souffle = pytest.mark.skipif( shutil.which("souffle") is None, reason="souffle executable not on PATH", @@ -25,7 +33,7 @@ def _read_rows(tsv_path): return [tuple(line.split("\t")) for line in text.splitlines()] -# --- input validation (no souffle invocation needed) --- +# --- Python-only input validation (no souffle invocation needed) --- def test_run_inference_rejects_missing_dir(tmp_path): @@ -47,7 +55,22 @@ def test_run_inference_rejects_edb_dir_without_facts(tmp_path): run_inference(edb) -# --- end-to-end (require souffle on PATH) --- +def test_run_inference_explains_how_to_install_missing_souffle( + edb_base_dir, monkeypatch +): + """Missing Soufflé must produce an actionable installation/PATH message.""" + content_path = extract_edb(COLORLOG_PYPI, base_dir=edb_base_dir) + # Simulate a machine without Soufflé regardless of the developer's PATH. + monkeypatch.setattr("daleq4py.inference.shutil.which", lambda _: None) + + with pytest.raises( + InferenceError, + match="Install Soufflé .* ensure 'souffle' is available on PATH", + ): + run_inference(content_path.parent) + + +# --- End-to-end execution (requires Soufflé on PATH) --- @requires_souffle