All contributions to BayBE are welcome!
... no matter if bug fixes, new features, or just typo corrections.
To shorten the overall development and review process, this page contains are a few sections that can make your life easier.
To implement your contributions in a local development environment, we recommend the following workflow:
-
Clone a fork of the repository to your local machine.
-
Create and activate a virtual python environment using one of the supported python versions.
-
Change into the root folder of the cloned repository and install an editable version including all development dependencies:
pip install -e '.[dev]'
-
Run our tests to verify everything works as expected:
pytest
-
Install our pre-commit hooks:
pre-commit install
-
Create a new branch for your contribution:
git checkout -b <your_branch_name>
-
Implement your changes.
-
Optional but recommended to prevent complaints from our CI pipeline: Test your code.
There are several test environments you can run via
tox
, each corresponding to a developer tool in a certain Python version. You can retrieve all available environments viatox list
. For more information, see our README about tests.For instance, running all code tests in Python 3.12 can be achieved via:
tox -e fulltest-py312
Other tox tests that are useful to verify your work locally are
tox -e lint-py312
,tox -e mypy-py312
andtox -e coretest-py312
.If you want to challenge your machine, you can run all checks in all Python versions in parallel via:
tox -p
This can be considered the ultimate one-stop check to make sure your code is ready for merge.
-
Push the updated branch back to your fork:
git push origin
-
Open a pull request via Github's web page.
A common situation encountered when submitting a pull request (PR) is that the upstream branch has evolved since the moment your PR branch was created, and a synchronization is needed in order to prepare your branch for a merge (e.g., to remove existing conflicts).
Because we care about our Git history and would like to keep it clean and easy to follow, we generally recommend rebasing your branch onto the latest upstream commit in such situations, especially if your changes are orthogonal to what has happened on the remote branch in the meantime. Compared to merging, this has the advantage of keeping the history of your commits (and thus of the entire repository) linear, and your own PR free of changes that happened remotely, which also greatly simplifies the review process (e.g., it produces simpler diffs).
That said, the above is only a recommendation and by no means a requirement. However, depending on the complexity of your PR commit history, we reserve the right to merge your branch using a squash-rebase as a last resort to keep our history clean. By following the guideline above, this step can be easily avoided in most cases.
(developer-tools)=
In order to maintain a high code quality, we use a variety of code developer tools. When following the above described workflow, pre-commit will automatically trigger (most) necessary checks during your development process. In any case, these checks are also conducted in our CI pipeline, which must pass before your pull request is considered ready for review. If you have questions or problems, simply ask for advice.
Tool | Purpose |
---|---|
ruff | code linting and formatting |
mypy | static type checking |
pydocstyle pydoclint |
analyzing docstrings |
typos | basic spell checking |
pytest | testing |
pytest-cov | measuring test coverage |
sphinx | generating our documentation |
pip-audit | detecting vulnerabilities in dependencies |
tox | orchestrating all the above |
Executing a specific one of these tools is easiest by using the corresponding tox environment,
tox -e <env>
where <env>
is any of the environment names found via tox list
.
(code-design)=
When reading BayBE's code, you will notice certain re-occurring design patterns. These patterns are by no means enforced, but following them can streamline your own development process:
- We build most our classes with attrs, which is useful for lean class design and attribute validation.
- Our (de-)serialization machinery is built upon cattrs, separating object serialization from class design.
- The modular nature of BayBE's components is reflected in our test suite through the use of hypothesis property tests.
For most parts, BayBE's code and functional components are organized into different subpackages. When extending its functionality (for instance, by adding new component subclasses), make sure that the newly written code is well integrated into the existing package and module hierarchy. In particular, public functionality should be imported into the appropriate high-level namespaces for easier user import. For an example, see our parameter namespace.
Our docstrings generally follow the Google Python Style Guide. Basic style and consistency checks are automatically performed via pre-commit during development and in our CI pipeline.
Apart from that, we generally recommend adhering to the following guideline:
-
Each function should have a docstring containing:
- a short one-line summary at the top,
- an optional extended summary or description below and
- all relevant sections (
Args
,Raises
, ...).
-
Use type hints (for variables/constants, attributes, function/method signatures, ...). Avoid repeating type hints in docstrings.
-
When referencing objects (classes, functions, ...), use
:<key>:`path.to.function`
where<key>
is to be replaced with the respective role (class
,func
, ...) -
Use double backticks for literals like in
``MyString``
.
-
Place attribute docstrings below the attribute declaration, not in the class docstring. Separate different attributes using a blank line. For example:
@define class Cookies: """A delicious recipe for chocolate-banana cookies.""" chocolate: float """Chocolate is naturally measured in terms of floats.""" bananas: int """For bananas, we use integers, of course."""
-
Unless another more specific name is suitable, use our default naming convention for
attrs
defaults and validators:@my_attribute.default def _default_my_attribute(self): ... @my_attribute.validator def _validate_my_attribute(self, attribute, value): ...
A one-line docstring suffices for these methods, but they should have a
Raises:
section if applicable. Linter warnings regarding missing attribute docstrings can be silenced using# noqa: DOC101, DOC103
.
When overriding methods in subclasses, decorate them with @typing_extensions.override
to make the relationship explicit:
from typing_extensions import override
class Parent:
def le_method():
"""The method of the parent class."""
...
class Child:
@override
def le_method():
"""Overridden method of the child class."""
...
Using the decorator provides a type-safe approach for defining inheritance structures that eliminates two potential sources of unintended class design:
- An intended override is does not occur because the method names differ between the parent and child classes (e.g. if the parent method is renamed)
- An unintended override occurs because a method name that exists in the parent class
is used in the child class by mistake.
In both cases,
mypy
will complain and force you to fix the problem.