Contributing Guide

Contributing guide for projects in the sphinx-toolbox organization.

Getting Started

Each project’s source code and issue tracker are hosted on GitHub. Before making your first contributing you will need a GitHub account. If you don’t already have an account visit https://github.com/join .

Issues

Issues can be reported using the issue trackers on GitHub. Click the Issues tab at the top of the repository’s page and then click the green New button. You’ll be presented with a list of issue templates to choose from:

_images/issue-templates.png

Once you’ve chosen the template that best fits your issue you’ll see a template like this:

_images/issue-bug-report.png

Feel free to remove any sections which you don’t think are relevant.

Making Changes

Before making changes to the code you will need to fork the repository and clone your fork. GitHub have an excellent guide to this at https://guides.github.com/activities/forking/.

Once you have done this you’ll need to set up your development environment. The recommended tool is pyproject-devenv, which will automatically install the project’s runtime, build and test requirements:

cd <project_dir>
python3 -m pip install pyproject-devenv
pyproject-devenv

This will create the virtualenv in the venv directory. Alternatively you can use virtualenv and install the dependencies with pip manually.

In either case, be sure to activate the virtual environment. With bash:

source venv/bin/ACTIVATE
(sphinx-toolbox) $

Style

formate is used for code formatting. It can be run manually via pre-commit:

pre-commit run formate -a

or, to run the complete autoformatting suite:

pre-commit run -a

The general formatting rules are based on PEP 8, but with some differences:

Indentation

Tabs are used instead of spaces.

Function signatures

Function signatures should be laid out like this:

def long_function_name(
        var_one: str,
        var_two: int,
        var_three: List[str],
        var_four: Dict[str, Any],
        ) -> Iterable[Tuple[str, int]]:
    ...

with each argument on its own line, and the function name and return annotatons on separate lines. Ensure to include the trailing comma after the last argument’s annotation. If the signature is short enough it may be placed on one line:

def function_name(var_one: str, var_two: int) -> bool:
    ...

Functions should always be type annotated. *args and **kwargs need not be annotated, but other use of * (e.g. **children) should be annotated. Annotations may be omitted in tests, but are strongly encouraged.

Type Annotations

String annotations should be avoided. Exceptions include TypeVars and where the object is imported using with TYPE_CHECKING: to resolve a circular dependency. PEP 563 (from __future__ import annotations) must not be used.

Type hints should be valid with the earliest supported Python version for the project, typically Python 3.6. This includes the use of typing.Dict[…] etc. rather than dict[…], and importing certain objects from typing-extensions.

Long if Statements

Long if statements should be formatted with if ( on one line, the conditions on the following lines and the ): on the final line. The operator should be at the start of the lines, not at the end.

Where possible such long statements should be avoided, for example by splitting it into multiple if statements or calculating the value in advance.

Long Sequences

Long sequences should be written with each element on its own line and a trailing comma after the last element:

# Bad
my_list = [
        1, 2, 3,
        4, 5, 6
        ]
# Good
my_list = [
        1,
        2,
        3,
        4,
        5,
        6,
        ]

Maximum Line Length

Limit all lines to a maximum of 110 characters. This also applies to docstrings, expect for long URLs in explicit hyperlink targets. Converting long, implicit targets to explicit targets improves the readability of the docstring. This also applies to reStructuredText files in the documentation.

If the summary line of a docstring must exceed 110 characters the line must be wrapped and the docstring marked with # noqa: D400 immediately after the closing quotes.

Blank Lines

Blank lines must be used:

  • After a file’s top-level comments.

  • Before a comment indicating a group of imports.

  • After a group of imports.

  • Immediately after a docstring.

  • Immediately after a class, function or method signature if there is no docstring.

Blank lines must not be used:

  • Between a class, function or method signature and its docstring.

Module Level Dunder Names

Module level “dunders” (i.e. names with two leading and two trailing underscores) such as __all__, __author__, __version__, etc. should be placed after the module docstring and any imports.

They should be in the following order:

  • __author__

  • __copyright__

  • __license__

  • __version__

  • __email__

  • <a blank line>

  • __all__

__all__ should always be included, even for modules with no public API. Only include the names of public objects. If the module has no public API write __all__ = (). TypeVars may be included if it is necessary to document them, otherwise they should be omitted.

