Skip to content

Latest commit

 

History

History
150 lines (101 loc) · 6.36 KB

CONTRIBUTING.md

File metadata and controls

150 lines (101 loc) · 6.36 KB

How to Contribute to the lymph Package

First, thanks for considering to help out on this project!

Before you start coding away and fixing some of the 🔗issues, let me give you an introduction of the standards and best-practices that we have tried to follow so far:

Git

We use 🔗git as source control manager and 🔗GitHub as the repository hosting service.

Branching Model

Our branching model in 🔗git looks like this:

%%{init: {'gitGraph': {'rotateCommitLabel': false}, 'theme': 'base'} }%%
gitGraph
    commit id: "initial commit"
    branch dev
    checkout dev
    commit id: "some dev stuff"
    branch feature-1
    checkout feature-1
    commit id: "add this"
    commit id: "fix this"
    commit id: "refactor"
    checkout dev
    merge feature-1
    branch release-x.y.z
    checkout release-x.y.z
    commit id: "update changelog"
    checkout main
    merge release-x.y.z tag: "x.y.z"
    checkout dev
    merge main
    branch feature-2
    commit id: "add feat"
    commit id: "new test"

So, we have a main branch where every tagged release lives. Then there's a dev branch that is the starting point for any feature branches or fixes. In these feature or fix branches, the actual work happens. When a feature or fix is ready, it gets merged into the dev branch, from where a new release-x.y.z branch is created to finalize and maybe also test a new release. When everything looks good, that release-x.y.z branch is merged into the main branch, tagged and a release is created from it.

This branching model is sometimes called 🔗git-flow and it is worth reading through the original article for a clearer understanding of how and why this works.

Semantic Versioning

For version numbers, we use 🔗SemVer. Generally, under this standard, any version number follows the scheme

MAJOR.MINOR.PATCH

The idea is that any change (or suit of changes) in the software, causes one of the three numbers MAJOR, MINOR, or PATCH to increment by one.

If the changes just fix a bug, but leave the API of the code unchanged, PATCH is incremented by one.

When a new feature is introduced but backwards compatibility regarding the existing features is maintained, the MINOR version gets incremented by one.

And as soon as any so-called "breaking change" occurs that is incompatible with previous versions - for example, renaming a function or class would be a breaking change - the MAJOR version needs to be updated.

There is one exception: As ong as the MAJOR version is 0 (meaning this is still in its initial development), breaking changes may also "just" increment the MINOR version.

Commit Messages

Since not so long ago, we use 🔗conventional commits in this repository. This is a standard, defining how commit messages should be written. It is worth the time to read the quick introduction on their website. Using it makes it easier to auto-update a draft of the CHANGELOG.md.

To avoid accidentally committing anything with a non-standard commit message, you can install a commit: If you have installed the package with the dev dependencies (for example by typing pip install -e .[dev]), then you can install the commit hook(s) by typing:

pre-commit install
pre-commit install --hook-type commit-msg

make sure this happens inside the virtual environment you have set up.

Documentation

Right now, we use 🔗sphinx to compile a documentation from the docstrings of the code and host it on 🔗readthedocs.

However, I like the simplistic style of 🔗pdoc a lot more that 🔗sphinx, which can be convoluted. So, I would like to switch to 🔗pdoc at some point in the foreseeable future.

What I like about 🔗pdoc is that for the documentation to turn out well and useful, the code is necessarily well documented. It should be easy to understand and well described even when only reading the source code.

Docstrings

The code uses 🔗Google style docstrings (or at least tries to do so most of the time). Generally, the 🔗Google Python style guide is worth a read and handy for reference.

Here's an example:

def my_well_named_function(
    first_parameter: bool,
    second_parameter: int,
    another_parameter: Dict[str, Any],
    optional_parameter: Optional[float] = None,
) -> np.ndarray:
    """This function is well named and does its job very well.

    After a blank line we can add a bit of a longer description. This can even span
    multiple lines if it is necessary. Afterwards, the arguments follow.

    Args:
        first_parameter: The first parameter is an integer.
        second_parameter: The second parameter is a string.
        another_parameter: The third parameter is a float.
        optional_parameter: The fourth parameter is optional.

    Returns:
        An array filled with some super useful values.

    Examples:
        >>> my_well_named_function(True, 42, {"a": 1, "b": 2}, 3.14)
        array([1, 2, 3])
    """
    # here comes the code

Tests

For simple functions and methods, I really like writing short and illustrative examples that can be used by 🔗doctest to test some basic functionality.

For more complicated stuff, we used 🔗pytest and 🔗hypothesis to cover a broad range of input values and edge cases. For the core components, this is important and ideally, with every new version of the package released, the portion of the code covered in tests goes up.

Questions?

If there is still something unclear, feel free to reach out to me: roman.ludwig@usz.ch.