Skip to content

Commit

Permalink
cookiecutter,python/linting: Normalize Ruff configuration, closes #92
Browse files Browse the repository at this point in the history
  • Loading branch information
jpmckinney committed Sep 17, 2024
1 parent 058656e commit 22b4a8f
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 95 deletions.
27 changes: 12 additions & 15 deletions cookiecutter-django/{{cookiecutter.project_slug}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,26 @@ target-version = "py311"
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN", "COM", "EM",
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
"W191", "D206", "Q000", "Q001", "Q002", "Q003", "ISC001",
"D203", "D212", # ignore incompatible rules
"D200", "D205", # documentation preferences
"C901", "PLR091", # complexity preferences
"ARG001", "ARG002", "DJ008", "RUF012", "SLF001", # Django
"ANN", "C901", "COM812", "D203", "D212", "D415", "EM", "ISC001", "PERF203", "PLR091", "Q000",
"D1",
"PTH",
]

[tool.ruff.lint.flake8-builtins]
builtins-ignorelist = ["copyright"]

[tool.ruff.lint.flake8-unused-arguments]
ignore-variadic-names = true

[tool.ruff.lint.per-file-ignores]
"docs/conf.py" = ["INP001"] # no __init__.py file
"docs/*" = ["D100", "INP001"]
"{*/signals,*/views,*/migrations/*}.py" = ["ARG001"]
"{*/admin,*/routers,*/views,*/commands/*}.py" = ["ARG002"]
"{*/admin,*/forms,*/models,*/routers,*/migrations/*,tests/*}.py" = ["RUF012"]
"*/migrations/*" = ["E501"]
"tests/*" = [
"D", # docstring
"ARG001", # pytest fixtures
"PLR0913", # Too many arguments
"PLR2004", # Magic value used
"PT", # Django
"S101", # assert
"D", "FBT003", "INP001", "PLR2004", "PT", "S", "TRY003",
]

[tool.coverage.run]
omit = ['*/migrations/*']
omit = ["*/migrations/*"]
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,17 @@ target-version = "py39"
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN", "COM", "EM",
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
"W191", "D206", "Q000", "Q001", "Q002", "Q003", "ISC001",
"D203", "D212", # ignore incompatible rules
"D200", "D205", # documentation preferences
"C901", "PLR091", # complexity preferences
"ANN", "C901", "COM812", "D203", "D212", "D415", "EM", "ISC001", "PERF203", "PLR091", "Q000",
]

[tool.ruff.lint.flake8-builtins]
builtins-ignorelist = ["copyright"]

[tool.ruff.lint.flake8-unused-arguments]
ignore-variadic-names = true

[tool.ruff.lint.per-file-ignores]
"docs/conf.py" = ["INP001"] # no __init__.py file
"docs/conf.py" = ["D100", "INP001"]
"tests/*" = [
"D", # docstring
"ARG001", # pytest fixtures
"PLR0913", # Too many arguments
"PLR2004", # Magic value used
"S101", # assert
"ARG001", "D", "FBT003", "INP001", "PLR2004", "S", "TRY003",
]
5 changes: 1 addition & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))


# -- Project information -----------------------------------------------------

Expand Down
33 changes: 1 addition & 32 deletions docs/python/documentation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Type hints are preferred to ``type``, ``rtype`` and ``vartype`` fields. Use `aut

.. seealso::

:ref:`check-docstring-style`
:doc:`linting`

Build documentation locally
---------------------------
Expand All @@ -53,37 +53,6 @@ Open http://127.0.0.1:8000/ in your web browser.

Documentation is built in ``docs/_build/html``, like when building with ``make html`` from the ``docs/`` directory.

.. _check-docstring-style:

Check docstring style
---------------------

Use `Ruff <https://docs.astral.sh/ruff/>`__ in new :doc:`packages<packages>`.

.. literalinclude:: samples/pyproject-ruff.toml
:language: toml

These error codes are ignored:

D100 Missing docstring in public module
Avoid generic docstrings for utility modules like ``util.py``.
D104 Missing docstring in public package
Document the package in Sphinx, not in ``mypackage/__init__.py``.
D200 One-line docstring should fit on one line with quotes
Allow one style for all docstrings. (Make diffs smaller if docstrings change.)
D203 1 blank line required before class docstring
Incompatible with D211 (No blank lines allowed before class docstring).
D205 1 blank line required between summary line and description
Allow summary line to be multiple lines, especially if it contains links or roles.
D212 Multi-line docstring summary should start at the first line
Incompatible with D213 (Multi-line docstring summary should start at the second line).
D400 First line should end with a period
See D205.
D415 First line should end with a period, question mark, or exclamation point
Duplicative with D400 (First line should end with a period).

