Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""Run quick blocking checks before Git sends a push."""

import subprocess
import sys
from pathlib import Path


REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
DOCUMENTATION_SMOKE_TESTS = (
"tests/docs/test_publication.py",
"tests/docs/test_user_content.py",
)
WRAPPER_SMOKE_TEST = (
"tests/fortran/building_shared_library/end_to_end/test_source_build_modes.py::"
"test_fortran_wrapper_default_module_name_does_not_collide_with_root_function"
)
REQUIRED_TESTS = ("tests/tools", "tests/workflows")


def _available_main_ref(remote_name: str) -> str:
"""Return the best local ref for the remote's pre-push main branch."""
candidates = (f"{remote_name}/main", "origin/main", "main")
for candidate in dict.fromkeys(candidates):
result = subprocess.run( # noqa: S603
["git", "rev-parse", "--verify", "--quiet", candidate],
cwd=REPOSITORY_ROOT,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
if result.returncode == 0:
return candidate
return "main"


def _required_commands(base_ref: str) -> tuple[tuple[str, tuple[str, ...]], ...]:
"""Return the ordered, fail-fast pre-push command set."""
return (
(
"Static-analysis tool versions",
(sys.executable, "tools/check_static_analysis_versions.py"),
),
("Ruff lint", (sys.executable, "-m", "ruff", "check", ".")),
(
"Ruff format",
(sys.executable, "-m", "ruff", "format", "--check", "."),
),
(
"Codegen complexity",
(sys.executable, "tools/check_codegen_complexity.py"),
),
(
"Bandit",
(
sys.executable,
"-m",
"bandit",
"-q",
"-c",
"pyproject.toml",
"-r",
"prik",
"--severity-level",
"medium",
"--confidence-level",
"medium",
),
),
("Vulture", (sys.executable, "-m", "vulture")),
(
f"Radon policy against {base_ref}",
(
sys.executable,
"tools/check_radon_policy.py",
"--base-ref",
base_ref,
),
),
(
"Documentation smoke",
(
sys.executable,
"-m",
"pytest",
"-q",
*DOCUMENTATION_SMOKE_TESTS,
),
),
(
"Compiled scalar-wrapper smoke",
(sys.executable, "-m", "pytest", "-q", WRAPPER_SMOKE_TEST),
),
(
"Maintainer and workflow tests",
(sys.executable, "-m", "pytest", "-q", *REQUIRED_TESTS),
),
)


def main() -> int:
"""Run each required check and return the first failing status."""
remote_name = sys.argv[1] if len(sys.argv) > 1 else "origin"
base_ref = _available_main_ref(remote_name)
for label, command in _required_commands(base_ref):
print(f"\n==> {label}", flush=True)
completed = subprocess.run( # noqa: S603
command,
cwd=REPOSITORY_ROOT,
check=False,
)
if completed.returncode != 0:
print(f"Pre-push checks stopped at: {label}", file=sys.stderr)
return completed.returncode
print("\nAll pre-push checks passed.", flush=True)
return 0


if __name__ == "__main__":
raise SystemExit(main())
5 changes: 3 additions & 2 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ jobs:
-o junit_family=legacy \
--junitxml="$RUNNER_TEMP/pytest-suite-results.xml" \
-m "not real_library and not toolchain_smoke" \
tests/architecture \
tests/c \
tests/docs \
tests/fortran \
tests/shared
tests/tools \
tests/workflows
- name: Summarize failed pytest nodes
if: failure()
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
path: .

- name: Run documentation tests
run: python -m pytest -q tests/shared/docs
run: python -m pytest -q tests/docs

- name: Build reviewed documentation
run: python -m mkdocs build --strict
Expand Down
17 changes: 10 additions & 7 deletions .github/workflows/merge-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,11 @@ jobs:
-o junit_family=legacy \
--junitxml="$RUNNER_TEMP/pytest-suite-results.xml" \
-m "not real_library and not toolchain_smoke" \
tests/architecture \
tests/c \
tests/docs \
tests/fortran \
tests/shared
tests/tools \
tests/workflows
- name: Run tests with project coverage
if: ${{ matrix.coverage }}
shell: bash
Expand All @@ -322,10 +323,11 @@ jobs:
-o junit_family=legacy \
--junitxml="$RUNNER_TEMP/pytest-suite-results.xml" \
-m "not real_library and not toolchain_smoke" \
tests/architecture \
tests/c \
tests/docs \
tests/fortran \
tests/shared
tests/tools \
tests/workflows
- name: Summarize failed pytest nodes
if: failure()
shell: bash
Expand Down Expand Up @@ -422,10 +424,11 @@ jobs:
-o junit_family=legacy \
--junitxml="$RUNNER_TEMP/pytest-macos-suite-results.xml" \
-m "not real_library and not toolchain_smoke" \
tests/architecture \
tests/c \
tests/docs \
tests/fortran \
tests/shared
tests/tools \
tests/workflows
- name: Summarize failed pytest nodes
if: failure()
shell: bash
Expand Down Expand Up @@ -646,7 +649,7 @@ jobs:
name: performance-snapshot
path: .
- name: Run documentation tests
run: python -m pytest -q tests/shared/docs
run: python -m pytest -q tests/docs
- name: Build reviewed documentation
run: python -m mkdocs build --strict

Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ jobs:
-o junit_family=legacy \
--junitxml="$RUNNER_TEMP/pytest-suite-results.xml" \
-m "not real_library and not toolchain_smoke" \
tests/architecture \
tests/c \
tests/docs \
tests/fortran \
tests/shared
tests/tools \
tests/workflows
- name: Summarize failed pytest nodes
if: failure()
shell: bash
Expand Down Expand Up @@ -144,10 +145,11 @@ jobs:
-o junit_family=legacy \
--junitxml="$RUNNER_TEMP/pytest-macos-suite-results.xml" \
-m "not real_library and not toolchain_smoke" \
tests/architecture \
tests/c \
tests/docs \
tests/fortran \
tests/shared
tests/tools \
tests/workflows
- name: Summarize failed pytest nodes
if: failure()
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ For documentation-only changes that do not modify executable Python code,
runtime behavior, build configuration, or test logic, do not run the complete
static-analysis suite by default. Run the focused documentation checks and
whitespace check instead:
- `python3 -m pytest -q tests/shared/docs/test_examples.py tests/shared/docs/test_structure.py`
- `python3 -m pytest -q tests/docs`
- `git diff --check`
Run the complete static-analysis suite when code, tests, build behavior, or
tooling configuration changes, or when explicitly requested for pre-merge or
Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ release tags add a leading `v` to the package version.

### Changed

- Moved documentation and maintainer-tool tests to `tests/docs/` and
`tests/tools/`, removed the generic `tests/shared/` bucket, and mirrored
internal tests by production package with narrower support helpers; removed
recursive layout-policing tests that froze maintainer organization, retaining
exceptional release safety under `tests/workflows/`. The maintainer-tool and
workflow-safety suites, blocking static analysis, and focused documentation
smoke checks now also run through the repository's tracked pre-push hook,
together with one compiled scalar-wrapper smoke test, for earlier local
feedback while remaining enforced by GitHub Actions.
- Simplified the documented DGESV validation and the LAPACK test suite to use
explicit NumPy Fortran-order copies, with documented numerical-test helper
conventions.
Expand All @@ -44,7 +53,7 @@ release tags add a leading `v` to the package version.
- Refreshed the README and website around the canonical
**PRIK — Python Runtime Interop Kit** identity, with a concise FAQ, a fair
PRIK-versus-f2py guide, clearer array guidance, and searchable real-library
examples.
examples, including a four-library capability and validation summary.
- Hardened preprocessing, compiler-derived type probes, semantic policy
completion, and multi-source build reporting so unsupported contracts fail
earlier with clearer diagnostics.
Expand Down
53 changes: 35 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and Pythonic APIs.**
PRIK generates native Python bindings from Fortran projects, producing
importable extensions and editable `.pyi` contracts for Pythonic APIs.

[![Tests](https://github.com/PyNumLab/prik/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/PyNumLab/prik/actions/workflows/tests.yml)
[![Static Analysis](https://github.com/PyNumLab/prik/actions/workflows/static-analysis.yml/badge.svg?branch=main)](https://github.com/PyNumLab/prik/actions/workflows/static-analysis.yml)
[![codecov](https://codecov.io/gh/PyNumLab/prik/graph/badge.svg?token=QZRRCS5YO6)](https://codecov.io/gh/PyNumLab/prik)

It preserves modules, derived types, arrays, callbacks, and native behavior so
you can shape the resulting API without writing low-level binding code.

Expand All @@ -24,23 +28,12 @@ semantic IR, emits editable `.pyi` interfaces, and reports unsupported or
incomplete contracts before code generation.
PRIK_C_DOCS_END -->

[![Tests](https://github.com/PyNumLab/prik/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/PyNumLab/prik/actions/workflows/tests.yml)
[![Static Analysis](https://github.com/PyNumLab/prik/actions/workflows/static-analysis.yml/badge.svg?branch=main)](https://github.com/PyNumLab/prik/actions/workflows/static-analysis.yml)
[![codecov](https://codecov.io/gh/PyNumLab/prik/graph/badge.svg?token=QZRRCS5YO6)](https://codecov.io/gh/PyNumLab/prik)

[Read the documentation](https://pynumlab.github.io/prik/) for installation,
the user guide, examples, and reference material.

For a complete real-library example, see the
[155-routine BLAS correctness project](examples/blas/README.md), which builds
the same Reference BLAS sources with PRIK and f2py and checks both against
independent numerical expectations. The
[LAPACK correctness project](examples/lapack/README.md) wraps the complete
Reference LAPACK implementation corpus once and validates the reviewed 127
SciPy-backed double-precision routines in the dedicated CI lane.

## Contents

- [Proven on real libraries](#proven-on-real-libraries)
- [See it in action](#see-it-in-action)
- [Key Features](#key-features)
- [Performance](#performance)
Expand All @@ -52,6 +45,22 @@ SciPy-backed double-precision routines in the dedicated CI lane.
- [License](#license)
- [Documentation](#documentation)

## Proven on real libraries

The maintained projects build real numerical libraries with PRIK and validate
their Python behavior, not just whether the generated wrapper compiles.

| Project | Validated surface | Capabilities demonstrated |
| --- | --- | --- |
| [BLAS](examples/blas/README.md) | All 155 discovered routines | Scalar, vector, and matrix operations; increments and leading dimensions; in-place updates; independent expectations and f2py comparisons |
| [LAPACK](examples/lapack/README.md) | Complete implementation corpus with 127 reviewed double-precision routines | Linear solves, factorizations, eigenproblems, singular values, work arrays, and large multi-source linking |
| [FFTPACK](examples/fftpack/README.md) | All 31 public procedures | Fourier, cosine, and sine transforms; low-level workspaces; in-place arrays; allocatable results; NumPy and SciPy oracles |
| [MINPACK](examples/minpack/README.md) | All 22 public procedures | Python callbacks; nonlinear and least-squares solvers; Jacobian and workspace writeback; immutable module constants |

Together they exercise arrays, callbacks, workspaces, in-place mutation,
allocatable results, module constants, and multi-file linking. The dedicated
Real Libraries CI lane builds and tests all four projects.

The complete example below builds with one command:

```bash
Expand Down Expand Up @@ -114,12 +123,20 @@ shows the available edits.

## Key Features

- Fortran modules exposed as Python namespaces and derived types as classes
- NumPy arrays with explicit dtype, shape, and layout checks
- Allocatable and pointer arrays with explicit lifetime operations
- Immediate Python callbacks and overloaded interfaces
- Editable `.pyi` contracts and readable generated docstrings
- Early, clear errors when a boundary cannot be wrapped
- **Native APIs that feel like Python.** Fortran modules become Python
namespaces, while derived types become classes with fields and methods.
- **First-class NumPy array interop.** Pass ordinary NumPy arrays to native
procedures, including multidimensional and in-place data, with generated
dtype, shape, layout, and mutability handling at the language boundary.
- **Managed access to native memory.** Expose allocatable and pointer arrays
without hiding their ownership, lifetime, allocation, or release operations.
- **Python callbacks and native overloads.** Pass Python callables into Fortran
and expose generic interfaces as familiar Python overloads.
- **Generated APIs you can reshape.** Edit the generated `.pyi` contract to
rename, hide, reorganize, or overload the public interface, backed by readable
generated docstrings.
- **Unsupported contracts fail before the build.** PRIK identifies the exact
boundary and reason before attempting code generation or compilation.

## Performance

Expand Down
Loading
Loading