## Environment Setup

1. We use [Poetry](https://python-poetry.org/docs/#installation) for managing
   virtual environments and dependencies. Once Poetry is installed, run
   `poetry install` in this repo to get started.
2. For managing linters, static-analysis, and other tools, we use
   [pre-commit](https://pre-commit.com/#installation). Once Pre-commit is
   installed, run `pre-commit install` in this repo to install the hooks. Using
   pre-commit ensures PRs match the linting requirements of the codebase.

## Documentation

Whenever possible, please add docstrings to your code! We use
[numpy-style napoleon docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/#google-vs-numpy).
To confirm docstrings are valid, build the docs by running
`poetry run make html` in the `docs/` folder.

I typically write dosctrings first, it will act as a guide to limit scope and
encourage unit-testable code. Good docstrings include information like:

1. If not immediately obvious, what is the intended use-case? When should this
   function be used?
2. What happens during errors/edge-cases.
3. When dealing with physical values, include units.

## Unit Tests

We use the [pytest](https://docs.pytest.org/) framework for unit testing.
Ideally, all new code is partners with new unit tests to exercise that code. If
fixing a bug, consider writing the test first to confirm the existence of the
bug, and to confirm that the new code fixes it.

Unit tests should only test a single concise body of code. If this is hard to
do, there are two solutions that can help:

1. Restructure the code. Keep inputs/outputs to be simple variables. Avoid
   complicated interactions with state.
2. Use [pytest-mock](https://pytest-mock.readthedocs.io/en/latest/) to mock out
   external interactions.

## Coding Style

In an attempt to keep consistency and maintainability in the code-base, here are
some high-level guidelines for code that might not be enforced by linters.

- Use f-strings.
- Keep/cast path variables as `pathlib.Path` objects. Do not use `os.path`. For
  public-facing functions, cast path arguments immediately to `Path`.
- Use magic-methods when appropriate. It might be better to implement
  `MyClass.__call__()` instead of `MyClass.run()`.
- Do not return sentinel values for error-states like `-1` or `None`. Instead,
  raise an exception.
- Avoid deeply nested code. Techniques like returning early and breaking up a
  complicated function into multiple functions results in easier to read and
  test code.
- Consider if you are double-name-spacing and how modules are meant to be
  imported. E.g. it might be better to name a function `read` instead of
  `image_read` in the module `my_package/image.py`. Consider the module
  name-space and whether or not it's flattened in `__init__.py`.
- Only use multiple-inheritance if using a mixin. Mixin classes should end in
  `"Mixin"`.
