diff --git a/cookiecutter-django/{{cookiecutter.project_slug}}/pyproject.toml b/cookiecutter-django/{{cookiecutter.project_slug}}/pyproject.toml index f82ed00..6ecfd60 100644 --- a/cookiecutter-django/{{cookiecutter.project_slug}}/pyproject.toml +++ b/cookiecutter-django/{{cookiecutter.project_slug}}/pyproject.toml @@ -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/*"] diff --git a/cookiecutter-pypackage/{{cookiecutter.repository_name}}/pyproject.toml b/cookiecutter-pypackage/{{cookiecutter.repository_name}}/pyproject.toml index b90f677..fafa391 100644 --- a/cookiecutter-pypackage/{{cookiecutter.repository_name}}/pyproject.toml +++ b/cookiecutter-pypackage/{{cookiecutter.repository_name}}/pyproject.toml @@ -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", ] diff --git a/docs/conf.py b/docs/conf.py index c669dbc..84f854b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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 ----------------------------------------------------- diff --git a/docs/python/documentation.rst b/docs/python/documentation.rst index f30dded..a433cfa 100644 --- a/docs/python/documentation.rst +++ b/docs/python/documentation.rst @@ -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 --------------------------- @@ -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 `__ in new :doc:`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 `__: `One-line Docstrings `__, `Multi-line Docstrings `__ - Check broken links ------------------ diff --git a/docs/python/linting.rst b/docs/python/linting.rst index 6ae191f..dedc3b0 100644 --- a/docs/python/linting.rst +++ b/docs/python/linting.rst @@ -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 `__ formatter and linter, with line lengths of 119 (the Django coding style until 4.0), configured as follows: +New projects should use the `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 `__: .. 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 `__ where possible, instead of ignoring rules entirely. Notably, use: + + - `builtins-ignorelist `__, instead of A002 + - `extend-immutable-calls `__, instead of B008 + - `allowed-confusables `__, instead of RUF001 + - `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 `__. 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: @@ -42,25 +84,6 @@ To avoid pushing commits that fail formatting or linting checks, new projects sh `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 @@ -73,10 +96,10 @@ Create a ``.github/workflows/lint.yml`` file. The :doc:`django` and :doc:`Pypack - Workflow files for linting :ref:`shell scripts` and :ref:`Javascript files` - `standard-maintenance-scripts `__ to learn about the Bash scripts -.. _python-optional-linting: +.. _python-additional-linting: -Optional linting ----------------- +Additional linting +------------------ `codespell `__ finds typographical errors. It is especially useful in repositories with lengthy documentation. Otherwise, all repositories can be periodically checked with: @@ -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 `__. 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`. diff --git a/docs/python/samples/pyproject-ruff.toml b/docs/python/samples/pyproject-ruff.toml index bbd1a49..3d2bcd4 100644 --- a/docs/python/samples/pyproject-ruff.toml +++ b/docs/python/samples/pyproject-ruff.toml @@ -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", +] diff --git a/docs/python/style_guide.rst b/docs/python/style_guide.rst index ff4d4d8..79a9b99 100644 --- a/docs/python/style_guide.rst +++ b/docs/python/style_guide.rst @@ -7,7 +7,7 @@ You may also refer to common guidance like the `Google Python Style Guide ` + :ref:`Organization-wide spell-checking` Built-in functions ------------------