diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/fastapi_template/template/{{cookiecutter.project_name}}/.pre-commit-config.yaml index c991436b..be0f1e3c 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/.pre-commit-config.yaml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/.pre-commit-config.yaml @@ -2,28 +2,6 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 - hooks: - - id: check-ast - - id: trailing-whitespace - - id: check-toml - - id: end-of-file-fixer - - - repo: https://github.com/asottile/add-trailing-comma - rev: v2.1.0 - hooks: - - id: add-trailing-comma - - - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.1.0 - hooks: - - id: pretty-format-yaml - args: - - --autofix - - --preserve-quotes - - --indent=2 - - repo: local hooks: @@ -33,6 +11,14 @@ repos: language: system types: [python] + - id: ruff + name: Check with Ruff + entry: poetry run ruff + language: system + pass_filenames: false + always_run: true + args: ["check", "{{cookiecutter.project_name}}", "tests", "--fix"] + - id: mypy name: Validate types with MyPy entry: poetry run mypy @@ -41,19 +27,3 @@ repos: pass_filenames: false args: - "{{cookiecutter.project_name}}" - - id: ruff - name: Validate project with Ruff - entry: poetry run ruff - language: system - types: [python] - pass_filenames: false - args: - - "{{cookiecutter.project_name}}" - - - id: ruff - name: Check with Ruff - entry: poetry run ruff - language: system - pass_filenames: false - always_run: true - args: ["check", "{{cookiecutter.project_name}}", "tests", "--fix"] diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml index ed6a54e4..4d0d8c11 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml @@ -244,17 +244,10 @@ src_folder = "./{{cookiecutter.project_name}}" {%- endif %} {%- endif %} -[fastapi-template.options] -{%- for key, value in cookiecutter.items() %} -{%- if not key.startswith("_") and not key == "db_info" %} -{{key}} = "{{value}}" -{%- endif %} -{%- endfor %} - [tool.ruff] # List of enabled rulsets. # See https://docs.astral.sh/ruff/rules/ for more information. -select = [ +lint.select = [ "E", # Error "F", # Pyflakes "W", # Pycodestyle @@ -281,9 +274,10 @@ select = [ "PL", # PyLint checks "RUF", # Specific to Ruff checks ] -ignore = [ +lint.ignore = [ "D105", # Missing docstring in magic method "D107", # Missing docstring in __init__ + "B008", # Do not perform function calls in argument defaults "D211", # No blank lines allowed before class docstring "D212", # Multi-line docstring summary should start at the first line "D401", # First line should be in imperative mood @@ -302,34 +296,28 @@ exclude = [ {%- endif %} ".venv/" ] -mccabe = { max-complexity = 10 } +lint.mccabe = { max-complexity = 10 } line-length = 88 -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "tests/*" = [ "S101", # Use of assert detected ] -[tool.ruff.pydocstyle] +[tool.ruff.lint.pydocstyle] convention = "pep257" ignore-decorators = ["typing.overload"] -[tool.ruff.pylint] +[tool.ruff.lint.pylint] allow-magic-value-types = ["int", "str", "float", "bytes"] -[tool.ruff.flake8-bugbear] -extend-immutable-calls = [ - "fastapi.Depends", - "fastapi.Query", - "fastapi.Path", - {%- if cookiecutter.enable_taskiq == "True" %} - "taskiq.TaskiqDepends", - {%- endif %} - {%- if cookiecutter.orm == "sqlalchemy" %} - "sqlalchemy.orm.joinedload", - {%- endif %} -] +[fastapi-template.options] +{%- for key, value in cookiecutter.items() %} +{%- if not key.startswith("_") and not key == "db_info" %} +{{key}} = "{{value}}" +{%- endif %} +{%- endfor %} [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/__main__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/__main__.py index d940af7f..3e83dcfe 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/__main__.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/__main__.py @@ -1,5 +1,6 @@ import os import shutil +from pathlib import Path import uvicorn @@ -25,8 +26,8 @@ def set_multiproc_dir() -> None: to avoid undefined behaviour. """ shutil.rmtree(settings.prometheus_dir, ignore_errors=True) - os.makedirs(settings.prometheus_dir, exist_ok=True) - os.environ["prometheus_multiproc_dir"] = str( + Path(settings.prometheus_dir).mkdir(parents=True) + os.environ["prometheus_multiproc_dir"] = str( # noqa: SIM112 settings.prometheus_dir.expanduser().absolute(), ) os.environ["PROMETHEUS_MULTIPROC_DIR"] = str( diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py index c150815b..59532644 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py @@ -341,18 +341,18 @@ async def setup_db() -> AsyncGenerator[None, None]: async def setup_db() -> AsyncGenerator[None, None]: """ Fixture to create database connection. - + :yield: nothing. """ client = AsyncIOMotorClient(settings.db_url.human_repr()) # type: ignore from {{cookiecutter.project_name}}.db.models import load_all_models # noqa: WPS433 await beanie.init_beanie( database=client[settings.db_base], - document_models=load_all_models(), + document_models=load_all_models(), # type: ignore ) yield - + {%- endif %} {%- if cookiecutter.enable_rmq == 'True' %} @@ -509,7 +509,7 @@ def fastapi_app( {%- if cookiecutter.enable_kafka == "True" %} application.dependency_overrides[get_kafka_producer] = lambda: test_kafka_producer {%- endif %} - return application # noqa: WPS331 + return application # noqa: RET504 @pytest.fixture diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py index 50c4bdc0..eac4acd6 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py @@ -4,11 +4,13 @@ from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel {%- endif %} +from beanie import Document +from typing import Type, Sequence -def load_all_models(): # type: ignore +def load_all_models() -> Sequence[Type[Document]]: """Load all models from this folder.""" # noqa: DAR201 return [ {%- if cookiecutter.add_dummy == "True" %} - DummyModel, # type: ignore + DummyModel, {%- endif %} ] diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/utils.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/utils.py index 18a531ce..c2ae2bf7 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/utils.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/utils.py @@ -1,5 +1,6 @@ import os +from pathlib import Path from sqlalchemy import text from sqlalchemy.engine import URL, create_engine, make_url from {{cookiecutter.project_name}}.settings import settings @@ -81,6 +82,6 @@ def create_database() -> None: def drop_database() -> None: """Drop current database.""" if settings.db_file.exists(): - os.remove(settings.db_file) + Path(settings.db_file).unlink() {%- endif %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_piccolo/app_conf.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_piccolo/app_conf.py index 33753ae7..7937a48b 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_piccolo/app_conf.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_piccolo/app_conf.py @@ -1,16 +1,14 @@ import os from piccolo.conf.apps import AppConfig, table_finder +from pathlib import Path -CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) +CURRENT_DIRECTORY = Path(__file__).parent APP_CONFIG = AppConfig( app_name='{{cookiecutter.project_name}}_db', - migrations_folder_path=os.path.join( - CURRENT_DIRECTORY, - 'migrations' - ), + migrations_folder_path=str(CURRENT_DIRECTORY / 'migrations'), table_classes=table_finder(modules=[ {%- if cookiecutter.add_dummy == "True" %} "{{cookiecutter.project_name}}.db.models.dummy_model" diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dao/dummy_dao.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dao/dummy_dao.py index fee89ed6..4f2b8a73 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dao/dummy_dao.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dao/dummy_dao.py @@ -14,7 +14,7 @@ class DummyDAO: def __init__( self, db_pool: AsyncConnectionPool[Any] = Depends(get_db_pool), - ): + ) -> None: self.db_pool = db_pool @@ -24,8 +24,7 @@ async def create_dummy_model(self, name: str) -> None: :param name: name of a dummy. """ - async with self.db_pool.connection() as connection: - async with connection.cursor(binary=True) as cur: + async with self.db_pool.connection() as connection, connection.cursor(binary=True) as cur: await cur.execute( "INSERT INTO dummy (name) VALUES (%(name)s);", params={ @@ -41,8 +40,7 @@ async def get_all_dummies(self, limit: int, offset: int) -> List[DummyModel]: :param offset: offset of dummies. :return: stream of dummies. """ - async with self.db_pool.connection() as connection: - async with connection.cursor( + async with self.db_pool.connection() as connection, connection.cursor( binary=True, row_factory=class_row(DummyModel) ) as cur: @@ -65,8 +63,7 @@ async def filter( :param name: name of dummy instance. :return: dummy models. """ - async with self.db_pool.connection() as connection: - async with connection.cursor( + async with self.db_pool.connection() as connection, connection.cursor( binary=True, row_factory=class_row(DummyModel) ) as cur: diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dao/dummy_dao.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dao/dummy_dao.py index 3bedd75e..98b97f25 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dao/dummy_dao.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/dao/dummy_dao.py @@ -10,7 +10,7 @@ class DummyDAO: """Class for accessing dummy table.""" - def __init__(self, session: AsyncSession = Depends(get_db_session)): + def __init__(self, session: AsyncSession = Depends(get_db_session)) -> None: self.session = session async def create_dummy_model(self, name: str) -> None: diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/utils.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/utils.py index e1c23d04..2d776070 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/utils.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_sa/utils.py @@ -4,6 +4,7 @@ from sqlalchemy.engine import URL, make_url from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine from sqlalchemy.orm import sessionmaker +from pathlib import Path from {{cookiecutter.project_name}}.settings import settings {% if cookiecutter.db_info.name == "postgresql" -%} @@ -83,6 +84,6 @@ async def create_database() -> None: async def drop_database() -> None: """Drop current database.""" if settings.db_file.exists(): - os.remove(settings.db_file) + Path(settings.db_file).unlink() {%- endif %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py index bbd0ad73..aab0b747 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py @@ -10,7 +10,7 @@ }, "apps": { "models": { - "models": MODELS_MODULES {%- if cookiecutter.enable_migrations == "True" %} + ["aerich.models"] {%- endif %} , + "models": {%- if cookiecutter.enable_migrations == "True" %} [*MODELS_MODULES, "aerich.models"] {%- else %} MODELS_MODULES {%- endif %} , "default_connection": "default", }, }, diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/gunicorn_runner.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/gunicorn_runner.py index c3691b0f..43b65a12 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/gunicorn_runner.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/gunicorn_runner.py @@ -20,7 +20,7 @@ class UvicornWorker(BaseUvicornWorker): to pass these parameters through gunicorn. """ - CONFIG_KWARGS = { # noqa: WPS115 (upper-case constant in a class) + CONFIG_KWARGS: dict[str, Any] = { # typing: ignore # noqa: RUF012 "loop": "uvloop" if uvloop is not None else "asyncio", "http": "httptools", "lifespan": "on", @@ -44,7 +44,7 @@ def __init__( # noqa: WPS211 (Too many args) port: int, workers: int, **kwargs: Any, - ): + ) -> None: self.options = { "bind": f"{host}:{port}", "workers": workers, diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py index bda77251..e62ec310 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py @@ -129,7 +129,7 @@ async def _setup_db(app: FastAPI) -> None: app.state.db_client = client await beanie.init_beanie( database=client[settings.db_base], - document_models=load_all_models(), + document_models=load_all_models(), # type: ignore ) {%- endif %} diff --git a/pyproject.toml b/pyproject.toml index 73ffd8ef..3b625db0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,8 @@ markers = [ "sqlite: tests for sqlite3.", ] env = [ - "POETRY_VIRTUALENVS_IN_PROJECT=True" + "POETRY_VIRTUALENVS_IN_PROJECT=True", + "PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring", ] testpaths = ["fastapi_template/tests"]