From ede58519c051c08ddb986821a9b9a72e3fcf5f96 Mon Sep 17 00:00:00 2001 From: Marcos Prieto Date: Wed, 16 Oct 2024 15:49:18 +0200 Subject: [PATCH] WIP --- _shared/hooks/post_gen_project.py | 4 + _shared/project/pyproject.toml | 95 ++++++++++++++++++- .../project/requirements/checkformatting.in | 4 + _shared/project/requirements/format.in | 4 + _shared/project/requirements/lint.in | 4 + _shared/project/tox.ini | 8 ++ pyramid-app/cookiecutter.json | 1 + .../scripts/init_db.py | 17 +++- 8 files changed, 134 insertions(+), 3 deletions(-) diff --git a/_shared/hooks/post_gen_project.py b/_shared/hooks/post_gen_project.py index 6ad183a..1b2bdfc 100755 --- a/_shared/hooks/post_gen_project.py +++ b/_shared/hooks/post_gen_project.py @@ -43,6 +43,10 @@ def remove_conditional_files(): paths_to_remove.extend([".github/workflows/pypi.yml"]) {% endif %} + {% if cookiecutter.get("linter") != "ruff" %} + paths_to_remove.extend(["tests/pyporject.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 0d101ea..9094348 100644 --- a/_shared/project/pyproject.toml +++ b/_shared/project/pyproject.toml @@ -21,6 +21,88 @@ 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 + + "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 +] +{% else %} [tool.pydocstyle] ignore = [ # Missing docstrings. @@ -51,6 +133,7 @@ ignore = [ {{ include("pydocstyle/ignores", indent=4) -}} {% endif %} ] +{% endif %} [tool.coverage.run] branch = true @@ -77,7 +160,12 @@ show_missing = true precision = 2 fail_under = 100.00 skip_covered = true +exclude_also = [ + # # TYPE_CHECKING block is only executed while running mypy + "if TYPE_CHECKING:" +] +{% if cookiecutter.get("linter") != "ruff" %} [tool.isort] multi_line_output = 3 include_trailing_comma = true @@ -172,6 +260,7 @@ good-names = [ [tool.pylint.reports] output-format = "colorized" score = "no" +{% endif %} [tool.mypy] allow_untyped_globals = true @@ -180,6 +269,7 @@ pretty = true warn_unused_configs = true warn_redundant_casts = true warn_unused_ignores = true +check_untyped_defs = true disable_error_code = [ # https://mypy.readthedocs.io/en/stable/error_code_list.html#code-import-untyped @@ -190,9 +280,12 @@ disable_error_code = [ ] [[tool.mypy.overrides]] -module= [ +module = [ # Don't try to typecheck the tests for now "tests.*", + {% if include_exists("mypy/ignored_modules") %} + {{- include("mypy/ignored_modules", indent=4) -}} + {% endif %} ] ignore_errors = true {% if include_exists("pyproject.toml") %} 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..7130e66 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 lms tests bin + format: ruff format lms tests bin + checkformatting: ruff check --select I lms tests bin + checkformatting: ruff format --check lms tests bin + lint: ruff check --preview -q lms 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 fbc6062..3ba2ae6 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 249c5d9..fcf2e56 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 @@ -8,7 +8,11 @@ python3 -m {{ cookiecutter.package_name }}.scripts.init_db --help """ +{% if cookiecutter.get("linter") == "pylint" %} # pylint:disable=import-outside-toplevel,unused-import +{% else %} +# noqa: PLC0415 +{% endif %} import argparse import logging from os import environ @@ -48,7 +52,13 @@ def delete(engine: Engine) -> None: else: pre_delete(engine) - Base.metadata.drop_all(engine) + with engine.connect() as connection: + # Delete the DB "public" schema directly. + # We do this instead of using SQLAlchemy's drop_all because we want to delete all tables in the current DB. + # For example, this will delete tables created by migrations in other branches, not only the ones SQLAlchemy know about in the current code base. + connection.execute(text("DROP SCHEMA PUBLIC CASCADE;")) + connection.execute(text("CREATE SCHEMA PUBLIC;")) + connection.execute(text("COMMIT;")) try: from {{ cookiecutter.package_name }}.db import post_delete @@ -108,7 +118,10 @@ def main(): stamped = is_stamped(engine) if args.create: - if stamped: # pylint:disable=possibly-used-before-assignment + if stamped: + {% if cookiecutter.get("linter") == "pylint" %} + # pylint:disable=possibly-used-before-assignment + {% endif %} log.warning("Not creating tables because the DB is stamped by Alembic") else: create(engine)