From ef5da867a6f562cc8b59a9bb930b71413c9b7c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=84riks=20K?= Date: Sat, 5 Apr 2025 13:15:41 +0300 Subject: [PATCH] Improve template (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created a README file - Added PyCharm/IntelliJ IDE run configuration - Squashed bugs related to: - Code style - Default Postgre database URL schema - Updated project dependencies Reviewed-on: https://git.72.lv/eriks/FastAPI-DB-Cookiecutter/pulls/1 Co-authored-by: Ēriks K Co-committed-by: Ēriks K --- {{ cookiecutter.project_slug }}/.gitignore | 3 +- .../.idea/.gitignore | 8 ++++ .../inspectionProfiles/Project_Default.xml | 6 +++ .../inspectionProfiles/profiles_settings.xml | 6 +++ .../.idea/misc.xml | 7 ++++ .../.idea/modules.xml | 8 ++++ .../runConfigurations/Create_migration.xml | 26 +++++++++++++ .../.idea/runConfigurations/Run.xml | 18 +++++++++ .../runConfigurations/Run_Migrations.xml | 26 +++++++++++++ {{ cookiecutter.project_slug }}/.idea/vcs.xml | 6 +++ .../.idea/{{ cookiecutter.project_slug }}.iml | 14 +++++++ {{ cookiecutter.project_slug }}/Dockerfile | 10 ++--- {{ cookiecutter.project_slug }}/ENVIRON.md | 2 +- {{ cookiecutter.project_slug }}/README.md | 37 +++++++++++++++++++ .../envs.example.yml | 4 +- .../requirements/base.txt | 24 ++++++------ .../requirements/local.txt | 36 +++++++++--------- .../requirements/production.txt | 2 +- .../service/config/_settings.py | 2 +- .../service/crud/user/methods.py | 7 ++-- .../service/crud/utils.py | 4 +- 21 files changed, 210 insertions(+), 46 deletions(-) create mode 100644 {{ cookiecutter.project_slug }}/.idea/.gitignore create mode 100644 {{ cookiecutter.project_slug }}/.idea/inspectionProfiles/Project_Default.xml create mode 100644 {{ cookiecutter.project_slug }}/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 {{ cookiecutter.project_slug }}/.idea/misc.xml create mode 100644 {{ cookiecutter.project_slug }}/.idea/modules.xml create mode 100644 {{ cookiecutter.project_slug }}/.idea/runConfigurations/Create_migration.xml create mode 100644 {{ cookiecutter.project_slug }}/.idea/runConfigurations/Run.xml create mode 100644 {{ cookiecutter.project_slug }}/.idea/runConfigurations/Run_Migrations.xml create mode 100644 {{ cookiecutter.project_slug }}/.idea/vcs.xml create mode 100644 {{ cookiecutter.project_slug }}/.idea/{{ cookiecutter.project_slug }}.iml create mode 100644 {{ cookiecutter.project_slug }}/README.md diff --git a/{{ cookiecutter.project_slug }}/.gitignore b/{{ cookiecutter.project_slug }}/.gitignore index bdaff43..dab990d 100644 --- a/{{ cookiecutter.project_slug }}/.gitignore +++ b/{{ cookiecutter.project_slug }}/.gitignore @@ -175,4 +175,5 @@ dmypy.json .pyre/ .pytype/ cython_debug/ -cookiecutter.zip \ No newline at end of file +cookiecutter.zip +envs.yml diff --git a/{{ cookiecutter.project_slug }}/.idea/.gitignore b/{{ cookiecutter.project_slug }}/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/{{ cookiecutter.project_slug }}/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/{{ cookiecutter.project_slug }}/.idea/inspectionProfiles/Project_Default.xml b/{{ cookiecutter.project_slug }}/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/{{ cookiecutter.project_slug }}/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/{{ cookiecutter.project_slug }}/.idea/inspectionProfiles/profiles_settings.xml b/{{ cookiecutter.project_slug }}/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/{{ cookiecutter.project_slug }}/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/{{ cookiecutter.project_slug }}/.idea/misc.xml b/{{ cookiecutter.project_slug }}/.idea/misc.xml new file mode 100644 index 0000000..f783827 --- /dev/null +++ b/{{ cookiecutter.project_slug }}/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/{{ cookiecutter.project_slug }}/.idea/modules.xml b/{{ cookiecutter.project_slug }}/.idea/modules.xml new file mode 100644 index 0000000..1418fcd --- /dev/null +++ b/{{ cookiecutter.project_slug }}/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/{{ cookiecutter.project_slug }}/.idea/runConfigurations/Create_migration.xml b/{{ cookiecutter.project_slug }}/.idea/runConfigurations/Create_migration.xml new file mode 100644 index 0000000..b9e529d --- /dev/null +++ b/{{ cookiecutter.project_slug }}/.idea/runConfigurations/Create_migration.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/{{ cookiecutter.project_slug }}/.idea/runConfigurations/Run.xml b/{{ cookiecutter.project_slug }}/.idea/runConfigurations/Run.xml new file mode 100644 index 0000000..6b83792 --- /dev/null +++ b/{{ cookiecutter.project_slug }}/.idea/runConfigurations/Run.xml @@ -0,0 +1,18 @@ + + + + diff --git a/{{ cookiecutter.project_slug }}/.idea/runConfigurations/Run_Migrations.xml b/{{ cookiecutter.project_slug }}/.idea/runConfigurations/Run_Migrations.xml new file mode 100644 index 0000000..0461cea --- /dev/null +++ b/{{ cookiecutter.project_slug }}/.idea/runConfigurations/Run_Migrations.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/{{ cookiecutter.project_slug }}/.idea/vcs.xml b/{{ cookiecutter.project_slug }}/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/{{ cookiecutter.project_slug }}/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/{{ cookiecutter.project_slug }}/.idea/{{ cookiecutter.project_slug }}.iml b/{{ cookiecutter.project_slug }}/.idea/{{ cookiecutter.project_slug }}.iml new file mode 100644 index 0000000..00b113c --- /dev/null +++ b/{{ cookiecutter.project_slug }}/.idea/{{ cookiecutter.project_slug }}.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/{{ cookiecutter.project_slug }}/Dockerfile b/{{ cookiecutter.project_slug }}/Dockerfile index a3280df..685bc65 100644 --- a/{{ cookiecutter.project_slug }}/Dockerfile +++ b/{{ cookiecutter.project_slug }}/Dockerfile @@ -1,13 +1,13 @@ -FROM python:3.12-alpine as python +FROM python:3.12-alpine AS python WORKDIR /app -ENV PYTHONUNBUFFERED 1 +ARG ENVIRONMENT=local +ENV PYTHONUNBUFFERED=1 ENV TZ="Europe/Riga" COPY requirements /app/requirements RUN apk add libpq \ poppler-utils zlib-dev \ -# curl \ - && pip install --no-cache-dir --upgrade -r requirements/production.txt \ + && pip install --no-cache-dir --upgrade -r requirements/${ENVIRONMENT}.txt \ && rm -rf /var/cache/apk/* COPY entrypoint.sh /entrypoint @@ -19,4 +19,4 @@ RUN chmod +x /entrypoint \ EXPOSE 5000 ENTRYPOINT ["/entrypoint"] -CMD ["uvicorn", "--workers", "2", "--proxy-headers", "--host", "0.0.0.0", "--port", "5000", "--forwarded-allow-ips=*", "service.main:app"] +CMD ["uvicorn", "--workers", "2", "--proxy-headers", "--host", "0.0.0.0", "--port", "5000", "--forwarded-allow-ips=*", "service.api.main:app"] diff --git a/{{ cookiecutter.project_slug }}/ENVIRON.md b/{{ cookiecutter.project_slug }}/ENVIRON.md index e8a497d..21f6cd5 100644 --- a/{{ cookiecutter.project_slug }}/ENVIRON.md +++ b/{{ cookiecutter.project_slug }}/ENVIRON.md @@ -48,7 +48,7 @@ # `SERVICE_DATABASE_URL` -*Optional*, default value: `psql://{{ cookiecutter.project_slug }}:{{ cookiecutter.project_slug }}@postgres:5432/{{ cookiecutter.project_slug }}` +*Optional*, default value: `asyncpg://{{ cookiecutter.project_slug }}:{{ cookiecutter.project_slug }}@postgres:5432/{{ cookiecutter.project_slug }}` # `SERVICE_TIMEZONE` diff --git a/{{ cookiecutter.project_slug }}/README.md b/{{ cookiecutter.project_slug }}/README.md new file mode 100644 index 0000000..ee40648 --- /dev/null +++ b/{{ cookiecutter.project_slug }}/README.md @@ -0,0 +1,37 @@ +# Welcome to {{ cookiecutter.project_name }} + +## Setup +### 1. Install dependencies +#### Local virtual environment +```shell +python3 -m venv venv +source venv/bin/activate +pip install -r requirements/local.txt +``` + +#### Docker image +```shell +docker build -f Dockerfile --tag {{ cookiecutter.project_slug }} . +``` + +### 2. Initial setup +#### Environment variables +Environment variables for the project are being read from the env.yml file: +- Copy example yml: +`cp envs.example.yml envs.yml` +- Adjust values as necessary + +#### Initialize database +```shell +aerich init-db +``` + +#### Migrate database +```shell +aerich upgrade +``` + +#### Create new database migration +```shell +aerich migrate +``` diff --git a/{{ cookiecutter.project_slug }}/envs.example.yml b/{{ cookiecutter.project_slug }}/envs.example.yml index 8f3e8b0..fee131f 100644 --- a/{{ cookiecutter.project_slug }}/envs.example.yml +++ b/{{ cookiecutter.project_slug }}/envs.example.yml @@ -7,13 +7,13 @@ service: token_expiration_days: 28 cors_origins: - "http://localhost:5000" - - "https://example.com" + - "https://{{ cookiecutter.project_slug }}.xyz" environment: "local" log_level: "DEBUG" redis_url: "redis://redis:6379" - database_url: "psql://{{ cookiecutter.project_slug }}:{{ cookiecutter.project_slug }}@postgres:5432/{{ cookiecutter.project_slug }}" + database_url: "asyncpg://{{ cookiecutter.project_slug }}:{{ cookiecutter.project_slug }}@postgres:5432/{{ cookiecutter.project_slug }}" timezone: "UTC" sentry_url: null diff --git a/{{ cookiecutter.project_slug }}/requirements/base.txt b/{{ cookiecutter.project_slug }}/requirements/base.txt index 460cb5d..b411687 100644 --- a/{{ cookiecutter.project_slug }}/requirements/base.txt +++ b/{{ cookiecutter.project_slug }}/requirements/base.txt @@ -1,26 +1,26 @@ -fastapi==0.115.5 # https://github.com/tiangolo/fastapi -uvicorn==0.32.1 # https://pypi.org/project/uvicorn/ +fastapi==0.115.12 # https://github.com/tiangolo/fastapi +uvicorn==0.34.0 # https://pypi.org/project/uvicorn/ -pydantic[email]==2.10.2 # https://github.com/pydantic/pydantic -pydantic-settings[yaml]==2.6.1 # https://github.com/pydantic/pydantic-settings/ +pydantic[email]==2.11.2 # https://github.com/pydantic/pydantic +pydantic-settings[yaml]==2.8.1 # https://github.com/pydantic/pydantic-settings/ -tortoise-orm[accel,asyncpg]==0.22.1 # https://pypi.org/project/tortoise-orm/ -aerich==0.7.2 # https://pypi.org/project/aerich/ +tortoise-orm[accel,asyncpg]==0.24.2 # https://pypi.org/project/tortoise-orm/ +aerich==0.8.2 # https://pypi.org/project/aerich/ setech==1.4.2 # https://pypi.org/project/setech/ -python-multipart==0.0.18 # https://pypi.org/project/python-multipart/ +python-multipart==0.0.20 # https://pypi.org/project/python-multipart/ email-validator==2.2.0 # https://pypi.org/project/email-validator/ -tenacity==9.0.0 # https://pypi.org/project/tenacity/ -pydantic==2.10.2 # https://pypi.org/project/pydantic/ +tenacity==9.1.2 # https://pypi.org/project/tenacity/ +pydantic==2.11.2 # https://pypi.org/project/pydantic/ #emails==0.6 # https://pypi.org/project/emails/ -python-jose[cryptography]==3.3 # https://pypi.org/project/python-jose/ +python-jose[cryptography]==3.4.0 # https://pypi.org/project/python-jose/ passlib[bcrypt]==1.7.4 # https://pypi.org/project/passlib/ -bcrypt==4.2.1 # https://pypi.org/project/bcrypt/ +bcrypt==4.3.0 # https://pypi.org/project/bcrypt/ # Pin bcrypt until passlib supports the latest -pydantic-settings==2.6.1 # https://pypi.org/project/pydantic-settings/ +pydantic-settings==2.8.1 # https://pypi.org/project/pydantic-settings/ asyncio==3.4.3 diff --git a/{{ cookiecutter.project_slug }}/requirements/local.txt b/{{ cookiecutter.project_slug }}/requirements/local.txt index cf7beb7..ff04d1e 100644 --- a/{{ cookiecutter.project_slug }}/requirements/local.txt +++ b/{{ cookiecutter.project_slug }}/requirements/local.txt @@ -1,25 +1,25 @@ -r base.txt -uvicorn[standard]==0.32.1 # https://pypi.org/project/uvicorn/ +uvicorn[standard]==0.34.0 # https://pypi.org/project/uvicorn/ -black==24.10.0 # https://pypi.org/project/black/ -isort==5.13.2 # https://pypi.org/project/isort/ -pur==7.3.2 # https://pypi.org/project/pur/ -pre-commit==4.0.1 # https://pypi.org/project/pre-commit/ -flake8==7.1.1 +black==25.1.0 # https://pypi.org/project/black/ +isort==6.0.1 # https://pypi.org/project/isort/ +pur==7.3.3 # https://pypi.org/project/pur/ +pre-commit==4.2.0 # https://pypi.org/project/pre-commit/ +flake8==7.2.0 -pytest==8.3.3 # https://pypi.org/project/pytest/ -coverage==7.6.8 # https://pypi.org/project/coverage/ +pytest==8.3.5 # https://pypi.org/project/pytest/ +coverage==7.8.0 # https://pypi.org/project/coverage/ -mypy==1.13.0 # https://pypi.org/project/mypy/ -types-python-jose==3.3.4.20240106 # https://pypi.org/project/types-python-jose/ -types-passlib==1.7.7.20240819 # https://pypi.org/project/types-passlib/ -types-PyYAML==6.0.12.20240917 -types-Pygments==2.18.0.20240506 +mypy==1.15.0 # https://pypi.org/project/mypy/ +types-python-jose==3.4.0.20250224 # https://pypi.org/project/types-python-jose/ +types-passlib==1.7.7.20250401 # https://pypi.org/project/types-passlib/ +types-PyYAML==6.0.12.20250402 +types-Pygments==2.19.0.20250305 types-colorama==0.4.15.20240311 -types-decorator==5.1.8.20240310 -types-six==1.16.21.20241105 -types-ujson==5.10.0.20240515 +types-decorator==5.2.0.20250324 +types-six==1.17.0.20250403 +types-ujson==5.10.0.20250326 -settings-doc==4.3.1 # https://github.com/radeklat/settings-doc -ipython==8.30.0 +settings-doc==4.3.2 # https://github.com/radeklat/settings-doc +ipython==9.0.2 diff --git a/{{ cookiecutter.project_slug }}/requirements/production.txt b/{{ cookiecutter.project_slug }}/requirements/production.txt index dc26733..00daef6 100644 --- a/{{ cookiecutter.project_slug }}/requirements/production.txt +++ b/{{ cookiecutter.project_slug }}/requirements/production.txt @@ -1,3 +1,3 @@ -r base.txt -sentry-sdk[fastapi]==2.19.0 # https://pypi.org/project/sentry-sdk/ +sentry-sdk[fastapi]==2.25.1 # https://pypi.org/project/sentry-sdk/ diff --git a/{{ cookiecutter.project_slug }}/service/config/_settings.py b/{{ cookiecutter.project_slug }}/service/config/_settings.py index 7572e8f..c3ed543 100644 --- a/{{ cookiecutter.project_slug }}/service/config/_settings.py +++ b/{{ cookiecutter.project_slug }}/service/config/_settings.py @@ -24,7 +24,7 @@ class ProjectSettings(ProjectBaseSettings): # Background task config redis_url: RedisDsn = Field(RedisDsn(url="redis://redis:6379")) - database_url: str = Field("psql://{{ cookiecutter.project_slug }}:{{ cookiecutter.project_slug }}@postgres:5432/{{ cookiecutter.project_slug }}") + database_url: str = Field("asyncpg://{{ cookiecutter.project_slug }}:{{ cookiecutter.project_slug }}@postgres:5432/{{ cookiecutter.project_slug }}") # Various timezone: str = Field( diff --git a/{{ cookiecutter.project_slug }}/service/crud/user/methods.py b/{{ cookiecutter.project_slug }}/service/crud/user/methods.py index 54c6a74..33681ce 100644 --- a/{{ cookiecutter.project_slug }}/service/crud/user/methods.py +++ b/{{ cookiecutter.project_slug }}/service/crud/user/methods.py @@ -1,9 +1,8 @@ +from service.api.dependencies import QueryParams from service.core.security import verify_password +from service.crud.user.models import UserPublic, UserRegister +from service.crud.utils import order_queryset from service.database.models import User -from ..utils import order_queryset - -from ...api.dependencies import QueryParams -from .models import UserPublic, UserRegister async def authenticate(*, username: str, password: str) -> User | None: diff --git a/{{ cookiecutter.project_slug }}/service/crud/utils.py b/{{ cookiecutter.project_slug }}/service/crud/utils.py index a9c57e8..fb87819 100644 --- a/{{ cookiecutter.project_slug }}/service/crud/utils.py +++ b/{{ cookiecutter.project_slug }}/service/crud/utils.py @@ -4,5 +4,7 @@ from service.constants.types import PaginationParams def order_queryset(qs: QuerySet, filters: PaginationParams, default: str) -> QuerySet: - ordering = [f for f in filters.order.split(",") if f.split("-")[-1] in qs.fields] + ordering = None + if filters.order: + ordering = [f for f in filters.order.split(",") if f.split("-")[-1] in qs.fields] return qs.order_by(*(ordering or (default, )))