Contributing
Thanks for your interest in contributing to rust-python-template.
Coding agents should read AGENTS.md in the repository root, which is the
machine-facing subset of this guide.
Repository layout
Cargo.toml workspace root: shared metadata, lints, deps
pyproject.toml maturin build backend + uv dev deps
rust-toolchain.toml stable channel, pinned
justfile single task surface
mkdocs.yml documentation site
deny.toml cargo-deny configuration
crates/
├── rust-python-template-core/ pure-Rust core; #![forbid(unsafe_code)]
└── rust-python-template-ffi/ PyO3 bindings, publish = false
python/rust_python_template/ Python wrapper + .pyi type stubs
tests/
├── rust/ cargo integration tests against -core
└── python/ pytest against the FFI boundary
tools/docs/ generators for the generated reference pages
docs/adr/ architecture decision records (unpublished)
docs/engineering/ opt-in extension catalogue (unpublished)
The pure-Rust core lives in crates/rust-python-template-core and is the
source of truth for the library's behavior. The FFI crate is a thin shell
around it that handles Python ↔ Rust conversion. Keep business logic out
of -ffi.
Prerequisites
- Rust (stable, pinned by
rust-toolchain.toml) viarustup. uvfor Python + venv.justfor the task surface.cargo-nextest(cargo install cargo-nextest --locked).cargo-deny(cargo install cargo-deny --locked).
Quickstart
just bootstrap # sync venv + build FFI extension in release mode
just check # lint + test + docs-test: the full gate
just fmt # auto-fix: cargo fmt + ruff format + ruff --fix
just audit # cargo-deny: advisories, licenses, bans, sources
just build # produce a release wheel in target/wheels/
just is the task surface; run just --list for everything. CI invokes the
same recipes, so a green just check locally means a green CI.
Day-to-day iteration: just develop (debug build of the FFI extension) is
faster than rebuilding in release mode. Re-run just develop after any
Rust change.
Decision records
Architecturally significant changes ship with an Architecture Decision Record
in docs/adr/. A record is required for anything that touches the public API
(Rust or Python), the crate boundaries, the build system, or the release
process. Bug fixes, behavior-preserving refactors, and dependency bumps do
not need one.
To create a record:
That copies docs/adr/template.md to docs/adr/NNNN-short-kebab-title.md.
Leave the number as NNNN — numbers are assigned on merge, not on draft,
so concurrent PRs don't collide. Ship the record in the same PR as the change
it describes; it starts at status proposed and becomes accepted when the
PR merges.
Moving a record from proposed to accepted is the architectural review:
open questions resolved, alternatives named and their rejections recorded, the
plan concrete enough that the implementer does not have to invent anything.
An accepted record is never edited. If circumstances change, write a new
record that supersedes it and set the old record's status line to
superseded by ADR-NNNN. That status edit is the only permitted modification.
Implementation PRs are linked under Links and references as they land; the
status does not change when the code ships.
Records are not published to this site — they are engineering history, not user documentation. The decision record index lists their numbers, titles, and statuses.
Documentation
Documentation follows Diátaxis per ADR-0003. Every page belongs to exactly one quadrant; decide which before you start writing:
| Quadrant | Directory | Purpose |
|---|---|---|
| Tutorial | docs/tutorials/ |
Learning-oriented. A guaranteed-to-succeed path. |
| How-to | docs/how-to/ |
Task-oriented. One page per task the reader arrives with. |
| Reference | docs/reference/ |
Information-oriented. Mostly generated. |
| Explanation | docs/explanation/ |
Understanding-oriented. Why, not how. |
Three rules that CI enforces:
- There are two reference surfaces. rustdoc on docs.rs documents the
-corecrate; this site documents the Python wrapper. Don't mirror one into the other. - Generated pages are generated.
reference/api.mdrenders docstrings via mkdocstrings;reference/adr-index.mdis written bytools/docs/. Change the docstring or the generator, never the page. Runjust docs-indexand commit the result — CI regenerates and fails on any diff. - Every fenced
pythonblock runs as a test. Tutorial pages carry state across blocks using thecontinuationfence option; how-to blocks stand alone. Usenotestonly for a block that genuinely cannot execute.
just docs # serve locally with auto-reload
just docs-test # run every fenced block and wrapper doctest
just docs-build # mkdocs build --strict
just docs-test imports the Python wrapper, which re-exports from the compiled
._core module. Run just develop first on a clean tree, or the import fails.
Pull-request checklist
Before opening a PR:
- [ ]
just checkpasses (lint, tests, and documentation examples). - [ ]
just auditis clean (or the failure is explained in the PR). - [ ]
just docs-indexleaves the working tree clean. - [ ] Public Rust items in
-corehave doc comments (missing_docsis a warn lint); public Python symbols have Google-style docstrings (ruff'sDrules). - [ ] If the change is architectural, the linked decision record is included.
License
Contributions are dual-licensed under Apache-2.0 and MIT at the contributor's option. By submitting a contribution you agree to these dual-license terms.