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
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# CI template: runs the pre-commit hooks and the test suite on every push.
#
# The Python version below should match the one in containers/apptainer.def;
# change it in both places when the project moves on.

name: CI

on:
push:
branches: [main]
pull_request:

# A new push to a branch cancels any run still in progress for it.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
PYTHON_VERSION: "3.12"

jobs:
# Runs every hook in .pre-commit-config.yaml (mdformat, ruff, ty, ...) over
# the whole repo, not just the changed files.
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v8.3.2
with:
python-version: ${{ env.PYTHON_VERSION }}
enable-cache: true
- uses: actions/cache@v6
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
- run: uvx pre-commit run --all-files --show-diff-on-failure

pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v8.3.2
with:
python-version: ${{ env.PYTHON_VERSION }}
enable-cache: true
# --frozen fails if uv.lock is out of date with pyproject.toml.
- run: uv sync --frozen
- run: uv run pytest
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ __pycache__/
*$py.class

# User defined folders
docs/
data/
containers/
models/

# Data files.
*.csv
Expand Down
16 changes: 8 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: check-added-large-files
args: ["--maxkb=4096"]
Expand All @@ -9,14 +9,14 @@ repos:
- id: trailing-whitespace
files: \.(js|rb|md|py|sh|txt|yaml|yml|kidm)$

- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.30.0
- repo: local
hooks:
- id: markdownlint
files: \.md$
language_version: "13.14.0"
args:
- --fix
- id: mdformat
name: mdformat
entry: uvx --with mdformat-gfm --with mdformat-frontmatter mdformat --wrap 100 --number
language: system
types: [markdown]
pass_filenames: true

- repo: local
hooks:
Expand Down
47 changes: 47 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# High-level project description

> Describe your project here

## Research workflows

- Maintain a append-only `LOGBOOK.md` file for each iteration, with the following structure in
antichronological order:

```
## <Brief description of the iteration> - YYYY-MM-DD

<Brief summary of the hypotheses being tested>

<Description of the methods and implementation>

<Highlight of the key results>

<Next steps>
```

- Maintain a `RESULTS.md` that compiles the (comparable) results obtained so far. It should be an
up-to-date version of the key results found in `LOGBOOK.md`.

## Reporting

Reports are written in quarto. All plots should be generated with Python cells (not copied from
elsewhere), ideally from cached results to minimize the generation time. Reporting code should be
re-usable, so favor functions instead of throwaway code.

## Coding Style

- Use `uv` to run scripts
- Use `uv ruff check` to lint the code and `uvx ty check` to type check it.
- Always use `uv ruff format` to format the code according to our guidelines after you are done
editing a file.
- All functions must have type hints for their arguments and return values
- All functions should have docstings (numpy style), the docstring should be short and simple, avoid
notes sections and exemples sections in docstrings unless strictly necessary.
- Code should focus on simplicity and clarity, avoid scope creep, avoid implementing non-necessary
options.
- Polars should be prefered over pandas unless really necessary (e.g., geopandas).
- Project configurations and data paths are defined in `src/package_name/data/config.yaml` and
`src/package_name/config.py`, data folder paths should not be hardcoded in scripts, rather they
should be loaded from configurations.
- Tests live in `tests/`, are run with `uv run pytest`, and follow the same style rules as the rest
of the code (type hints, short numpy-style docstrings).
113 changes: 113 additions & 0 deletions GUIDELINES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Project template

This template should be used for every Python project in the lab. It uses:

