Coding standards

This document describes the coding standards used in this project.

Note

Just want to run all quality checks and tests? See Testing checklist below.

Linters

django-ca is linted and formatted with the following formatters:

To test all linters, simply run (pylint is separate for now, as it is very slow):

$ ./dev.py code-quality
$ pylint ca/django_ca/

Type hints

The source code also uses type hints and is checked using mypy. To check typehints, use mypy:

$ mypy ca/django_ca/

Overrides

isort, flake8, pylint and mypy support overriding warnings. If necessary, follow these general rules:

  • Use overrides as rarely as possible.

  • Exclude specific errors (so e.g. for flake8, use # NOQA: E501 instead of # NOQA).

  • Add comments explaining the exclude. If possible, comment in the same line:

    import unused  # NOQA: F401  # Import this for some important reason
    

    If your comment does not fit in the same line, add a comment above prefixed with $SW NOTE::

    # PYLINT NOTE: A really long explanation why we have the bar argument that is not used.
    # TYPE NOTE: We don't type this, since it's only a demo.
    def func(foo, bar):  # type: ignore # pylint: disable=unused-argument
        """Comment to make pylint happy."""
        print(foo)
    

Documentation

Documentation is checked using doc8 and spell checked using sphinxcontrib.spelling.

$ doc8 docs/source/
$ make -C docs spelling

Warnings are always turned into errors, as this uncovers various mistakes such as broken references. To build the documentation, simply run:

$ make -C docs html

tox

To run all checkers with tox, simply run:

$ tox -e lint,pylint,mypy,docs,dist-test

Note that pylint (currently) runs for an extremely long time.

Test coverage

The test suite must ensure 100% test coverage. Completely excluding code from test coverage is only allowed when absolutely necessary. To generate a coverage report in docs/build/coverage/, simply run:

$ ./dev.py coverage

Conditional pragmas

In addition to the standard # pragma: no cover and # pragma: no branch, the test suite adds pragmas to exclude code based on the Python version or library versions. For example:

if sys.version_info >= (3, 8):  # pragma: only py>=3.8
   from typing import Literal
else:  # pragma: only py<3.8
   from typing_extensions import Literal

If you have branches that are only relevant for some versions, there’s also pragmas for that:

if sys.version_info >= (3, 8):  # pragma: py>=3.8 branch
   print("Do something that's only useful in Python 3.8 or newer.")
if django.VERSION[:2] >= (3, 2):  # pragma: django>=3.2 branch
   print("Do something that's only useful in Django 3.2 or newer.")

You can use all operators (<, <=, ==, !=, >, >=), and we add pragma for the versions of Python, Django, cryptography.

Please check ca/django_ca/tests/base/pragmas.py for a tested file that includes all supported pragmas. Correctly using the pragmas is mandatory, as they are also used for finding outdated code when older versions are deprecated.a

Testing checklist

The following commands, assuming you have a virtualenv active, run all linters, test code coverage and check documentation (note that pylint currently takes a long time).

$ tox -e lint,pylint,mypy,docs,dist-test
$ ./dev.py coverage