diff --git a/_shared/hooks/post_gen_project.py b/_shared/hooks/post_gen_project.py index 63581e0..63cd8f3 100755 --- a/_shared/hooks/post_gen_project.py +++ b/_shared/hooks/post_gen_project.py @@ -57,6 +57,10 @@ def remove_conditional_files(): paths_to_remove.extend([".github/workflows/pypi.yml"]) {% endif %} + {% if cookiecutter.get("linter") == "ruff" %} + paths_to_remove.extend(["tests/pyproject.toml"]) + {% endif %} + {% if cookiecutter.get("console_script") != "yes" %} paths_to_remove.extend(["src/{{ cookiecutter.package_name }}/__main__.py"]) paths_to_remove.extend(["src/{{ cookiecutter.package_name }}/cli.py"]) diff --git a/_shared/project/pyproject.toml b/_shared/project/pyproject.toml index 124d0c9..54fe2eb 100644 --- a/_shared/project/pyproject.toml +++ b/_shared/project/pyproject.toml @@ -59,6 +59,90 @@ filterwarnings = [ {% endif %} ] +{% if cookiecutter.get("linter") == "ruff" %} +[tool.ruff] +target-version = "py311" +line-length = 88 +exclude = [ + "tests/bdd/steps/_compiled_feature_steps.py", +] + +[tool.ruff.lint] +select = [ + "E", "W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w + "D", # https://docs.astral.sh/ruff/rules/#pydocstyle-d + "ARG", # https://docs.astral.sh/ruff/rules/#flake8-unused-arguments-arg + "BLE001", # https://docs.astral.sh/ruff/rules/blind-except/ + "R", "PLR", # https://docs.astral.sh/ruff/rules/#refactor-r + "C", "PLC", # https://docs.astral.sh/ruff/rules/#convention-c + "SLF", # flake-8-self + "N", # https://docs.astral.sh/ruff/rules/#pep8-naming-n + "F", # https://docs.astral.sh/ruff/rules/unused-import/ + + "RUF100", # unused-noqa +] + +ignore = [ + # Missing docstrings. + "D100","D101","D102","D103","D104","D105","D106","D107", + + # "No blank lines allowed after function docstring" conflicts with the + # Black code formatter which insists on inserting blank lines after + # function docstrings. + "D202", + + # "1 blank line required before class docstring" conflicts with another + # pydocstyle rule D211 "No blank lines allowed before class docstring". + "D203", + + # "Multi-line docstring summary should start at the first line" + # and "Multi-line docstring summary should start at the second line". + # These two rules conflict with each other so you have to disable one of them. + # How about we disable them both? PEP 257 says either approach is okay: + # + # > The summary line may be on the same line as the opening quotes or on + # > the next line. + # > + # > https://peps.python.org/pep-0257/#multi-line-docstrings + "D212", + "D213", + + # We use Black to format our code automatically, so we don't need PyLint to + # check formatting for us. + "E501", # line-too-long + + "PLR2004", # Magic values, we mostly get it on HTTP status codes + + # Disabled during the pylint migration, ideally we'll enable this after we are settled in ruff + "RET504", + "RET501", + "PLR6301", +] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = [ + # Just disable name style checking for the tests, because we + # frequently use lots of argument names that don't conform. + # For example we frequently create pytest fixtures that aren't named in + # snake_case, such as a fixture that returns a mock of the FooBar class would + # be named FooBar in CamelCase. + "N", + # We are more lax about comment formatting in the tests + "D", + + "PLR0913", + + # Lots of test methods don't use self, but we still want to group our tests + # into classes. + "PLR6301", + + "PLR0917", # too-many-arguments + "PLC2701", # private import + "PLR0904", # too-many-public-methods +] +# Ignore unused import errors on __init__ files to avoid having to add either a noqa stament or an __all__ declaration. +"__init__.py" = ["F401"] +{% else %} [tool.pydocstyle] ignore = [ # Missing docstrings. @@ -89,6 +173,7 @@ ignore = [ {{ include("pydocstyle/ignores", indent=4) -}} {% endif %} ] +{% endif %} [tool.coverage.run] branch = true @@ -127,6 +212,7 @@ exclude_also = [ {% endif %} ] +{% if cookiecutter.get("linter") != "ruff" %} [tool.isort] multi_line_output = 3 include_trailing_comma = true @@ -221,6 +307,7 @@ good-names = [ [tool.pylint.reports] output-format = "colorized" score = "no" +{% endif %} [tool.mypy] allow_untyped_globals = true diff --git a/_shared/project/requirements/checkformatting.in b/_shared/project/requirements/checkformatting.in index e3a51ed..6c114cf 100644 --- a/_shared/project/requirements/checkformatting.in +++ b/_shared/project/requirements/checkformatting.in @@ -1,7 +1,11 @@ pip-tools pip-sync-faster +{% if cookiecutter.get("linter") == "ruff" %} +ruff +{% else %} black isort +{% endif %} {% if include_exists("requirements/checkformatting.in") %} {{- include("requirements/checkformatting.in") -}} {% endif %} diff --git a/_shared/project/requirements/format.in b/_shared/project/requirements/format.in index b076147..06a4b3b 100644 --- a/_shared/project/requirements/format.in +++ b/_shared/project/requirements/format.in @@ -1,7 +1,11 @@ pip-tools pip-sync-faster +{% if cookiecutter.get("linter") == "ruff" %} +ruff +{% else %} black isort +{% endif %} {% if include_exists("requirements/format.in") %} {{- include("requirements/format.in") -}} {% endif %} diff --git a/_shared/project/requirements/lint.in b/_shared/project/requirements/lint.in index 37371af..1414e88 100644 --- a/_shared/project/requirements/lint.in +++ b/_shared/project/requirements/lint.in @@ -2,10 +2,14 @@ pip-tools pip-sync-faster -r tests.txt -r functests.txt +{% if cookiecutter.get("linter") == "ruff" %} +ruff +{% else %} toml # Needed for pydocstyle to support pyproject.toml. pylint>=3.0.0 pydocstyle pycodestyle +{% endif %} {% if include_exists("requirements/lint.in") %} {{- include("requirements/lint.in") -}} {% endif %} diff --git a/_shared/project/tox.ini b/_shared/project/tox.ini index f58088b..91682e1 100644 --- a/_shared/project/tox.ini +++ b/_shared/project/tox.ini @@ -117,6 +117,13 @@ commands = {% endif %} {% if cookiecutter._directory in ['pyapp', 'pyramid-app'] %} dev: {posargs:supervisord -c conf/supervisord-dev.conf} + {% if cookiecutter.get("linter") == "ruff" %} + format: ruff check --select I --fix {{ cookiecutter.package_name}} tests bin + format: ruff format {{ cookiecutter.package_name}} tests bin + checkformatting: ruff check --select I {{ cookiecutter.package_name}} tests bin + checkformatting: ruff format --check {{ cookiecutter.package_name}} tests bin + lint: ruff check --preview -q {{ cookiecutter.package_name}} tests bin + {% else %} format: black {{ cookiecutter.package_name }} tests bin format: isort --atomic {{ cookiecutter.package_name }} tests bin checkformatting: black --check {{ cookiecutter.package_name }} tests bin @@ -125,6 +132,7 @@ commands = lint: pylint --rcfile=tests/pyproject.toml tests lint: pydocstyle {{ cookiecutter.package_name }} tests bin lint: pycodestyle {{ cookiecutter.package_name }} tests bin + {% endif %} {% else %} dev: {posargs:ipython --classic --no-banner --no-confirm-exit} format: black src tests bin diff --git a/pyramid-app/cookiecutter.json b/pyramid-app/cookiecutter.json index 19e447b..900a397 100644 --- a/pyramid-app/cookiecutter.json +++ b/pyramid-app/cookiecutter.json @@ -17,6 +17,7 @@ "__frontend_typechecking": "{{ cookiecutter.frontend }}", "postgres": ["no", "yes"], "docker": ["no", "yes"], + "linter": ["pylint", "ruff"], "__postgres_version": "15.3-alpine", "__postgres_port": "{{ random_port_number() }}", "__docker_namespace": "{{ cookiecutter.github_owner }}", diff --git a/pyramid-app/{{ cookiecutter.slug }}/{{ cookiecutter.package_name }}/scripts/init_db.py b/pyramid-app/{{ cookiecutter.slug }}/{{ cookiecutter.package_name }}/scripts/init_db.py index b19ef35..864d1cb 100755 --- a/pyramid-app/{{ cookiecutter.slug }}/{{ cookiecutter.package_name }}/scripts/init_db.py +++ b/pyramid-app/{{ cookiecutter.slug }}/{{ cookiecutter.package_name }}/scripts/init_db.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # mypy: disable-error-code="attr-defined" """ + Initialize the DB. Usage: @@ -8,7 +9,12 @@ python3 -m {{ cookiecutter.package_name }}.scripts.init_db --help """ + +{% if cookiecutter.get("linter") == "pylint" %} # pylint:disable=import-outside-toplevel,unused-import +{% else %} +# ruff: noqa: PLC0415, F401 +{% endif %} import argparse import logging from os import environ @@ -117,7 +123,10 @@ def main(): stamped = is_stamped(engine) if args.create: - if stamped: # pylint:disable=possibly-used-before-assignment + {% if cookiecutter.get("linter") == "pylint" %} + # pylint:disable=possibly-used-before-assignment + {% endif %} + if stamped: log.warning("Not creating tables because the DB is stamped by Alembic") else: create(engine)