- [`uv`](https://docs.astral.sh/uv/) for dependency management.
- [`ruff`](https://docs.astral.sh/ruff/) for code formatting.
- [`ty`](https://docs.astral.sh/ty/) for type checking.
- [`pre-commit`](https://pre-commit.com/) hooks for automated validation.
- [`pytest`](https://docs.pytest.org/) for testing.
- GitHub Actions to run the hooks and the tests on every push.

## Dependency management

We use [`uv`](https://docs.astral.sh/uv/) for dependency management. It is just as full-featured as
`poetry`, but _much faster_. Follow the instructions below to create a new project:

1. Update the name of the project in `pyproject.toml`. This is the distribution name, and it usually
matches the name of the repository (hyphens are fine here).

2. Rename the folder `src/package_name` to the name of your package, and update the import in
`tests/test_hello.py` to match. This is a Python module name, so it must use underscores rather
than hyphens (`my_project`, not `my-project`) — otherwise it cannot be imported.

3. Run `uv sync` from the root of the repo. This will create a virtual environment and install
needed development dependencies.

4. Add the dependencies you need (and run this same command every time you need a new package):

```sh
uv add polars lightgbm
```

5. Take a look at the `uv`'s [Getting started guide](https://docs.astral.sh/uv/getting-started/).

## Pre-commit hooks

Install the pre-commit hooks:

```sh
uvx pre-commit install
```

This will create a `.git/hooks/pre-commit` file that will run the pre-commit hooks every time you
commit. Upon the first commit, the hooks will be installed.

Some hooks output error message that require a manual change (e.g., linting errors). Other hooks
perform automated fixes. Either way, you need to re-run the commit command:

```sh
git commit -m "My message"
```

### Code formatting

Among the pre-commit hooks, you will find one that runs [`ruff`](https://docs.astral.sh/ruff/) on
every Python file. It is also warmly recommended that you set up `ruff` in your IDE (e.g., Visual
Studio Code, PyCharm).

### Typing

We recommend the use of [type hints](https://docs.python.org/3/library/typing.html) of your code.
One of the pre-commit hooks is [`ty`](https://docs.astral.sh/ty/), which will perform type checking
when hints are available. This reduces greatly the risk of bugs and the maintainability of the code.

## Tests

Tests live in `tests/` and are run with [`pytest`](https://docs.pytest.org/):

```sh
uv run pytest
```

Unlike the linters, `pytest` is a dev dependency in `pyproject.toml` rather than a `uvx` tool: it
has to import your package, so it needs the project environment. It is installed by `uv sync`.

`tests/test_hello.py` is a stub covering the example `hello` function. Populate `tests/` as follows:

1. Delete `src/package_name/hello.py` and `tests/test_hello.py` once your own code replaces the
example.

2. Name test files `test_*.py` and test functions `test_*`, mirroring the layout of your package
(`src/package_name/foo.py` is tested by `tests/test_foo.py`).

3. Write one test per behaviour you want to keep working, and name it after that behaviour
(`test_hello_returns_greeting`, not `test_1`). A test that would still pass if the function were
broken is not worth having.

4. Cover the cases you are tempted to check by hand in a notebook: the empty input, the boundary,
the error path. Add a test reproducing any bug you fix, so it cannot come back.

5. Keep tests fast and independent of the lab storage. If a test needs data, generate a small
fixture in the test itself rather than reading from a data path.

The same style rules apply to test code as to the rest: type hints and a short numpy-style docstring
on every test function.

## Continuous integration

`.github/workflows/ci.yml` defines two jobs, which run on every push to `main` and on every pull
request:

- `pre-commit` runs every hook over all files, so CI fails on anything you did not run locally.
- `pytest` runs `uv sync --frozen` and the test suite. `--frozen` fails if `uv.lock` is out of date
with `pyproject.toml`, so commit the lockfile whenever you add a dependency.

Both jobs pin the Python version through the `PYTHON_VERSION` variable at the top of the workflow.
It should match the version in `containers/apptainer.def`; change it in both places. To reproduce a
CI failure locally:

```sh
uvx pre-commit run --all-files
uv run pytest
```
102 changes: 64 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,85 @@
# Project template
# {Project name}

This template should be used for every Python project in the lab. It uses:
> One or two sentences on what this project does and why it exists.

- [`uv`](https://docs.astral.sh/uv/) for dependency management.
- [`ruff`](https://docs.astral.sh/ruff/) for code formatting.
- [`ty`](https://docs.astral.sh/ty/) for type checking.
- [`pre-commit`](https://pre-commit.com/) hooks for automated validation.
## Status

## Dependency management
{Early exploration | Active development | Stable | Archived}. {Anything a newcomer should know
before relying on this: what works, what doesn't.}

We use [`uv`](https://docs.astral.sh/uv/) for dependency management. It is just as
full-featured as `poetry`, but _much faster_. Follow the instructions below to
create a new project:
## Getting started

1. Update the name of the project in `pyproject.toml`.
2. Change the name of the folder `src/python-base` to match the project name.
3. Run `uv sync` from the root of the repo.
This will create a virtual environment and install needed development dependencies.
4. Add the dependencies you need (and run this same command every time you need
a new package):
Requires Python >=3.12 and [`uv`](https://docs.astral.sh/uv/).

```sh
uv add polars lightgbm
```
```sh
uv sync # create the virtualenv and install dependencies
uvx pre-commit install # install the pre-commit hooks
```

Check the install works:

5. Take a look at the `uv`'s [Getting started guide](https://docs.astral.sh/uv/getting-started/).
```sh
uv run python -m {package_name}.hello
```

## Pre-commit hooks
## Usage

Install the pre-commit hooks:
{The shortest command that produces something useful, and the output it prints. Add one example per
entry point.}

```sh
uvx pre-commit install
uv run python -m {package_name}.{entry_point} --help
```

This will create a `.git/hooks/pre-commit` file that will run the pre-commit
hooks every time you commit. Upon the first commit, the hooks will be installed.
## Layout

| Path | Contents |
| -------------------------- | ------------------------------------------------- |
| `src/{package_name}/` | Library code, importable as `{package_name}`. |
| `src/{package_name}/data/` | `config.yaml` — data paths and project settings. |
| `tests/` | Test suite, run with `uv run pytest`. |
| `containers/` | Apptainer definition and entrypoint. |
| {scripts, notebooks, …} | {Fill in the folders this project actually adds.} |

Data paths live in `config.yaml` and are read through `config.py` — never hardcode them in scripts.

## Data

{Where the inputs come from, where they live on the lab storage, and how to get them. Say if
anything is restricted or must not be committed.}

Some hooks output error message that require a manual change (e.g., linting
errors). Other hooks perform automated fixes. Either way, you need to re-run
the commit command:
## Results

`LOGBOOK.md` records each iteration in antichronological order (hypothesis, method, key results,
next steps). `RESULTS.md` compiles the comparable results across iterations, and is the place to
look first.

{Link the reports here as they are produced.}

## Containers

Build and run the Apptainer image, binding your working directory:

```sh
git commit -m "My message"
apptainer build --build-arg PROJECT_NAME={project-name} project.sif containers/apptainer.def
apptainer run --bind /path/to/workdir:/mnt/{project-name} project.sif
```

### Code formatting
See the header of `containers/apptainer.def` for the available build arguments.

## Development

`GUIDELINES.md` covers the lab-wide toolchain (`uv`, `ruff`, `ty`, `pre-commit`); `AGENTS.md` covers
the coding style and research workflow, and is what coding agents read. Both apply to this project —
document only the deviations here.

```sh
uv run pytest # run the tests
uvx pre-commit run --all-files # run every hook, as CI does
```

Among the pre-commit hooks, you will find one that runs
[`ruff`](https://docs.astral.sh/ruff/) on every Python file. It is also warmly
recommended that you set up `ruff` in your IDE (e.g., Visual Studio Code, PyCharm).
`.github/workflows/ci.yml` runs those same two checks on every push.

### Typing
## Contact

We recommend the use of [type hints](https://docs.python.org/3/library/typing.html)
of your code. One of the pre-commit hooks is [`ty`](https://docs.astral.sh/ty/),
which will perform type checking when hints are available. This reduces greatly the
risk of bugs and the maintainability of the code.
{Maintainer name and how to reach them.}
Loading
Loading