Thanks for considering a contribution. The most valuable additions are new research-grounded curves and well-documented transforms — both can be added without touching the rest of the codebase.
-
Create
src/curveforge/curves/my_curve.pywith three things:- A Pydantic
Paramsmodel describing the native knobs (useFieldwithdescription=,ge=,le=soinfoand CLI errors stay informative). - A
_render(params, freqs) -> NDArray[float64]function returning gains in dB at each frequency. - A module-level
SPEC: CurveSpec[MyParams]referencing all of the above plus a one-line description and a citation string.
Use
curveforge.dspfor analytical shapes (low-shelf, high-shelf, peaking, tilt, Butterworth high-pass) so the math stays consistent. - A Pydantic
-
Register it: add the import and SPEC to
src/curveforge/curves/__init__.pyin the_REGISTEREDtuple. -
Add a snapshot test in
tests/test_curves.py. The convention is: render default params on a small log grid, compare against a checked-in reference array. -
Cite your source in the module docstring (paper, page, or URL). The
infocommand exposes this to users.
-
Create
src/curveforge/transforms/my_transform.pywith a PydanticParamsmodel, an_apply(curve, params) -> Curvefunction, and aSPEC: TransformSpec[MyParams]. -
Register it in
src/curveforge/transforms/__init__.py. -
Add a unit test that applies the transform to a flat 0 dB curve and asserts a known shape.
If a curve has to be expressed as raw breakpoints (e.g. a vendor's
published target file that's not analytically described), ship it as a
data file alongside the package source and load it via
importlib.resources. Don't encode big arrays in the Python source.
The literal-size check (tools/check_literal_size.py, run in CI) fails
the build if any .py file under src/curveforge/ declares a list /
tuple / set / dict literal with more than 10 elements. Genuine in-code
reference constants (e.g. ISO 1/3-octave centers) can be exempted by
placing # curveforge: allow-literal on the line directly above the
literal.
We use ruff (lint + format) and mypy --strict. Pyright runs as a
secondary checker in standard mode. Hard caps:
- 50 lines per function (max per ruff config).
- Cyclomatic complexity 12, cognitive complexity 15.
- Avoid
Anyin production code where a concrete type fits.Anyis permitted at Pydantic / JSON-schema boundaries where raw user-supplied dicts are being coerced; each such use carries a# noqa: ANN401 — <reason>annotation explaining why. - No
# type: ignorewithout an inline explanation. - Public symbols (modules, classes, top-level functions) carry a docstring; private helpers can be undocumented if the name is self-explanatory.
- Large literal collections (list/tuple/set/dict with >10 elements)
in
src/curveforge/are rejected by the literal-size linter; move them to data files or add# curveforge: allow-literal.
pip install -e '.[dev]'
ruff check
ruff format --check
mypy src
pyright src
python tools/check_literal_size.py src/curveforge
pytestCI runs the same on Python 3.11, 3.12, 3.13 across Ubuntu and macOS.