Reference: `PEP 257 <https://peps.python.org/pep-0257/>`__: `One-line Docstrings <https://peps.python.org/pep-0257/#one-line-docstrings>`__, `Multi-line Docstrings <https://peps.python.org/pep-0257/#multi-line-docstrings>`__

Check broken links
------------------

Expand Down
77 changes: 47 additions & 30 deletions docs/python/linting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,57 @@ Linting

Before writing any code, set up formatters and linters.

In general, add all project configuration to ``pyproject.toml``. Do not use ``setup.cfg``, ``setup.py``, ``.editorconfig`` or tool-specific files like ``.coveragerc`` or ``pytest.ini``.

Configuration
-------------

New projects should use the `Ruff <https://docs.astral.sh/ruff/>`__ formatter and linter, with line lengths of 119 (the Django coding style until 4.0), configured as follows:
New projects should use the `Ruff <https://docs.astral.sh/ruff/>`__ formatter and linter, with line lengths of 119 (the Django coding style until 4.0). A starting point, based on `script.sh in standard-maintenance-scripts <https://github.com/open-contracting/standard-maintenance-scripts/blob/main/tests/script.sh>`__:

.. literalinclude:: samples/pyproject-ruff.toml
:language: toml

In general, add all configuration to ``pyproject.toml``. Do not use ``setup.cfg``, ``setup.py``, ``.editorconfig`` or tool-specific files like ``.coveragerc`` or ``pytest.ini``.
With this starting point, check which rules fail:

.. code-block:: bash
ruff check . --statistics
And check individual failures, for example:

.. code-block:: bash
ruff check . --select D400
As general guidance:

- Fix failures, if possible.
- Use a ``# noqa: RULE`` comment if the failure is rare (for example, an ``S`` rule), or if it should be fixed, given more time. Add a short comment to explain the failure. For example: ``# noqa: S104 # Docker``
- Use ``per-file-ignores`` if the failures occur in a single (or a set of) files. For example: ``"*/__main__.py" = ["T201"] # print``
- Use ``ignore`` if the failures occur in disparate files and are expected to occur in new code. For example: ``"TRY003", # errors``
- Use `settings <https://docs.astral.sh/ruff/settings/>`__ where possible, instead of ignoring rules entirely. Notably, use:

- `builtins-ignorelist <https://docs.astral.sh/ruff/settings/#lint_flake8-builtins_builtins-ignorelist>`__, instead of A002
- `extend-immutable-calls <https://docs.astral.sh/ruff/settings/#lint_flake8-bugbear_extend-immutable-calls>`__, instead of B008
- `allowed-confusables <https://docs.astral.sh/ruff/settings/#lint_allowed-confusables>`__, instead of RUF001
- `extend-ignore-names <https://docs.astral.sh/ruff/settings/#lint_flake8-self_extend-ignore-names>`__, instead of SLF001

``isort:skip`` and ``type: ignore`` comments should be avoided, and should reference the specific error if used, to avoid shadowing another error: for example, ``# type: ignore[attr-defined]``.

.. admonition:: Complexity rules

We ignore the ``C901`` and all ``PLR091`` rules.

Complexity is best measured by the effort required to read and modify code. This cannot be measured using techniques like `cyclomatic complexity <https://en.wikipedia.org/wiki/Cyclomatic_complexity>`__. Reducing cyclomatic complexity typically means extracting single-caller methods and/or using object-oriented programming, which frequently *increases* cognitive complexity.

See the note under :ref:`create-products-sustainably`.