The flake8-dunder-all pre-commit hook can be used to automatically generate __all__, although its output should be checked by hand as it can sometimes produce odd results.

String Quotes

Strings should use double quotes where possible, except for single characters and empty strings which should use single quotes. Single quotes may be used where a double quote occurs within the string.

Triple double quotes (""") must be used for docstrings.

Docstrings

The first line of the docstring must start a new line:

# Bad:
"""Return a foobang
"""
# Good:
"""
Return a foobang
"""

This rule is enforced even for short one-liner docstrings, although those should be rare.

Each docstring must document the function’s parameters. For classes, the class docstring should document the arguments of __init__ or __new__ as appropriate.

__init__ should not have a docstring. __new__ may have a docstring in certain crcumstances, expecially for namedtuples.

Naming Style

Function and method names should be written in lower_case_with_underscores.

Class names should use CamelCase. When using acronyms, capitalize all the letters of the acronym. HTTPServerError is better than HttpServerError.

Private classes, functions and instance variables should be prefixed with a single underscore.

Testing

tox is used to automate testing, with the tests themselves run with pytest.

Start by installing tox and tox-envlist if you don’t already have them:

python3 -m pip install tox tox-envlist

To run tests for a specific Python version, such as Python 3.6:

tox -e py36

or, to test all Python versions:

tox -n test

Type Annotations

Type annotations are checked using mypy:

tox -e mypy

Code Quality

Run flake8 with tox to ensure your changes don’t introduce any issues:

tox -e flake8

Coverage

On POSIX systems with Firefox installed an HTML coverage report can be generated by running:

tox -n cov

Ensure your changes do not cause a significant decrease in the test coverage. If the coverage drops below the level set in repo_helper.yml (or 80% not specified) the tests will fail.

GitHub Actions

Tests are run on pushes to GitHub using GitHub Actions. You can see the results of these test at the bottom of the pull request page:

_images/github-actions-checks.png

A label will be added to the pull request automatically if the tests fail:

_images/pull-request-labels.png

This makes it easy to identify which tests are failing. Once the tests pass the label will be removed automatically.

If you are a first time contributor to a project manual approval is required for GitHub Actions to run. For more information see https://github.blog/2021-04-22-github-actions-update-helping-maintainers-combat-bad-actors/

Branch protection is used to ensure the following tests pass before merging pull requests:

  • Tests on Windows and Linux for all CPython version between 3.6 and 3.9 supported by the project.

  • Tests on Windows and Linux for PyPy 3.6, if supported by the project.

  • mypy type checking on Windows and Linux.

  • Flake8

  • The documentation check, if the project has documentation.

If the project only supports Linux the tests on Windows will not run and are not required to merge the pull request.

Tests on macOS are optional as they take longer than other platforms. CPython 3.10 and PyPy 3.7 are considered experimental and will not block a pull request from being merged if they fail. However, you should still check the results of these runs to ensure your changes have not introduced any errors there.

You should check the Files changed tab of the pull request to see whether any issues have been identified. This can be due to syntax errors in the documentation source or issues identified by flake8 and Codefactor.

Your pull request may be commented on by Coveralls to report any changes to the code coverage.

Documentation

Most projects have documentation generated with Sphinx and hosted on ReadTheDocs.

The documentation source is located in the doc-source directory. A local copy of the documentation can be built with tox:

tox -e docs

The built documentation can be found in the doc-soure/build/html directory.

Building from source

The recommended way to build from source is via tox:

tox -e build

As well as building the sdist and wheel this will check them for common errors, such as missing files or potential rendering issues on PyPI.

The output files will be in the dist directory.

If you wish, you may also use pep517.build or another PEP 517-compatible build tool.

Release Process

New releases are created using a combination of repo-helper and GitHub Actions. To start you will need repo-helper installed:

python3 -m pip install repo-helper --upgrade

Then ensure all changes have been committed or stashed:

git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working tree clean

The create a release using repo-helper release:

repo-helper release minor

Repleace minor with major or patch as appropriate.

Once the release has been created locally, push to GitHub. Ensure you push the tag too:

git push
git push --tags

Once the tests pass GitHub Actions will take care of building and uploading to PyPI and Anaconda (if enabled for the project). You should keep an eye on the tests to ensure they pass.

The release will be automatcally copied to GitHub Releases within the next two days using OctoCheese.