diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2492d319637f293dc56dfccdd4f5c36e270d918..481840a16a8062656bb4f00f1e185c006ecb5ca3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,8 +2,8 @@ name: Release python package on: push: - branches: - - master + tags: + - "*" jobs: deploy: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d084d752b1461dd5efdd5b9ff5dcd1749805e131..f3e8a1eb004dacd23f9a11ab67470582c0802736 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,8 @@ on: push: branches-ignore: - master + tags-ignore: + - "*" jobs: pytest: 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 2dac1b288de9375bc95d63224fc5119c69e10c04..173e5b9d22f705f8d955381998301eda6bde1873 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/.pre-commit-config.yaml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/.pre-commit-config.yaml @@ -15,10 +15,14 @@ repos: hooks: - id: add-trailing-comma - - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt - rev: 0.0.11 # or specific tag + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.1.0 hooks: - - id: yamlfmt + - id: pretty-format-yaml + args: + - --autofix + - --preserve-quotes + - --indent=2 - repo: local hooks: diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/Dockerfile b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/Dockerfile index 9a5c22695472c9907a1f558a6d369b4c94ce2f6f..fc939a62c83749e110f85944170e2f1d0e9f92e6 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/Dockerfile +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/Dockerfile @@ -1,21 +1,20 @@ FROM python:3.9.6-slim-buster -RUN apt-get update && apt-get install -y \ - wait-for-it \ - && apt-get clean && rm -rf /var/lib/apt/lists/* RUN pip install poetry==1.1.8 -# Installing requirements +# Configuring poetry RUN poetry config virtualenvs.create false +# Copying requirements of a project COPY pyproject.toml poetry.lock /app/src/ WORKDIR /app/src +# Installing requirements RUN poetry install # Copying actuall application COPY . /app/src/ -RUN pip install --use-feature=in-tree-build . +RUN poetry install -CMD python -m {{cookiecutter.project_name}} +CMD ["/usr/local/bin/python", "-m", "{{cookiecutter.project_name}}"] 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 639a7eecb9ad9630f9196c43a6e785a72c2efe70..6b46853d24e5671a1693250c91ce3aa59587421a 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml @@ -36,7 +36,6 @@ services: {{cookiecutter.project_name | upper}}_DB_BASE: {{cookiecutter.project_name}} {%- endif %} {%- endif %} - {%- if cookiecutter.db_info.name == "sqlite" %} volumes: - {{cookiecutter.project_name}}-db-data:/db_data/ @@ -47,14 +46,18 @@ services: image: {{cookiecutter.db_info.image}} hostname: {{cookiecutter.project_name}}-db environment: - - "POSTGRES_PASSWORD={{cookiecutter.project_name}}" - - "POSTGRES_USER={{cookiecutter.project_name}}" - - "POSTGRES_DB={{cookiecutter.project_name}}" + POSTGRES_PASSWORD: "{{cookiecutter.project_name}}" + POSTGRES_USER: "{{cookiecutter.project_name}}" + POSTGRES_DB: "{{cookiecutter.project_name}}" volumes: - {{cookiecutter.project_name}}-db-data:/var/lib/postgresql/data restart: always healthcheck: - test: pg_isready -U {{cookiecutter.project_name}} + test: + - CMD + - pg_isready + - -U + - {{cookiecutter.project_name}} interval: 2s timeout: 3s retries: 40 @@ -66,19 +69,19 @@ services: hostname: {{cookiecutter.project_name}}-db restart: always environment: - - "ALLOW_EMPTY_PASSWORD=yes" - - "MYSQL_ROOT_PASSWORD={{cookiecutter.project_name}}" - - "MYSQL_ROOT_USER={{cookiecutter.project_name}}" - - "MYSQL_DATABASE={{cookiecutter.project_name}}" + ALLOW_EMPTY_PASSWORD: "yes" + MYSQL_ROOT_PASSWORD: "{{cookiecutter.project_name}}" + MYSQL_ROOT_USER: "{{cookiecutter.project_name}}" + MYSQL_DATABASE: "{{cookiecutter.project_name}}" healthcheck: test: - - "CMD" - - "mysql" - - "-u" - - "{{cookiecutter.project_name}}" - - "-p{{cookiecutter.project_name}}" - - "-e" - - "SELECT 1" + - CMD + - mysql + - -u + - {{cookiecutter.project_name}} + - -p{{cookiecutter.project_name}} + - -e + - SELECT 1 interval: 3s timeout: 3s retries: 40 @@ -89,6 +92,7 @@ services: {% if cookiecutter.enable_migrations == 'True' -%} migrator: image: {{cookiecutter.project_name}}:{{"${" }}{{cookiecutter.project_name | upper }}_VERSION:-latest{{"}"}} + restart: "no" {%- if cookiecutter.orm == 'sqlalchemy' %} command: alembic upgrade head {%- elif cookiecutter.orm == 'tortoise' %} @@ -120,9 +124,12 @@ services: hostname: {{cookiecutter.project_name}}-redis restart: always environment: - - "ALLOW_EMPTY_PASSWORD=yes" + ALLOW_EMPTY_PASSWORD: "yes" healthcheck: - test: redis-cli ping + test: + - CMD + - redis-cli + - ping interval: 1s timeout: 3s retries: 30 diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/app.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/app.yml index 8c44a379acfd2f085fa6e33f2e49fb0b29766e7f..45d316f9bde96dbc1113bd2126d17530e156f9d6 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/app.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/app.yml @@ -2,8 +2,10 @@ apiVersion: apps/v1 kind: Deployment metadata: + namespace: {{cookiecutter.kube_name}} name: {{cookiecutter.kube_name}}-app spec: + replicas: 2 selector: matchLabels: app: {{cookiecutter.kube_name}}-app @@ -15,26 +17,38 @@ spec: containers: - name: app image: {{cookiecutter.project_name}}:latest + readinessProbe: + httpGet: + path: /api/health + port: api-port + initialDelaySeconds: 5 + periodSeconds: 10 {%- if cookiecutter.db_info.name == "sqlite" %} command: ["/bin/sh"] args: - -c - >- + {%- if cookiecutter.orm == 'sqlalchemy' %} alembic upgrade head && + {%- elif cookiecutter.orm == 'tortoise' %} + aerich upgrade && + {%- endif %} python -m {{cookiecutter.project_name }} {%- endif %} env: - name: {{cookiecutter.project_name | upper }}_HOST - value: 0.0.0.0 + value: "0.0.0.0" + - name: {{cookiecutter.project_name | upper }}_WORKERS_COUNT + value: "10" {%- if cookiecutter.db_info.name != "none" %} {%- if cookiecutter.db_info.name != "sqlite" %} - name: {{cookiecutter.project_name | upper }}_DB_HOST - value: {{cookiecutter.kube_name}}-db-service + value: "{{cookiecutter.kube_name}}-db-service" {%- endif %} {%- endif %} {%- if cookiecutter.enable_redis == 'True' %} - name: {{cookiecutter.project_name | upper }}_REDIS_HOST - value: {{cookiecutter.kube_name}}-redis-service + value: "{{cookiecutter.kube_name}}-redis-service" {%- endif %} resources: limits: @@ -42,22 +56,28 @@ spec: cpu: "200m" ports: - containerPort: 8000 + name: api-port --- apiVersion: v1 kind: Service metadata: + namespace: {{cookiecutter.kube_name}} name: {{cookiecutter.kube_name}}-app-service spec: selector: app: {{cookiecutter.kube_name}}-app ports: - - port: 80 - targetPort: 8000 + - protocol: TCP + port: 80 + targetPort: api-port + name: api-port + --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{cookiecutter.kube_name}}-app + namespace: {{cookiecutter.kube_name}} labels: name: {{cookiecutter.kube_name}}-app spec: @@ -71,4 +91,6 @@ spec: service: name: {{cookiecutter.kube_name}}-app-service port: - number: 80 + name: api-port + +--- diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml index e488897fbce9eba8cfdb79abda87ec0530dc215d..817e177a7f13742896b06ab949f82d510f6daff5 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml @@ -2,6 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: + namespace: {{cookiecutter.kube_name}} name: {{cookiecutter.kube_name}}-db spec: selector: @@ -22,18 +23,18 @@ spec: env: {%- if cookiecutter.db_info.name == 'postgresql' %} - name: POSTGRES_PASSWORD - value: {{cookiecutter.project_name}} + value: "{{cookiecutter.project_name}}" - name: POSTGRES_USER - value: {{cookiecutter.project_name}} + value: "{{cookiecutter.project_name}}" - name: POSTGRES_DB - value: {{cookiecutter.project_name}} + value: "{{cookiecutter.project_name}}" {%- elif cookiecutter.db_info.name == 'mysql' %} - name: MYSQL_PASSWORD - value: {{cookiecutter.project_name}} + value: "{{cookiecutter.project_name}}" - name: MYSQL_USER - value: {{cookiecutter.project_name}} + value: "{{cookiecutter.project_name}}" - name: MYSQL_DATABASE - value: {{cookiecutter.project_name}} + value: "{{cookiecutter.project_name}}" - name: ALLOW_EMPTY_PASSWORD value: "yes" {%- endif %} @@ -43,6 +44,7 @@ spec: apiVersion: v1 kind: Service metadata: + namespace: {{cookiecutter.kube_name}} name: "{{cookiecutter.kube_name}}-db-service" spec: selector: @@ -54,6 +56,7 @@ spec: apiVersion: batch/v1 kind: Job metadata: + namespace: {{cookiecutter.kube_name}} name: {{cookiecutter.kube_name}}-migrator spec: ttlSecondsAfterFinished: 100 @@ -63,11 +66,6 @@ spec: - name: migrator image: {{cookiecutter.project_name}}:latest command: - - "wait-for-it" - - "-t" - - "180" - - "{{cookiecutter.kube_name}}-db-service:{{cookiecutter.db_info.port}}" - - "--" {%- if cookiecutter.orm == 'sqlalchemy' %} - "alembic" - "upgrade" @@ -76,7 +74,18 @@ spec: - "aerich" - "upgrade" {%- endif %} + resources: + limits: + memory: "200Mi" + cpu: "250m" env: - name: {{cookiecutter.project_name | upper }}_DB_HOST - value: {{cookiecutter.kube_name}}-db-service + value: "{{cookiecutter.kube_name}}-db-service" + initContainers: + - name: wait-for-db + image: toschneck/wait-for-it:latest + command: ["./wait-for-it.sh", "-t", "60", "{{cookiecutter.kube_name}}-db-service:{{cookiecutter.db_info.port}}"] restartPolicy: Never + + +--- diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/namespace.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/namespace.yml new file mode 100644 index 0000000000000000000000000000000000000000..4e1e543156e06c70633acfbde4b99aba05cd336c --- /dev/null +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/namespace.yml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: "{{cookiecutter.project_name}}" + +--- diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/redis.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/redis.yml index 6ad232f0f65a4e37e8360606bc4e2857c40ce09a..72fcc7dfcb8de449d437900414568e34c81874f8 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/redis.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/redis.yml @@ -2,6 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: + namespace: {{cookiecutter.kube_name}} name: {{cookiecutter.kube_name}}-redis spec: selector: @@ -15,6 +16,11 @@ spec: containers: - name: redis image: bitnami/redis:6.2.5 + startupProbe: + exec: + command: ["redis-cli", "ping"] + failureThreshold: 30 + periodSeconds: 5 env: - name: ALLOW_EMPTY_PASSWORD value: "yes" @@ -28,6 +34,7 @@ spec: apiVersion: v1 kind: Service metadata: + namespace: {{cookiecutter.kube_name}} name: "{{cookiecutter.kube_name}}-redis-service" spec: selector: @@ -35,3 +42,5 @@ spec: ports: - port: 6379 targetPort: 6379 + +--- diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml index 42060ce7919d271f64784c272d3b85f1d2a85995..c3b760d84b5dbce7c40feb2a2ab7c79e65e30aaa 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml @@ -16,6 +16,7 @@ fastapi = "^0.68.0" uvicorn = "^0.15.0" pydantic = {version = "^1.8.2", extras = ["dotenv"]} yarl = "^1.6.3" +ujson = "^4.2.0" {%- if cookiecutter.orm == "sqlalchemy" %} SQLAlchemy = {version = "^1.4", extras = ["mypy", "asyncio"]} {%- elif cookiecutter.orm == "tortoise" %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/router.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/router.py index 63f736ffee7a179cb06faf692e965432419171f4..fb2a3b46b120e15136e13ea6bc10d7148742dcd5 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/router.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/router.py @@ -23,11 +23,9 @@ api_router.include_router(docs.router) {%- endif %} {%- if cookiecutter.enable_routers == "True" %} api_router.include_router(echo.router, prefix="/echo", tags=["echo"]) -{%- if cookiecutter.db_info.name != "none" %} {%- if cookiecutter.add_dummy == 'True' %} api_router.include_router(dummy.router, prefix="/dummy", tags=["dummy"]) {%- endif %} -{%- endif %} {%- if cookiecutter.enable_redis == "True" %} api_router.include_router(redis.router, prefix="/redis", tags=["redis"]) {%- endif %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py index 0731def605363499d2e7815e5bf8353299c60082..cc5e51b634ab1b7e13206eb7f2f2c0f9fd2ec6d8 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py @@ -1,4 +1,5 @@ from fastapi import FastAPI +from fastapi.responses import UJSONResponse from {{cookiecutter.project_name}}.web.api.router import api_router from {{cookiecutter.project_name}}.web.lifetime import shutdown, startup @@ -39,6 +40,7 @@ def get_app() -> FastAPI: redoc_url="/api/redoc", {%- endif %} openapi_url="/api/openapi.json", + default_response_class=UJSONResponse, ) app.on_event("startup")(startup(app)) diff --git a/pyproject.toml b/pyproject.toml index 39133bec5673f526ab88111f8c9bdfdb5ca19c2b..b008af6226373147b9ea6ee0904436a09381b303 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "fastapi_template" -version = "3.1.3" +version = "3.1.5" description = "Feature-rich robust FastAPI template" authors = ["Pavel Kirilin <win10@list.ru>"] packages = [{ include = "fastapi_template" }]