Contributing to NeuroLang#
Thank you for considering a contribution! This page explains how to set up a development environment and submit changes.
Prerequisites#
Python ≥ 3.12
git
uv (fast Python package manager) — install with:
curl -LsSf https://astral.sh/uv/install.sh | sh
Or on Windows (PowerShell):
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Development Install#
git clone https://github.com/NeuroLang/NeuroLang.git
cd NeuroLang
uv sync --all-packages --all-extras --no-extra embeddings
This installs NeuroLang’s four packages in editable mode, in the
environment .venv of the clone, together with all development
(pytest, pytest-cov) and documentation (Sphinx) dependencies. To
install them in another environment, uv pip install -e ".[apps,dev,doc]"
does the same from the root of the clone, whose pyproject.toml is a
project, never published, depending on the four.
The Packages#
The repository is a uv workspace of four packages, each of which imports only the ones before it:
|
the expression engine: expressions, walkers, types, logic, configuration |
|
relational algebra and its sets, Datalog, probabilistic Datalog |
|
the languages and frontends, brain regions |
|
the command line, its interactive mode, the query assistant, the engines |
.importlinter states these layers, which CI checks with
uv run lint-imports. A module put in the wrong package, or importing
one above it, fails that check. The modules keep their former names under
neurolang: neurolang.datalog is nl_core.datalog, and
neurolang.utils.cli is neurolang_apps.utils.cli. Code inside the
packages imports the new names.
The tests of a package are in it, beside the modules they test, and use only that package and the ones before it. CI runs the tests of the packages a pull request changes and of those built on them.
Running the Tests#
uv run pytest packages/
or those of one package, e.g. uv run pytest packages/nl_core. To run
with coverage:
uv run pytest --cov=nl_expr --cov=nl_core --cov=neurolang \
--cov=neurolang_apps packages/
The test suite should pass fully before you submit a pull request.
Building the Documentation#
make -C doc html
This will build the HTML docs into doc/_build/html/. Open
doc/_build/html/index.html in your browser to preview.
Note
The documentation build executes only the gallery examples whose file
name matches plot_load_destrieux (filename_pattern in
doc/conf.py); the other examples are rendered without being run.
Running them requires the neuroimaging packages (nibabel, nilearn) and
downloads data. To skip running the gallery entirely during a quick doc
build, disable plot_gallery:
make -C doc html SPHINXOPTS="-D plot_gallery=0"
Running the REPL in the Browser Locally#
The REPL in the browser runs NeuroLang on Pyodide, from a static page. To run that of your checkout on your machine:
uv run --extra apps neurolang-repl-serve
The first run builds the page’s bundle (NeuroLang’s wheels and the data of
the neurosynth engine, a few minutes) in
~/.cache/neurolang/repl-site; later runs serve it at once, and
--rebuild builds it again from the checkout. The page opens at
http://localhost:8766/_static/repl/index.html; the browser loads Pyodide
from its CDN. --host 0.0.0.0 serves the other machines of the network,
--port changes the port, --no-browser does not open the page, and
--help lists the options. The command is that of the workspace’s root
project (tools/serve_repl).
Submitting a Pull Request#
Fork the repository on GitHub.
Create a feature branch:
git checkout -b my-featureMake your changes.
Ensure all tests pass:
uv run pytest packages/Add a changelog entry under Unreleased in
doc/changelog.rst.Update docstrings for any changed public API.
Push your branch and open a pull request against
master.
PR checklist:
[ ] Tests pass (
uv run pytest packages/)[ ] New code has docstrings (NumPy format)
[ ] Changelog entry added to
doc/changelog.rst[ ] No new Sphinx build warnings (
make -C doc html)
Code Style#
NeuroLang follows PEP 8 with
NumPy-style docstrings. Run flake8 before committing:
uvx flake8 packages/
Adding a New Dataset Engine#
The neurolang-query CLI uses a declarative engine registry to manage
dataset backends. To add a new engine:
Write a YAML entry in
packages/neurolang_apps/neurolang_apps/utils/engines/engines.yaml:my_dataset: description: "Short description of the dataset" requires_mni_mask: false python_init: "neurolang_apps.utils.engines.my_dataset.init" datalog_init: | derived_pred(X) :- base_pred(X) relations: base_pred: "my_dataset/base_data.csv" predicates: my_predicate: arity: 2 columns: [name, value] description: "What this predicate represents"
The optional fields
datalog_init(inline Datalog rules) andrelations(CSV/TSV files loaded as predicates) run after the Python init. See Concepts for details.If your engine needs custom Python logic (data downloads, non-trivial processing), create the init module at
packages/neurolang_apps/neurolang_apps/utils/engines/my_dataset/init.pythat defines:from pathlib import Path import nibabel as nib from neurolang.frontend import NeurolangPDL def init_engine( nl: NeurolangPDL, mask: nib.Nifti1Image | None, data_dir: Path ) -> None: """Register symbols and load data into *nl*.""" # Register predicates with nl.add_tuple_set, etc.
If your engine needs the common neuroimaging symbols (
agg_count,agg_create_region,agg_create_region_overlay,principal_direction,region_union), setuse_base_symbols: truein the YAML entry instead of callinginit_base_engine()from Python:my_dataset: description: "..." requires_mni_mask: true use_base_symbols: true builtins: [exp, log, startswith] ...
This replaces the need for a Python init script when your engine only needs the standard neuroimaging symbols plus declarative YAML fields (
atlases,relations,datalog_init, etc.). See thedestrieuxengine entry for a complete example of a YAML-only engine.For engines that require custom data processing (like NeuroSynth’s peak coordinate conversion), you still write a Python init module. In that case, import and call
init_base_engineif you need the base symbols:from neurolang.utils.engines.base import init_base_engine def init_engine(nl, mask, data_dir): init_base_engine(nl, mask) # ... custom logic ...
If using
use_base_symbols: truein the YAML, do not also callinit_base_enginefrom Python — the registry handles it for you.Add unit tests in
packages/neurolang_apps/neurolang_apps/utils/tests/test_cli.py, specifically in theTestEngineRegistryclass to verify that your engine is discoverable and its config loads correctly.Run
uv run pytest packages/neurolang_apps/neurolang_apps/utils/tests/test_cli.pyto confirm everything passes.
Reporting Bugs#
Open an issue on the GitHub issue tracker. Include:
NeuroLang version (
python -c "import neurolang; print(neurolang.__version__)"if available)Python version
Operating system
Minimal reproducible example