Contributing to Formulate
Thank you for your interest in contributing to Formulate! This guide will help you get started with contributing to the project.
Setting Up Your Development Environment
Fork the Repository
Start by forking the Formulate repository on GitHub.
Clone Your Fork
git clone https://github.com/YOUR-USERNAME/formulate.git cd formulate
Set Up a Virtual Environment
It’s recommended to use a virtual environment for development (e.g.
venv,conda,uv, etc.):python -m venv .venv source .venv/bin/activate # .venv\Scripts\activate on Windows
Install Development Dependencies
pip install -e ".[dev]"
The
devextra pulls in the test and docs requirements plusprek..[test]on its own is enough to run the whole suite;.[docs]on its own is enough to build the documentation.Set Up Pre-commit Hooks
Formulate uses pre-commit hooks to ensure code quality. They are run with prek, a faster drop-in replacement for
pre-committhat reads the same.pre-commit-config.yaml. It is installed by thedevextra above, so you only need to install the hooks:prek installTo run all the hooks against every file without making a commit:
prek run --all-files
Development Workflow
Create a Branch
Create a new branch for your feature or bugfix:
git checkout -b feature-or-bugfix-name
Make Your Changes
Implement your feature or fix the bug. Be sure to:
Follow the coding style of the project
Add tests for your changes
Update documentation if necessary
Run Tests
Make sure all tests pass:
pytest
A few useful variations:
pytest tests/test_root.py # one file pytest -k tmath_min # one test, by name pytest --cov=formulate --cov-branch # with coverage
Note that
tests/test_constants.pyevaluates expressions with the real engines and skips the ROOT half unless ROOT is importable, which CI only arranges on one job. Everything else runs everywhere.Commit Your Changes
Commit your changes with a descriptive commit message:
git add . git commit -m "Add feature X" or "Fix bug Y"
Push Your Changes
Push your changes to your fork:
git push origin feature-or-bugfix-name
Create a Pull Request
Go to the Formulate repository and create a pull request from your branch.
Using nox
nox runs each task in its own isolated
environment, which is what CI does and is the quickest way to reproduce a CI
failure locally. It needs no setup beyond pipx install nox:
nox # the default: lint, pylint and tests
nox -s tests # just the tests
nox -s coverage # tests, with coverage measured
nox -s lint # all the pre-commit hooks
nox -s docs # build the documentation
nox -s docs -- --serve # build it and serve it at localhost:8000
Coding Guidelines
Code Style
Formulate follows the PEP 8 style guide, enforced by
ruff. The hooks installed above take care of the formatting for you;mypyalso runs oversrcin strict mode, so new code needs to be fully typed.Documentation
Document all public functions, classes, and methods using docstrings. They are what the API Reference pages are built from.
Update the narrative documentation when you add or change a feature. In particular,
docs/guide/expressions.rstlists every supported function and constant, anddocs/guide/issues.rstis the reference for the places where the languages disagree — both should stay in step with the code.Examples in the docs are executed when the docs are built, so an example that has gone stale will show up as a failed build rather than as wrong output on the website.
Testing
Write tests for all new features and bug fixes
Coverage is enforced at 100% for both the project and the diff, so a new branch needs a test that reaches it. Genuinely unreachable code is marked
# pragma: no cover; if a branch cannot be reached, consider whether it should exist at all.Warnings are errors in the test suite, so anything that starts warning will fail
Commit Messages
Write clear, concise commit messages
Start with a short summary line (50 chars or less)
Optionally, follow with a blank line and a more detailed explanation
Adding a function or a constant
This is the most common kind of contribution, and it is mostly table editing.
In src/formulate/identifiers.py:
Add the canonical name to
FUNCTIONSorCONSTANTS. This name is internal — it is what appears in the AST — so it should not be any one language’s spelling. The NumPy-style name is the usual choice.Add an entry to each backend’s map (
ROOT_FUNCTIONS,NUMEXPR_FUNCTIONS,PYTHON_FUNCTIONS, and the*_CONSTANTSequivalents) for the languages that support it. Leaving it out of a map is how “not supported here” is expressed; the conversion will then raise a clearValueErrorrather than emitting something wrong.Add any other spellings people might write to
FUNCTION_ALIASES,CONSTANTS_ALIASESorCONSTANTS_FUNCTION_ALIASES.
tests/test_identifiers.py drives every entry in these tables through the
parser and cross-checks the tables against each other, so a name that is
declared but not mapped — or mapped but not declared — fails loudly.
Finally, add the new name to the tables in docs/guide/expressions.rst,
which is where users look for what is supported.
Types of Contributions
There are many ways to contribute to Formulate:
Bug Reports
If you find a bug, please report it by creating an issue on GitHub. Include:
A clear description of the bug
Steps to reproduce the bug
Expected behavior
Actual behavior
Any relevant logs or error messages
Feature Requests
If you have an idea for a new feature, create an issue on GitHub describing:
What the feature would do
Why it would be useful
How it might be implemented
Documentation Improvements
Help improve the documentation by:
Fixing typos or errors
Clarifying explanations
Adding examples
Translating documentation
Code Contributions
Contribute code by:
Fixing bugs
Implementing new features
Improving performance
Refactoring code
Reviewing Pull Requests
Help review pull requests by:
Testing the changes
Reviewing the code
Providing constructive feedback