# Python Server Development

## Overview


Python code should be:

* Broken up into small, clear, testable functions.
* Documented with docstrings (For conventions see below).
* Type hinted
* Covered by unit tests (using [pytest](https://docs.pytest.org))


## Python environment, build, and dependencies

As of `v3` we specify dependencies in `pyproject.toml`. These are not currently frozen due to difficulties matching versions on different platforms. On Raspberry Pi, it's best to specify `--only-binary=:all:` when installing packages, to ensure libraries like `numpy` and `scipy` are not compiled from source (which takes many hours, and/or fails). Pinning dependencies with `requirements.txt` and/or `requirements.in` may happen in the future.


## Python: Formatting, linting, and tests

[Ruff](https://docs.astral.sh/ruff/) is used both for linting and formatting the codebase. Our custom linter rules are in our `pyproject.toml`.

> All of the commands below assume that you are running in the OFM virtual environment, i.e. you have run `ofm activate` on an OpenFlexure SD card, or `source .venv/bin/activate` on Linux, or `.venv/Scripts/activate`on Windows Powershell, or `.venv\Scripts\activate.bat` on Windows CMD.

**Before committing** you should lint and auto-format your code.

Please run:

* `ruff check` - to lint your Python code (please manually fix any flagged issues)
* `ruff format` - to automatically format your Python code
* `codespell .` - to check for spelling errors

**Before merging** please auto-format your code and also run the quality checks (linting, static analysis, and unit tests)

Please run:

* All commands above
* `mypy src` to type check your Python code
* `pytest` to run all the unit tests in `tests/unit_tests`

We use several code analysis and formatting libraries in this project. Our CI will check each of these automatically, so ensuring they pass locally will save you time. Currently `ruff` is used for linting/formatting and `pytest` for unit tests.

To check your code before each commit we recommend putting the following script in your `.git/hooks` directory with the filename `pre-commit`.


```python
#!/bin/bash

set -e

function angrymessage()
{
    RED='\033[1;31m'
    NC='\033[0m' # No Color
    printf "\n${RED}Not committing as ruff or spellcheck failed${NC}\n\n"
}

trap angrymessage ERR

ruff format --check
ruff check
codespell .
```


## Docstrings conventions.

We write docstrings in [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html), and process them with [pydoctor](https://pydoctor.readthedocs.io/).

For function arguments, return values, and exceptions we use the following style recognised by reStructuredText:

```python
def a_function(arg1: str, arg2: int) -> tuple[float, int]:
    """A one sentence summary, in imperative mood, ending with punctuation.

    More detail after a blank line. This could be a whole paragraph.

    When referring to ``code`` in reStructuredText used double backticks

    :param arg1: This is the first argument, we can say everything we need to about it.
        If the text wraps onto a new line, this line is indented.
    :param arg2: This is the second argument.

    :returns: Say what it returns without anything within the colons. If returning
        multiple values, a bulleted list is a good idea - just make sure:

        * It is indented to 1 level in
        * There is a blank line before the bullets
        * The bullet points are ``*`` not ``-``
    """
```

+The linter rules we use for docstrings are defined by [pydocstyle](https://docs.astral.sh/ruff/rules/#pydocstyle-d). We do not use rules D203, D213, D400 for reasons explained in our `pyproject.toml` file.

The documentation generated by pydoctor from docstrings can be viewed in any merge request (if the CI pipeline has passed) using the "View App" button.