diff --git a/fastapi_template/cli.py b/fastapi_template/cli.py index 2e1a476..5cbeb00 100644 --- a/fastapi_template/cli.py +++ b/fastapi_template/cli.py @@ -158,7 +158,7 @@ def checker(ctx: BuilderContext) -> bool: ), additional_info=Database( name="mysql", - image="bitnami/mysql:8.0.30", + image="mysql:8.4", async_driver="mysql+aiomysql", driver_short="mysql", driver="mysql", @@ -178,7 +178,7 @@ def checker(ctx: BuilderContext) -> bool: ), additional_info=Database( name="postgresql", - image="postgres:13.8-bullseye", + image="postgres:16.3-bullseye", async_driver="postgresql+asyncpg", driver_short="postgres", driver="postgresql", @@ -274,7 +274,6 @@ def checker(ctx: BuilderContext) -> bool: code="ormar", user_view="Ormar", is_hidden=check_db(["sqlite", "mysql", "postgresql"]), - pydantic_v1=True, description=( "{what} is a great {feature} ORM.\n" "It's compatible with pydantic models and alembic migrator.".format( @@ -354,12 +353,6 @@ def checker(ctx: BuilderContext) -> bool: multiselect=True, before_ask=do_not_ask_features_if_quiet, entries=[ - MenuEntry( - code="pydanticv1", - cli_name="pydantic-v1", - user_view="Use older version of pydantic", - description="Use pydantic version ^1 instead of ^2", - ), MenuEntry( code="enable_redis", cli_name="redis", diff --git a/fastapi_template/input_model.py b/fastapi_template/input_model.py index 4f7d3a9..5884a72 100644 --- a/fastapi_template/input_model.py +++ b/fastapi_template/input_model.py @@ -29,7 +29,6 @@ class MenuEntry(BaseModel): description: str is_hidden: Optional[Callable[["BuilderContext"], bool]] = None additional_info: Any = None - pydantic_v1: bool = False @property def generated_name(self) -> str: @@ -159,8 +158,6 @@ def ask(self, context: "BuilderContext") -> Optional["BuilderContext"]: return setattr(context, self.code, chosen_entry.code) - if chosen_entry.pydantic_v1: - context.pydanticv1 = True return context @@ -240,10 +237,6 @@ def ask(self, context: "BuilderContext") -> Optional["BuilderContext"]: for entry in chosen_entries: setattr(context, entry.code, True) - for ch_entry in chosen_entries: - if ch_entry.pydantic_v1: - context.pydanticv1 = True - return context diff --git a/fastapi_template/template/cookiecutter.json b/fastapi_template/template/cookiecutter.json index 6c553a5..b94e950 100644 --- a/fastapi_template/template/cookiecutter.json +++ b/fastapi_template/template/cookiecutter.json @@ -59,9 +59,6 @@ "otlp_enabled": { "type": "bool" }, - "pydanticv1": { - "type": "bool" - }, "gunicorn": { "type": "bool" }, diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml b/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml index af81543..9e35a5b 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml @@ -3,48 +3,24 @@ name: Testing {{cookiecutter.project_name}} on: push jobs: - black: + lint: + strategy: + matrix: + cmd: + - black + - ruff + - mypy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.9' - - name: Install deps - uses: knowsuchagency/poetry-install@v1 - env: - POETRY_VIRTUALENVS_CREATE: false - - name: Run black check - run: poetry run black --check . - flake8: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.9' - - name: Install deps - uses: knowsuchagency/poetry-install@v1 - env: - POETRY_VIRTUALENVS_CREATE: false - - name: Run flake8 check - run: poetry run flake8 --count . - mypy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: '3.9' + python-version: '3.11' - name: Install deps - uses: knowsuchagency/poetry-install@v1 - env: - POETRY_VIRTUALENVS_CREATE: false - - name: Run mypy check - run: poetry run mypy . + run: poetry install + - name: Run lint check + run: poetry run pre-commit run -a {{ '${{' }} matrix.cmd {{ '}}' }} pytest: runs-on: ubuntu-latest {%- if ((cookiecutter.db_info.name != "none" and cookiecutter.db_info.name != "sqlite") or @@ -63,9 +39,8 @@ jobs: {%- endif %} {%- if cookiecutter.db_info.name == "mysql" %} MYSQL_ROOT_PASSWORD: "{{ cookiecutter.project_name }}" - MYSQL_ROOT_USER: "{{ cookiecutter.project_name }}" + MYSQL_USER: "{{ cookiecutter.project_name }}" MYSQL_DATABASE: "{{ cookiecutter.project_name }}" - MYSQL_AUTHENTICATION_PLUGIN: "mysql_native_password" {%- endif %} {%- if cookiecutter.db_info.name == "mongodb" %} MONGO_INITDB_ROOT_USERNAME: "{{ cookiecutter.project_name }}" @@ -73,7 +48,7 @@ jobs: {%- endif %} {%- if cookiecutter.db_info.name == "mysql" %} options: >- - --health-cmd="mysqladmin ping -u root" + --health-cmd="mysqladmin ping --user={{ cookiecutter.project_name }} --password={{ cookiecutter.project_name }}" --health-interval=15s --health-timeout=5s --health-retries=6 diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml index fd17467..6c63c75 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml @@ -130,17 +130,24 @@ services: hostname: {{cookiecutter.project_name}}-db restart: always environment: - MYSQL_ROOT_USER: "{{cookiecutter.project_name}}" + MYSQL_ROOT_USER: "root" MYSQL_ROOT_PASSWORD: "{{cookiecutter.project_name}}" + MYSQL_USER: "{{cookiecutter.project_name}}" + MYSQL_PASSWORD: "{{cookiecutter.project_name}}" MYSQL_DATABASE: "{{cookiecutter.project_name}}" - MYSQL_AUTHENTICATION_PLUGIN: "mysql_native_password" + MYSQL_HOST: "0.0.0.0" healthcheck: - test: mysqladmin ping -h localhost + test: + - CMD + - mysqladmin + - ping + - --user={{cookiecutter.project_name}} + - --password={{cookiecutter.project_name}} interval: 10s timeout: 5s retries: 40 volumes: - - {{cookiecutter.project_name}}-db-data:/bitnami/mysql/data + - "{{cookiecutter.project_name}}-db-data:/bitnami/mysql/data" {%- endif %} {%- if cookiecutter.enable_migrations == 'True' %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml index 4d0d8c1..9521afa 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml @@ -12,169 +12,165 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.9" -fastapi = "^0.100.0" -uvicorn = { version = "^0.22.0", extras = ["standard"] } +fastapi = "^0.111.0" +uvicorn = { version = "^0.30.1", extras = ["standard"] } {%- if cookiecutter.gunicorn == "True" %} -gunicorn = "^21.2.0" +gunicorn = "^22.0.0" {%- endif %} {%- if cookiecutter.add_users == "True" %} {%- if cookiecutter.orm == "sqlalchemy" %} -fastapi-users = "^12.1.2" -httpx-oauth = "^0.10.2" +fastapi-users = "^13.0.0" +httpx-oauth = "^0.14.1" fastapi-users-db-sqlalchemy = "^6.0.1" {%- endif %} {%- endif %} -{%- if cookiecutter.pydanticv1 == "True" %} -pydantic = { version = "^1", extras=["dotenv"] } -{%- else %} pydantic = "^2" pydantic-settings = "^2" -{%- endif %} -yarl = "^1.9.2" -ujson = "^5.8.0" +yarl = "^1" +ujson = "^5.10.0" {%- if cookiecutter.orm == "piccolo" %} {%- if cookiecutter.db_info.name == "postgresql" %} -piccolo = {version = "^0.117.0", extras = ["postgres"]} +piccolo = {version = "^1.13.0", extras = ["postgres"]} {%- elif cookiecutter.db_info.name == "sqlite" %} -piccolo = {version = "^0.117.0", extras = ["sqlite"]} +piccolo = {version = "^1.13.0", extras = ["sqlite"]} {%- endif %} {%- endif %} {%- if cookiecutter.orm == "sqlalchemy" %} -SQLAlchemy = {version = "^2.0.18", extras = ["asyncio"]} +SQLAlchemy = {version = "^2.0.31", extras = ["asyncio"]} {%- if cookiecutter.enable_migrations == "True" %} -alembic = "^1.11.1" +alembic = "^1.13.2" {%- endif %} {%- if cookiecutter.db_info.name == "postgresql" %} -asyncpg = {version = "^0.28.0", extras = ["sa"]} +asyncpg = {version = "^0.29.0", extras = ["sa"]} {%- elif cookiecutter.db_info.name == "sqlite" %} -aiosqlite = "^0.19.0" +aiosqlite = "^0.20.0" {%- elif cookiecutter.db_info.name == "mysql" %} aiomysql = "^0.2.0" -mysqlclient = "^2.2.0" +mysqlclient = "^2.2.4" {%- endif %} {%- endif %} {%- if cookiecutter.orm == "tortoise" %} -tortoise-orm = "^0.19.3" +tortoise-orm = "^0.21.3" {%- if cookiecutter.enable_migrations == "True" %} -aerich = "^0.7.1" +aerich = "^0.7.2" {%- endif %} {%- if cookiecutter.db_info.name == "postgresql" %} -asyncpg = "^0.28.0" +asyncpg = "^0.29.0" {%- elif cookiecutter.db_info.name == "sqlite" %} -aiosqlite = "<0.19.0" +aiosqlite = "<0.20.0" {%- elif cookiecutter.db_info.name == "mysql" %} aiomysql = "^0.2.0" -mysqlclient = "^2.2.0" -cryptography = "^41.0.1" +mysqlclient = "^2.2.4" +cryptography = "^42.0.8" {%- endif %} {%- endif %} {%- if cookiecutter.orm == "ormar" %} -ormar = "^0.12.2" +ormar = "^0.20.1" {%- if cookiecutter.enable_migrations == "True" %} -alembic = "^1.11.1" +alembic = "^1.13.2" {%- endif %} {%- if cookiecutter.db_info.name == "postgresql" %} -asyncpg = "^0.28.0" -psycopg2-binary = "^2.9.6" +asyncpg = "^0.29.0" +psycopg2-binary = "^2.9.9" {%- elif cookiecutter.db_info.name == "sqlite" %} -aiosqlite = "^0.19.0" +aiosqlite = "^0.20.0" {%- elif cookiecutter.db_info.name == "mysql" %} aiomysql = "^0.2.0" -mysqlclient = "^2.2.0" +mysqlclient = "^2.2.4" {%- endif %} {%- endif %} {%- if cookiecutter.enable_redis == "True" %} -redis = {version = "^4.6.0", extras = ["hiredis"]} +redis = {version = "^5.0.7", extras = ["hiredis"]} {%- endif %} {%- if cookiecutter.self_hosted_swagger == 'True' %} -aiofiles = "^23.1.0" +aiofiles = "^24.1.0" {%- endif %} {%- if cookiecutter.orm == "psycopg" %} -psycopg = { version = "^3.1.9", extras = ["binary", "pool"] } +psycopg = { version = "^3.1.19", extras = ["binary", "pool"] } {%- endif %} -httptools = "^0.6.0" +httptools = "^0.6.1" {%- if cookiecutter.orm == "beanie" %} -beanie = "^1.21.0" +beanie = "^1.26.0" {%- else %} -pymongo = "^4.5.0" +pymongo = "^4.8.0" {%- endif %} {%- if cookiecutter.api_type == "graphql" %} -strawberry-graphql = { version = "^0.194.4", extras = ["fastapi"] } +strawberry-graphql = { version = "^0.235.1", extras = ["fastapi"] } {%- endif %} {%- if cookiecutter.enable_rmq == "True" %} -aio-pika = "^9.1.4" +aio-pika = "^9.4.1" {%- endif %} {%- if cookiecutter.prometheus_enabled == "True" %} -prometheus-client = "^0.17.0" -prometheus-fastapi-instrumentator = "6.0.0" +prometheus-client = "^0.20.0" +prometheus-fastapi-instrumentator = "7.0.0" {%- endif %} {%- if cookiecutter.sentry_enabled == "True" %} -sentry-sdk = "^1.27.1" +sentry-sdk = "^2.7.1" {%- endif %} {%- if cookiecutter.otlp_enabled == "True" %} -opentelemetry-api = "^1.18.0" -opentelemetry-sdk = "^1.18.0" -opentelemetry-exporter-otlp = "^1.18.0" -opentelemetry-instrumentation = "^0.39b0" -opentelemetry-instrumentation-fastapi = "^0.39b0" +opentelemetry-api = "^1.25.0" +opentelemetry-sdk = "^1.25.0" +opentelemetry-exporter-otlp = "^1.25.0" +opentelemetry-instrumentation = "^0.46b0" +opentelemetry-instrumentation-fastapi = "^0.46b0" {%- if cookiecutter.enable_loguru != "True" %} -opentelemetry-instrumentation-logging = "^0.39b0" +opentelemetry-instrumentation-logging = "^0.46b0" {%- endif %} {%- if cookiecutter.enable_redis == "True" %} -opentelemetry-instrumentation-redis = "^0.39b0" +opentelemetry-instrumentation-redis = "^0.46b0" {%- endif %} {%- if cookiecutter.db_info.name == "postgresql" and cookiecutter.orm in ["ormar", "tortoise"] %} -opentelemetry-instrumentation-asyncpg = "^0.39b0" +opentelemetry-instrumentation-asyncpg = "^0.46b0" {%- endif %} {%- if cookiecutter.orm == "sqlalchemy" %} -opentelemetry-instrumentation-sqlalchemy = "^0.39b0" +opentelemetry-instrumentation-sqlalchemy = "^0.46b0" {%- endif %} {%- if cookiecutter.enable_rmq == "True" %} -opentelemetry-instrumentation-aio-pika = "^0.39b0" +opentelemetry-instrumentation-aio-pika = "^0.46b0" {%- endif %} {%- endif %} {%- if cookiecutter.enable_loguru == "True" %} -loguru = "^0.7.0" +loguru = "^0" {%- endif %} {%- if cookiecutter.enable_kafka == "True" %} -aiokafka = "^0.8.1" +aiokafka = "^0" {%- endif %} {%- if cookiecutter.enable_taskiq == "True" %} taskiq = "^0" taskiq-fastapi = "^0" {%- if cookiecutter.enable_redis == "True" %} -taskiq-redis = "^0" + taskiq-redis = "^1" {%- endif %} {%- if cookiecutter.enable_rmq == "True" %} -taskiq-aio-pika = "^0" + taskiq-aio-pika = "^0" {%- endif %} {%- if (cookiecutter.enable_rmq or cookiecutter.enable_rmq) != "True" %} -pyzmq = "^25" + pyzmq = "^26" {%- endif %} {%- endif %} -[tool.poetry.dev-dependencies] -pytest = "^7.2.1" -ruff = "^0" -mypy = "^1.1.1" -pre-commit = "^3.0.1" -black = "^22.12.0" -pytest-cov = "^4.0.0" -anyio = "^3.6.2" -pytest-env = "^0.8.1" +[tool.poetry.group.dev.dependencies] +pytest = "^8" +ruff = "^0.5.0" +mypy = "^1.10.1" +pre-commit = "^3.7.1" +black = "^24.4.2" +pytest-cov = "^5" +anyio = "^4" +pytest-env = "^1.1.3" {%- if cookiecutter.enable_redis == "True" %} -fakeredis = "^2.5.0" +fakeredis = "^2.23.3" {%- endif %} {%- if cookiecutter.orm == "tortoise" %} asynctest = "^0.13.0" -nest-asyncio = "^1.5.6" +nest-asyncio = "^1.6.0" {%- endif %} -httpx = "^0.23.3" +httpx = "^0.27.0" {%- if cookiecutter.enable_taskiq == "True" %} taskiq = { version = "^0", extras = ["reload"] } {%- endif %} @@ -227,6 +223,9 @@ env = [ {%- else %} "{{cookiecutter.project_name | upper}}_DB_BASE={{cookiecutter.project_name}}_test", {%- endif %} + {%- if cookiecutter.db_info.name == "mysql" %} + "{{cookiecutter.project_name | upper}}_DB_USER=root" + {%- endif %} {%- if cookiecutter.orm == "piccolo" %} "PICCOLO_CONF={{cookiecutter.project_name}}.piccolo_conf", {%- endif %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/tests/conftest.py b/fastapi_template/template/{{cookiecutter.project_name}}/tests/conftest.py index 5953264..b71f514 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/tests/conftest.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/tests/conftest.py @@ -52,7 +52,7 @@ nest_asyncio.apply() {%- elif cookiecutter.orm == "ormar" %} from sqlalchemy.engine import create_engine -from {{cookiecutter.project_name}}.db.config import database +from {{cookiecutter.project_name}}.db.base import database from {{cookiecutter.project_name}}.db.utils import create_database, drop_database {%- elif cookiecutter.orm == "psycopg" %} @@ -168,7 +168,7 @@ async def initialize_db() -> AsyncGenerator[None, None]: :yield: new engine. """ - from {{cookiecutter.project_name}}.db.meta import meta # noqa: WPS433 + from {{cookiecutter.project_name}}.db.base import meta # noqa: WPS433 from {{cookiecutter.project_name}}.db.models import load_all_models # noqa: WPS433 load_all_models() @@ -192,8 +192,8 @@ async def initialize_db() -> AsyncGenerator[None, None]: async def drop_db() -> None: """Drops database after tests.""" - pool = AsyncConnectionPool(conninfo=str(settings.db_url.with_path("/postgres"))) - await pool.wait() + pool = AsyncConnectionPool(conninfo=str(settings.db_url.with_path("/postgres")), open=False) + await pool.open(wait=True) async with pool.connection() as conn: await conn.set_autocommit(True) await conn.execute( @@ -213,8 +213,8 @@ async def drop_db() -> None: async def create_db() -> None: # noqa: WPS217 """Creates database for tests.""" - pool = AsyncConnectionPool(conninfo=str(settings.db_url.with_path("/postgres"))) - await pool.wait() + pool = AsyncConnectionPool(conninfo=str(settings.db_url.with_path("/postgres")), open=False) + await pool.open(wait=True) async with pool.connection() as conn_check: res = await conn_check.execute( "SELECT 1 FROM pg_database WHERE datname=%(dbname)s", @@ -268,8 +268,8 @@ async def dbpool() -> AsyncGenerator[AsyncConnectionPool[Any], None]: :yield: database connections pool. """ await create_db() - pool = AsyncConnectionPool(conninfo=str(settings.db_url)) - await pool.wait() + pool = AsyncConnectionPool(conninfo=str(settings.db_url), open=False) + await pool.open(wait=True) async with pool.connection() as create_conn: await create_tables(create_conn) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/base.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/base.py index 286c883..0ad8273 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/base.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/base.py @@ -1,10 +1,10 @@ -from ormar import ModelMeta -from {{cookiecutter.project_name}}.db.config import database -from {{cookiecutter.project_name}}.db.meta import meta +import sqlalchemy as sa +from databases import Database +from ormar import OrmarConfig +from {{cookiecutter.project_name}}.settings import settings +meta = sa.MetaData() +database = Database(str(settings.db_url)) -class BaseMeta(ModelMeta): - """Base metadata for models.""" +ormar_config = OrmarConfig(metadata=meta, database=database) - database = database - metadata = meta diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/config.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/config.py deleted file mode 100644 index 05e67ed..0000000 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/config.py +++ /dev/null @@ -1,4 +0,0 @@ -from databases import Database -from {{cookiecutter.project_name}}.settings import settings - -database = Database(str(settings.db_url)) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/meta.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/meta.py deleted file mode 100644 index 4c95254..0000000 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/meta.py +++ /dev/null @@ -1,3 +0,0 @@ -import sqlalchemy as sa - -meta = sa.MetaData() diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/migrations/env.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/migrations/env.py index c7f99b2..6843d38 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/migrations/env.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/migrations/env.py @@ -2,7 +2,7 @@ from alembic import context from sqlalchemy.engine import Connection, create_engine -from {{cookiecutter.project_name}}.db.meta import meta +from {{cookiecutter.project_name}}.db.base import meta from {{cookiecutter.project_name}}.db.models import load_all_models from {{cookiecutter.project_name}}.settings import settings diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/models/dummy_model.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/models/dummy_model.py index 9dfbfc8..e028f40 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/models/dummy_model.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_ormar/models/dummy_model.py @@ -1,12 +1,10 @@ import ormar -from {{cookiecutter.project_name}}.db.base import BaseMeta +from {{cookiecutter.project_name}}.db.base import ormar_config class DummyModel(ormar.Model): """Model for demo purpose.""" - - class Meta(BaseMeta): - tablename = "dummy_model" + ormar_config = ormar_config.copy(tablename="dummy_model") id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=200) # noqa: WPS432 diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py index ec3ab54..d28b122 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py @@ -4,14 +4,8 @@ from tempfile import gettempdir from typing import List, Optional -{%- if cookiecutter.pydanticv1 == "True" %} -from pydantic import BaseSettings - -{%- else %} from pydantic_settings import BaseSettings, SettingsConfigDict -{%- endif %} - from yarl import URL TEMP_DIR = Path(gettempdir()) @@ -44,7 +38,7 @@ class Settings(BaseSettings): # Current environment environment: str = "dev" - + log_level: LogLevel = LogLevel.INFO {%- if cookiecutter.add_users == "True" %} @@ -210,19 +204,11 @@ def rabbit_url(self) -> URL: ) {%- endif %} - {%- if cookiecutter.pydanticv1 == "True" %} - class Config: - env_file = ".env" - env_prefix = "{{cookiecutter.project_name | upper }}_" - env_file_encoding = "utf-8" - - {%- else %} model_config = SettingsConfigDict( env_file = ".env", env_prefix = "{{cookiecutter.project_name | upper }}_", env_file_encoding = "utf-8", ) - {%- endif %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tkq.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tkq.py index faa9043..97489ff 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tkq.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tkq.py @@ -1,5 +1,6 @@ +from typing import Any import taskiq_fastapi -from taskiq import InMemoryBroker, ZeroMQBroker +from taskiq import InMemoryBroker, ZeroMQBroker, AsyncBroker, AsyncResultBackend from {{cookiecutter.project_name}}.settings import settings {%- if cookiecutter.enable_redis == "True" %} @@ -13,22 +14,22 @@ {%- endif %} {%- if cookiecutter.enable_redis == "True" %} -result_backend = RedisAsyncResultBackend( +result_backend: AsyncResultBackend[Any] = RedisAsyncResultBackend( redis_url=str(settings.redis_url.with_path("/1")), ) {%- endif %} {%- if cookiecutter.enable_rmq == "True" %} -broker = AioPikaBroker( +broker: AsyncBroker = AioPikaBroker( str(settings.rabbit_url), ){%- if cookiecutter.enable_redis == "True" %}.with_result_backend(result_backend){%- endif %} {%- elif cookiecutter.enable_redis == "True" %} -broker = ListQueueBroker( +broker: AsyncBroker = ListQueueBroker( str(settings.redis_url.with_path("/1")), ).with_result_backend(result_backend) {%- else %} -broker = ZeroMQBroker() +broker: AsyncBroker = ZeroMQBroker() {%- endif %} if settings.environment.lower() == "pytest": diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py index 82238eb..5c7d133 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py @@ -1,15 +1,8 @@ from pydantic import BaseModel -{%- if cookiecutter.pydanticv1 != "True" %} from pydantic import ConfigDict - -{%- endif %} {%- if cookiecutter.db_info.name == "mongodb" %} -{%- if cookiecutter.pydanticv1 == "True" %} -from pydantic import validator, Field -{%- else %} from pydantic import field_validator -{%- endif %} from bson import ObjectId {%- endif %} @@ -24,20 +17,12 @@ class DummyModelDTO(BaseModel): {%- if cookiecutter.db_info.name != "mongodb" %} id: int {%- else %} - {%- if cookiecutter.pydanticv1 == "True" %} - id: str = Field(alias="_id") - {%- else %} id: str {%- endif %} - {%- endif %} name: str {%- if cookiecutter.db_info.name == "mongodb" %} - {%- if cookiecutter.pydanticv1 == "True" %} - @validator("id", pre=True) - {%- else %} @field_validator("id", mode="before") - {%- endif %} @classmethod def parse_object_id(cls, document_id: ObjectId) -> str: """ @@ -49,13 +34,7 @@ def parse_object_id(cls, document_id: ObjectId) -> str: return str(document_id) {%- endif %} - - {%- if cookiecutter.pydanticv1 == "True" %} - class Config: - orm_mode = True - {%- else %} model_config = ConfigDict(from_attributes=True) - {%- endif %} class DummyModelInputDTO(BaseModel): """DTO for creating new dummy model.""" 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 e62ec31..34f2d6a 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 @@ -35,11 +35,11 @@ {%- if cookiecutter.orm == "ormar" %} -from {{cookiecutter.project_name}}.db.config import database +from {{cookiecutter.project_name}}.db.base import database {%- if cookiecutter.db_info.name != "none" and cookiecutter.enable_migrations != "True" %} from sqlalchemy.engine import create_engine -from {{cookiecutter.project_name}}.db.meta import meta +from {{cookiecutter.project_name}}.db.base import meta from {{cookiecutter.project_name}}.db.models import load_all_models {%- endif %} @@ -86,8 +86,8 @@ async def _setup_db(app: FastAPI) -> None: :param app: current FastAPI app. """ - app.state.db_pool = psycopg_pool.AsyncConnectionPool(conninfo=str(settings.db_url)) - await app.state.db_pool.wait() + app.state.db_pool = psycopg_pool.AsyncConnectionPool(conninfo=str(settings.db_url), open=False) + await app.state.db_pool.open(wait=True) {%- endif %} {%- if cookiecutter.orm == "sqlalchemy" %} diff --git a/fastapi_template/tests/conftest.py b/fastapi_template/tests/conftest.py index 9eae3b7..a4e464e 100644 --- a/fastapi_template/tests/conftest.py +++ b/fastapi_template/tests/conftest.py @@ -3,6 +3,7 @@ import shutil import tempfile from pathlib import Path +from typing import Generator import pytest from faker import Faker @@ -26,7 +27,7 @@ def project_name(worker_id: str) -> str: @pytest.fixture(scope="session", autouse=True) -def generator_start_dir() -> str: +def generator_start_dir() -> Generator[str, None, None]: """ Generate directory to work into @@ -74,7 +75,7 @@ def default_context(project_name: str) -> None: @pytest.fixture(autouse=True) -def default_dir(generator_start_dir: str) -> None: +def default_dir(generator_start_dir: str) -> Generator[None, None, None]: """ Change directory to generator_start_dir. @@ -87,7 +88,9 @@ def default_dir(generator_start_dir: str) -> None: @pytest.fixture(autouse=True) -def docker_module_shutdown(generator_start_dir: str, project_name: str) -> None: +def docker_module_shutdown( + generator_start_dir: str, project_name: str +) -> Generator[None, None, None]: """ Cleans up docker context. diff --git a/fastapi_template/tests/test_generator.py b/fastapi_template/tests/test_generator.py index ffdbf18..38785a1 100644 --- a/fastapi_template/tests/test_generator.py +++ b/fastapi_template/tests/test_generator.py @@ -2,7 +2,7 @@ import pytest -from fastapi_template.cli import db_menu, orm_menu +from fastapi_template.cli import db_menu from fastapi_template.input_model import BuilderContext from fastapi_template.tests.utils import model_dump_compat, run_default_check @@ -17,23 +17,16 @@ def init_context( for entry in db_menu.entries: if entry.code == db: db_info = model_dump_compat(entry.additional_info) - if entry.pydantic_v1: - context.pydanticv1 = True + if db_info is None: raise ValueError(f"Unknown database: {db}") context.db = db context.db_info = db_info context.orm = orm - for entry in orm_menu.entries: - if entry.code == orm: - if entry.pydantic_v1: - context.pydanticv1 = True if api is not None: context.api_type = api - if api == "graphql": - context.pydanticv1 = True context.enable_migrations = db != "none" context.add_dummy = db != "none" @@ -41,8 +34,8 @@ def init_context( return context -def test_default_without_db(default_context: BuilderContext): - run_default_check(init_context(default_context, "none", None)) +def test_default_without_db(default_context: BuilderContext, worker_id: str): + run_default_check(init_context(default_context, "none", None), worker_id) @pytest.mark.parametrize( @@ -62,10 +55,10 @@ def test_default_without_db(default_context: BuilderContext): "piccolo", ], ) -def test_default_with_db(default_context: BuilderContext, db: str, orm: str): +def test_default_with_db(default_context: BuilderContext, db: str, orm: str, worker_id: str): if orm == "piccolo" and db == "mysql": return - run_default_check(init_context(default_context, db, orm)) + run_default_check(init_context(default_context, db, orm), worker_id) @pytest.mark.parametrize( @@ -80,8 +73,8 @@ def test_default_with_db(default_context: BuilderContext, db: str, orm: str): "beanie", ], ) -def test_default_with_nosql_db(default_context: BuilderContext, db: str, orm: str): - run_default_check(init_context(default_context, db, orm)) +def test_default_with_nosql_db(default_context: BuilderContext, db: str, orm: str, worker_id: str): + run_default_check(init_context(default_context, db, orm), worker_id) @pytest.mark.parametrize("api", ["rest", "graphql"]) @@ -94,8 +87,8 @@ def test_default_with_nosql_db(default_context: BuilderContext, db: str, orm: st "piccolo", ], ) -def test_default_for_apis(default_context: BuilderContext, orm: str, api: str): - run_default_check(init_context(default_context, "postgresql", orm, api)) +def test_default_for_apis(default_context: BuilderContext, orm: str, api: str, worker_id: str): + run_default_check(init_context(default_context, "postgresql", orm, api), worker_id) @pytest.mark.parametrize("api", ["rest", "graphql"]) @@ -105,8 +98,8 @@ def test_default_for_apis(default_context: BuilderContext, orm: str, api: str): "beanie", ] ) -def test_default_for_apis_with_nosql_db(default_context: BuilderContext, orm: str, api: str): - run_default_check(init_context(default_context, "mongodb", orm, api)) +def test_default_for_apis_with_nosql_db(default_context: BuilderContext, orm: str, api: str, worker_id: str): + run_default_check(init_context(default_context, "mongodb", orm, api), worker_id) @pytest.mark.parametrize( @@ -115,8 +108,8 @@ def test_default_for_apis_with_nosql_db(default_context: BuilderContext, orm: st "psycopg", ], ) -def test_pg_drivers(default_context: BuilderContext, orm: str): - run_default_check(init_context(default_context, "postgresql", orm)) +def test_pg_drivers(default_context: BuilderContext, orm: str, worker_id: str): + run_default_check(init_context(default_context, "postgresql", orm), worker_id) @pytest.mark.parametrize( @@ -129,16 +122,16 @@ def test_pg_drivers(default_context: BuilderContext, orm: str): "piccolo", ], ) -def test_without_routers(default_context: BuilderContext, orm: str): +def test_without_routers(default_context: BuilderContext, orm: str, worker_id: str): context = init_context(default_context, "postgresql", orm) context.enable_routers = False - run_default_check(context) + run_default_check(context, worker_id) -def test_without_routers_with_nosql_db(default_context: BuilderContext): +def test_without_routers_with_nosql_db(default_context: BuilderContext, worker_id: str): context = init_context(default_context, "mongodb", "beanie") context.enable_routers = False - run_default_check(context) + run_default_check(context, worker_id) @pytest.mark.parametrize( @@ -150,21 +143,21 @@ def test_without_routers_with_nosql_db(default_context: BuilderContext): "piccolo", ], ) -def test_without_migrations(default_context: BuilderContext, orm: str): +def test_without_migrations(default_context: BuilderContext, orm: str, worker_id: str): context = init_context(default_context, "postgresql", orm) context.enable_migrations = False - run_default_check(context) + run_default_check(context, worker_id) -def test_without_migrations_with_nosql_db(default_context: BuilderContext): +def test_without_migrations_with_nosql_db(default_context: BuilderContext, worker_id: str): context = init_context(default_context, "mongodb", "beanie") context.enable_migrations = False - run_default_check(context) + run_default_check(context, worker_id) -def test_with_selfhosted_swagger(default_context: BuilderContext): +def test_with_selfhosted_swagger(default_context: BuilderContext, worker_id: str): default_context.self_hosted_swagger = True - run_default_check(default_context) + run_default_check(default_context, worker_id) @pytest.mark.parametrize( @@ -177,16 +170,16 @@ def test_with_selfhosted_swagger(default_context: BuilderContext): "piccolo", ], ) -def test_without_dummy(default_context: BuilderContext, orm: str): +def test_without_dummy(default_context: BuilderContext, orm: str, worker_id: str): context = init_context(default_context, "postgresql", orm) context.add_dummy = False - run_default_check(context) + run_default_check(context, worker_id) -def test_without_dummy_with_nosql_db(default_context: BuilderContext): +def test_without_dummy_with_nosql_db(default_context: BuilderContext, worker_id: str): context = init_context(default_context, "mongodb", "beanie") context.add_dummy = False - run_default_check(context) + run_default_check(context, worker_id) @pytest.mark.parametrize( @@ -196,13 +189,11 @@ def test_without_dummy_with_nosql_db(default_context: BuilderContext): "graphql", ], ) -def test_redis(default_context: BuilderContext, api: str): +def test_redis(default_context: BuilderContext, api: str, worker_id: str): default_context.enable_redis = True default_context.enable_taskiq = True default_context.api_type = api - if api == "graphql": - default_context.pydanticv1 = True - run_default_check(default_context) + run_default_check(default_context, worker_id) @pytest.mark.parametrize( @@ -212,28 +203,26 @@ def test_redis(default_context: BuilderContext, api: str): "graphql", ], ) -def test_rmq(default_context: BuilderContext, api: str): +def test_rmq(default_context: BuilderContext, api: str, worker_id: str): default_context.enable_rmq = True default_context.enable_taskiq = True default_context.api_type = api - if api == "graphql": - default_context.pydanticv1 = True - run_default_check(default_context) + run_default_check(default_context, worker_id) -def test_telemetry_pre_commit(default_context: BuilderContext): +def test_telemetry_pre_commit(default_context: BuilderContext, worker_id: str): default_context.enable_rmq = True default_context.enable_redis = True default_context.prometheus_enabled = True default_context.otlp_enabled = True default_context.sentry_enabled = True default_context.enable_loguru = True - run_default_check(default_context, without_pytest=True) + run_default_check(default_context, worker_id, without_pytest=True) -def test_gunicorn(default_context: BuilderContext): +def test_gunicorn(default_context: BuilderContext, worker_id: str): default_context.gunicorn = True - run_default_check(default_context, without_pytest=True) + run_default_check(default_context, worker_id, without_pytest=True) # @pytest.mark.parametrize("api", ["rest", "graphql"]) diff --git a/fastapi_template/tests/utils.py b/fastapi_template/tests/utils.py index 99eb75f..dfab3bb 100644 --- a/fastapi_template/tests/utils.py +++ b/fastapi_template/tests/utils.py @@ -40,12 +40,13 @@ def run_docker_compose_command( return subprocess.run(docker_command) -def run_default_check(context: BuilderContext, without_pytest=False): +def run_default_check(context: BuilderContext, worker_id: str, without_pytest=False): generate_project_and_chdir(context) compose = Path("./deploy/docker-compose.yml") with compose.open("r") as compose_file: data = yaml.safe_load(compose_file) data['services']['api']['build'].pop('target', None) + data['services']['api']['image'] = f"test_image:v{worker_id}" with compose.open("w") as compose_file: yaml.safe_dump(data, compose_file) diff --git a/pyproject.toml b/pyproject.toml index 55d3049..51a0503 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,8 +42,8 @@ env = [ "PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring", ] testpaths = ["fastapi_template/tests"] -retries = 2 -retry_delay = 0.5 +retries = 3 +retry_delay = 2 cumulative_timing = true [tool.poetry.scripts]