..
Maintainers can check if docs/ and tests/ rules are included in projects without those directories
diff -u <(find . -type d -maxdepth 1 ! -name '*handbook' -exec test -f '{}'/pyproject.toml -a -d '{}'/docs \; -print | cut -d/ -f2 | sort) <(rg -c copyright */pyproject.toml | cut -d/ -f1 | sort)
diff -u <(find . -type d -maxdepth 1 ! -name '*handbook' -exec test -f '{}'/pyproject.toml -a -d '{}'/docs \; -print | cut -d/ -f2 | sort) <(rg -c docs/ */pyproject.toml | cut -d/ -f1 | sort)
diff -u <(find . -type d -maxdepth 1 -exec test -f '{}'/pyproject.toml -a \( -d '{}'/tests -o -d '{}'/backend/tests \) \; -print | cut -d/ -f2 | sort) <(rg -c tests/ */pyproject.toml | cut -d/ -f1 | sort)
.. _linting-pre-commit:

Expand Down Expand Up @@ -42,25 +84,6 @@ To avoid pushing commits that fail formatting or linting checks, new projects sh

`pre-commit/pre-commit-hooks <https://github.com/pre-commit/pre-commit-hooks>`__ is not used in the templates, as the errors it covers are rarely encountered.

Skipping linting
----------------

``noqa``, ``isort:skip`` and ``type: ignore`` comments should be avoided, and should reference the specific error if used, to avoid shadowing another error: for example, ``# noqa: E501`` or ``# type: ignore[attr-defined]``.

The errors that are allowed to be ignored per line are:

- ``E501 line too long`` for long strings
- ``F401 module imported but unused`` in a library's top-level ``__init__.py`` file
- ``E402 module level import not at top of file`` in a Django project's ``asgi.py`` file
- ``W291 Trailing whitespace`` in tests relating to trailing whitespace
- ``isort:skip`` if ``sys.path`` needs to be changed before an import

Maintainers can find unwanted comments with this regular expression:

.. code-block:: none
# noqa(?!(: (E501|F401|E402|W291)| isort:skip)\n)
.. _linting-ci:

Continuous integration
Expand All @@ -73,10 +96,10 @@ Create a ``.github/workflows/lint.yml`` file. The :doc:`django` and :doc:`Pypack
- Workflow files for linting :ref:`shell scripts<shell-ci>` and :ref:`Javascript files<javascript-ci>`
- `standard-maintenance-scripts <https://github.com/open-contracting/standard-maintenance-scripts#tests>`__ to learn about the Bash scripts

.. _python-optional-linting:
.. _python-additional-linting:

Optional linting
----------------
Additional linting
------------------

`codespell <https://pypi.org/project/codespell/>`__ finds typographical errors. It is especially useful in repositories with lengthy documentation. Otherwise, all repositories can be periodically checked with:

Expand All @@ -96,9 +119,3 @@ Optional linting
- specific third-party code (docson, htmlcov, schemaspy)
- non-code and non-documentation files
- codespell-covered repositories (european-union-support)

.. admonition:: Complexity rules

Complexity is best measured by the effort required to read and modify code. This cannot be measured using techniques like `cyclomatic complexity <https://en.wikipedia.org/wiki/Cyclomatic_complexity>`__. Reducing cyclomatic complexity typically means extracting single-caller methods and/or using object-oriented programming, which frequently *increases* cognitive complexity.

See the note under :ref:`create-products-sustainably`.
18 changes: 17 additions & 1 deletion docs/python/samples/pyproject-ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,20 @@ line-length = 119
target-version = "py311"

[tool.ruff.lint]
select = ["E", "C4", "F", "I", "W"]
select = ["ALL"]
ignore = [
"ANN", "C901", "COM812", "D203", "D212", "D415", "EM", "ISC001", "PERF203", "PLR091", "Q000",
"D1",
]

[tool.ruff.lint.flake8-builtins]
builtins-ignorelist = ["copyright"]

[tool.ruff.lint.flake8-unused-arguments]
ignore-variadic-names = true

[tool.ruff.lint.per-file-ignores]
"docs/conf.py" = ["D100", "INP001"]
"tests/*" = [
"ARG001", "D", "FBT003", "INP001", "PLR2004", "S", "TRY003",
]
2 changes: 1 addition & 1 deletion docs/python/style_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ You may also refer to common guidance like the `Google Python Style Guide <https

The :doc:`../services/index` section contains Python-related content for :doc:`../services/postgresql` and :doc:`../services/rabbitmq`.

:ref:`Organization-wide spell-checking<python-optional-linting>`
:ref:`Organization-wide spell-checking<python-additional-linting>`

Built-in functions
------------------
Expand Down

0 comments on commit 22b4a8f

Please sign in to comment.