Compare commits
3
Commits
v0.1.1
...
1f416dbe60
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f416dbe60 | ||
|
|
9afa7da9c4 | ||
|
|
aac037676c |
No files matched your search
@@ -1,8 +1,8 @@
|
|||||||
# backend-python
|
# backend-python
|
||||||
|
|
||||||
Copier template for a `backend/` part: a uv project with one Docker image and one `python -m <module>` entrypoint per service (`api`, `bot`, `userbot`, `worker`, `scheduler`), pydantic-settings, rich logging, optional Dishka, MongoDB or PostgreSQL, Redis, taskiq, CryptoPay, pydantic-ai and a DynamicConfig stored in the database.
|
Copier template for a Python backend, either as a `backend/` part next to other parts or alone at the root of the project (`layout`): a uv project with one Docker image and one `python -m <module>` entrypoint per service (`api`, `bot`, `userbot`, `worker`, `scheduler`), pydantic-settings, rich logging, optional Dishka, MongoDB or PostgreSQL, Redis, taskiq (in-memory, Redis or PostgreSQL broker), CryptoPay, pydantic-ai and a DynamicConfig stored in the database.
|
||||||
|
|
||||||
Ships `backend/docker-compose.yml` with root-relative paths, `docker-compose.local.yml` (published ports) and `docker-compose.prod.yml` (the `caddy` network), a `Makefile` with `fmt`, `check`, `build` and service-specific targets, `.env.example` and `CLAUDE.md`. Meant to be applied on top of the `infra` template.
|
Ships `backend/docker-compose.yml` with root-relative paths, `docker-compose.local.yml` (published ports) and `docker-compose.prod.yml` (the `caddy` network), a `Makefile` with `fmt`, `check`, `build` and service-specific targets, `.env.example` and `CLAUDE.md`. Applied on top of the `infra` template, `layout: parts` or `layout: single` respectively.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
copier copy --trust -d project_name="My Thing" <this-repo> <dest>
|
copier copy --trust -d project_name="My Thing" <this-repo> <dest>
|
||||||
|
|||||||
+24
-3
@@ -25,6 +25,14 @@ author_email:
|
|||||||
type: str
|
type: str
|
||||||
default: ""
|
default: ""
|
||||||
|
|
||||||
|
layout:
|
||||||
|
type: str
|
||||||
|
help: Where the backend lives
|
||||||
|
choices:
|
||||||
|
In backend/ next to other parts: part
|
||||||
|
At the root of the project: root
|
||||||
|
default: part
|
||||||
|
|
||||||
backend_services:
|
backend_services:
|
||||||
type: str
|
type: str
|
||||||
multiselect: true
|
multiselect: true
|
||||||
@@ -63,6 +71,9 @@ taskiq_broker:
|
|||||||
choices:
|
choices:
|
||||||
in-memory: memory
|
in-memory: memory
|
||||||
Redis: redis
|
Redis: redis
|
||||||
|
PostgreSQL (taskiq-pg):
|
||||||
|
value: postgres
|
||||||
|
validator: "{% if database != 'postgres' %}Requires the PostgreSQL database{% endif %}"
|
||||||
default: memory
|
default: memory
|
||||||
|
|
||||||
include_payments:
|
include_payments:
|
||||||
@@ -84,7 +95,7 @@ dynamic_config:
|
|||||||
|
|
||||||
serve_spa:
|
serve_spa:
|
||||||
type: bool
|
type: bool
|
||||||
when: "{{ 'api' in backend_services }}"
|
when: "{{ layout == 'part' and 'api' in backend_services }}"
|
||||||
help: Serve the frontend build from the api as a SPA
|
help: Serve the frontend build from the api as a SPA
|
||||||
default: false
|
default: false
|
||||||
|
|
||||||
@@ -93,6 +104,16 @@ install_deps:
|
|||||||
help: uv sync after generation
|
help: uv sync after generation
|
||||||
default: true
|
default: true
|
||||||
|
|
||||||
|
part_dir:
|
||||||
|
type: str
|
||||||
|
when: false
|
||||||
|
default: "{{ 'backend' if layout == 'part' else '.' }}"
|
||||||
|
|
||||||
|
prefix:
|
||||||
|
type: str
|
||||||
|
when: false
|
||||||
|
default: "{{ 'backend/' if layout == 'part' else '' }}"
|
||||||
|
|
||||||
_tasks:
|
_tasks:
|
||||||
- "{% if _copier_operation == 'copy' and install_deps %}sh -c 'cd backend && uv sync'{% else %}true{% endif %}"
|
- "{% if _copier_operation == 'copy' and install_deps %}sh -c 'cd {{ part_dir }} && uv sync'{% else %}true{% endif %}"
|
||||||
- "{% if install_deps %}sh -c 'cd backend && uv run ruff format -q'{% else %}true{% endif %}"
|
- "{% if install_deps %}sh -c 'cd {{ part_dir }} && uv run ruff format -q'{% else %}true{% endif %}"
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
from .modules.client import PyroClient
|
|
||||||
|
|
||||||
__all__ = ["PyroClient"]
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[environment]
|
|
||||||
python = "backend/.venv"
|
|
||||||
|
|
||||||
[src]
|
|
||||||
exclude = ["backend/migrations"]
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[environment]
|
||||||
|
python = "{{ prefix }}.venv"
|
||||||
|
|
||||||
|
[src]
|
||||||
|
exclude = ["{{ prefix }}migrations"]
|
||||||
File renamed without changes.
@@ -1,4 +1,5 @@
|
|||||||
RUN_ENVIRONMENT=dev
|
{% if layout == 'root' %}COMPOSE_FILE=docker-compose.yml:docker-compose.local.yml
|
||||||
|
{% endif %}RUN_ENVIRONMENT=dev
|
||||||
LOG__LEVEL=INFO
|
LOG__LEVEL=INFO
|
||||||
LOG__LEVEL_EXTERNAL=WARNING
|
LOG__LEVEL_EXTERNAL=WARNING
|
||||||
LOG__SHOW_TIME=false
|
LOG__SHOW_TIME=false
|
||||||
File renamed without changes.
File renamed without changes.
@@ -5,9 +5,11 @@
|
|||||||
{%- if 'kurigram_userbot' in backend_services %}{% set _ = svc_modules.append("userbot") %}{% endif -%}
|
{%- if 'kurigram_userbot' in backend_services %}{% set _ = svc_modules.append("userbot") %}{% endif -%}
|
||||||
{%- if 'scheduler' in backend_services and 'taskiq_worker' not in backend_services %}{% set _ = svc_modules.append("scheduler") %}{% endif -%}
|
{%- if 'scheduler' in backend_services and 'taskiq_worker' not in backend_services %}{% set _ = svc_modules.append("scheduler") %}{% endif -%}
|
||||||
{%- set has_script = dynamic_config and svc_modules -%}
|
{%- set has_script = dynamic_config and svc_modules -%}
|
||||||
COMPOSE := docker compose --project-directory ..
|
COMPOSE := docker compose{% if layout == 'part' %} --project-directory ..{% endif %}
|
||||||
|
{% if layout == 'root' %}SERVICES := $(COMPOSE) --profile services
|
||||||
.PHONY: fmt check build{% if database == 'postgres' %} migrate revision{% endif %}{% if has_script %} script{% endif %}{% if 'kurigram_userbot' in backend_services %} session{% endif %}
|
EXTERNAL := $(COMPOSE) --profile external
|
||||||
|
{% endif %}
|
||||||
|
.PHONY: {% if layout == 'root' %}env external recreate rebuild restart logs down {% endif %}fmt check build{% if database == 'postgres' %} migrate revision{% endif %}{% if has_script %} script{% endif %}{% if 'kurigram_userbot' in backend_services %} session{% endif %}
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
uv run ruff format
|
uv run ruff format
|
||||||
@@ -20,7 +22,27 @@ check:
|
|||||||
|
|
||||||
build:
|
build:
|
||||||
$(COMPOSE) build {{ svc_modules[0] }}
|
$(COMPOSE) build {{ svc_modules[0] }}
|
||||||
{% if database == 'postgres' %}
|
{% if layout == 'root' %}
|
||||||
|
env:
|
||||||
|
@test -f .env || cp .env.example .env
|
||||||
|
|
||||||
|
external:
|
||||||
|
$(EXTERNAL) up -d
|
||||||
|
|
||||||
|
recreate: external
|
||||||
|
$(SERVICES) up -d --force-recreate
|
||||||
|
|
||||||
|
rebuild: build recreate
|
||||||
|
|
||||||
|
restart:
|
||||||
|
$(SERVICES) restart
|
||||||
|
|
||||||
|
logs:
|
||||||
|
$(SERVICES) logs -f --tail=100
|
||||||
|
|
||||||
|
down:
|
||||||
|
$(EXTERNAL) --profile services down
|
||||||
|
{% endif %}{% if database == 'postgres' %}
|
||||||
migrate:
|
migrate:
|
||||||
$(COMPOSE) --profile external run --rm migrator
|
$(COMPOSE) --profile external run --rm migrator
|
||||||
|
|
||||||
File renamed without changes.
File renamed without changes.
+12
-12
@@ -9,21 +9,21 @@
|
|||||||
{%- if 'kurigram_userbot' in backend_services %}{% set _ = app_services.append({'name': 'userbot', 'command': '[userbot]'}) %}{% endif -%}
|
{%- if 'kurigram_userbot' in backend_services %}{% set _ = app_services.append({'name': 'userbot', 'command': '[userbot]'}) %}{% endif -%}
|
||||||
{%- if 'scheduler' in backend_services %}{% set _ = app_services.append({'name': 'scheduler', 'command': ('[worker, scheduler]' if 'taskiq_worker' in backend_services else '[scheduler]')}) %}{% endif -%}
|
{%- if 'scheduler' in backend_services %}{% set _ = app_services.append({'name': 'scheduler', 'command': ('[worker, scheduler]' if 'taskiq_worker' in backend_services else '[scheduler]')}) %}{% endif -%}
|
||||||
{%- macro backend_service(name, command) %} {{ name }}:
|
{%- macro backend_service(name, command) %} {{ name }}:
|
||||||
build: backend
|
build: {{ part_dir }}
|
||||||
image: {{ project_slug }}/backend
|
image: {{ project_slug }}/backend
|
||||||
profiles: [{{ name }}, services]
|
profiles: [{{ name }}, services]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
- path: .env
|
- path: .env
|
||||||
required: false
|
required: false
|
||||||
- path: backend/.env
|
{% if layout == 'part' %} - path: backend/.env
|
||||||
required: false
|
required: false
|
||||||
environment:
|
{% endif %} environment:
|
||||||
RUN_ENVIRONMENT: prod
|
RUN_ENVIRONMENT: prod
|
||||||
volumes:
|
volumes:
|
||||||
- ./backend/src:/app/src
|
- ./{{ prefix }}src:/app/src
|
||||||
{% if has_scripts %} - ./backend/scripts:/app/scripts
|
{% if has_scripts %} - ./{{ prefix }}scripts:/app/scripts
|
||||||
{% endif %}{% if name == 'userbot' %} - ./backend/sessions:/app/sessions
|
{% endif %}{% if name == 'userbot' %} - ./{{ prefix }}sessions:/app/sessions
|
||||||
{% endif %}{% if name == 'api' and serve_spa %} - ./frontend/build:/app/static:ro
|
{% endif %}{% if name == 'api' and serve_spa %} - ./frontend/build:/app/static:ro
|
||||||
{% endif %} command: {{ command }}
|
{% endif %} command: {{ command }}
|
||||||
{% if svc_networks %} networks:
|
{% if svc_networks %} networks:
|
||||||
@@ -70,20 +70,20 @@ services:
|
|||||||
- ${DB__HOST:-postgres}
|
- ${DB__HOST:-postgres}
|
||||||
|
|
||||||
migrator:
|
migrator:
|
||||||
build: backend
|
build: {{ part_dir }}
|
||||||
image: {{ project_slug }}/backend
|
image: {{ project_slug }}/backend
|
||||||
profiles: [migrate]
|
profiles: [migrate]
|
||||||
env_file:
|
env_file:
|
||||||
- path: .env
|
- path: .env
|
||||||
required: false
|
required: false
|
||||||
- path: backend/.env
|
{% if layout == 'part' %} - path: backend/.env
|
||||||
required: false
|
required: false
|
||||||
environment:
|
{% endif %} environment:
|
||||||
RUN_ENVIRONMENT: prod
|
RUN_ENVIRONMENT: prod
|
||||||
volumes:
|
volumes:
|
||||||
- ./backend/src:/app/src
|
- ./{{ prefix }}src:/app/src
|
||||||
- ./backend/migrations:/app/migrations
|
- ./{{ prefix }}migrations:/app/migrations
|
||||||
- ./backend/alembic.ini:/app/alembic.ini
|
- ./{{ prefix }}alembic.ini:/app/alembic.ini
|
||||||
entrypoint: [alembic]
|
entrypoint: [alembic]
|
||||||
command: [upgrade, head]
|
command: [upgrade, head]
|
||||||
depends_on:
|
depends_on:
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
{%- if 'aiogram_bot' in backend_services %}{% set _ = deps.append("aiogram>=3.29.0") %}{% endif -%}
|
{%- if 'aiogram_bot' in backend_services %}{% set _ = deps.append("aiogram>=3.29.0") %}{% endif -%}
|
||||||
{%- if include_payments %}{% set _ = deps.append("aiosend>=3.0.7") %}{% endif -%}
|
{%- if include_payments %}{% set _ = deps.append("aiosend>=3.0.7") %}{% endif -%}
|
||||||
{%- if 'kurigram_userbot' in backend_services %}{% set _ = deps.append("anyio>=4.0.0") %}{% set _ = deps.append("kurigram>=2.2.7") %}{% set _ = deps.append("tgcrypto>=1.2.5") %}{% set _ = deps.append("uvloop>=0.21.0") %}{% endif -%}
|
{%- if 'kurigram_userbot' in backend_services %}{% set _ = deps.append("anyio>=4.0.0") %}{% set _ = deps.append("kurigram>=2.2.7") %}{% set _ = deps.append("tgcrypto>=1.2.5") %}{% set _ = deps.append("uvloop>=0.21.0") %}{% endif -%}
|
||||||
{%- if 'taskiq_worker' in backend_services %}{% set _ = deps.append("taskiq>=0.12.4") %}{% if taskiq_broker == 'redis' %}{% set _ = deps.append("taskiq-redis>=1.2.3") %}{% endif %}{% endif -%}
|
{%- if 'taskiq_worker' in backend_services %}{% set _ = deps.append("taskiq>=0.12.4") %}{% if taskiq_broker == 'redis' %}{% set _ = deps.append("taskiq-redis>=1.2.3") %}{% endif %}{% if taskiq_broker == 'postgres' %}{% set _ = deps.append("taskiq-pg>=0.2.0") %}{% endif %}{% endif -%}
|
||||||
{%- if 'scheduler' in backend_services %}{% set _ = deps.append("apscheduler>=3.11.0") %}{% endif -%}
|
{%- if 'scheduler' in backend_services %}{% set _ = deps.append("apscheduler>=3.11.0") %}{% endif -%}
|
||||||
{%- if database == 'mongo' %}{% set _ = deps.append("beanie>=2.0.1") %}{% endif -%}
|
{%- if database == 'mongo' %}{% set _ = deps.append("beanie>=2.0.1") %}{% endif -%}
|
||||||
{%- if database == 'postgres' %}{% set _ = deps.append("sqlmodel>=0.0.38") %}{% set _ = deps.append("asyncpg>=0.31.0") %}{% set _ = deps.append("alembic>=1.18.4") %}{% set _ = deps.append("greenlet>=3.1.0") %}{% endif -%}
|
{%- if database == 'postgres' %}{% set _ = deps.append("sqlmodel>=0.0.38") %}{% set _ = deps.append("asyncpg>=0.31.0") %}{% set _ = deps.append("alembic>=1.18.4") %}{% set _ = deps.append("greenlet>=3.1.0") %}{% endif -%}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+3
@@ -0,0 +1,3 @@
|
|||||||
|
from .modules.client import DEVICE_MODEL, PyroClient
|
||||||
|
|
||||||
|
__all__ = ["DEVICE_MODEL", "PyroClient"]
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+14
-4
@@ -1,20 +1,30 @@
|
|||||||
from pyrogram import Client, enums
|
from pyrogram import Client, enums
|
||||||
|
|
||||||
|
DEVICE_MODEL = "{{ project_name[:32] }}"
|
||||||
|
|
||||||
|
|
||||||
class PyroClient(Client):
|
class PyroClient(Client):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, name: str, *, workdir: str = "sessions", load_handlers: bool = True
|
self,
|
||||||
|
name: str,
|
||||||
|
*,
|
||||||
|
workdir: str = "sessions",
|
||||||
|
device_model: str | None = None,
|
||||||
|
load_handlers: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name,
|
name,
|
||||||
workdir=workdir,
|
workdir=workdir,
|
||||||
api_id=2040,
|
api_id=2040,
|
||||||
api_hash="b18441a1ff607e10a989891a5462e627",
|
api_hash="b18441a1ff607e10a989891a5462e627",
|
||||||
device_model="Desktop",
|
device_model=device_model or DEVICE_MODEL,
|
||||||
system_version="Windows 11 x64",
|
system_version="Windows 11 x64",
|
||||||
app_version="6.2.4 x64",
|
app_version="7.0.8 x64",
|
||||||
lang_pack="tdesktop",
|
lang_pack="tdesktop",
|
||||||
|
lang_code="en",
|
||||||
|
system_lang_code="en-US",
|
||||||
client_platform=enums.ClientPlatform.DESKTOP,
|
client_platform=enums.ClientPlatform.DESKTOP,
|
||||||
|
max_concurrent_transmissions=4,
|
||||||
)
|
)
|
||||||
|
|
||||||
if load_handlers:
|
if load_handlers:
|
||||||
@@ -24,4 +34,4 @@ class PyroClient(Client):
|
|||||||
self.add_handler(*handler)
|
self.add_handler(*handler)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["PyroClient"]
|
__all__ = ["DEVICE_MODEL", "PyroClient"]
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+16
@@ -39,6 +39,22 @@ broker = ResilientListQueueBroker(env.redis.url).with_result_backend(
|
|||||||
{% endif %}{% if use_dishka %}
|
{% endif %}{% if use_dishka %}
|
||||||
setup_dishka(container, broker)
|
setup_dishka(container, broker)
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{%- elif taskiq_broker == 'postgres' -%}
|
||||||
|
{% if use_dishka %}from dishka.integrations.taskiq import setup_dishka
|
||||||
|
{% endif %}{% if 'scheduler' in backend_services %}from taskiq import TaskiqScheduler
|
||||||
|
from taskiq.schedule_sources import LabelScheduleSource
|
||||||
|
{% endif %}from taskiq_pg import AsyncpgBroker, AsyncpgResultBackend
|
||||||
|
|
||||||
|
{% if use_dishka %}from dependencies.container import container
|
||||||
|
{% endif %}from utils.env import env
|
||||||
|
|
||||||
|
broker = AsyncpgBroker(env.db.connection_url).with_result_backend(
|
||||||
|
AsyncpgResultBackend(env.db.connection_url)
|
||||||
|
)
|
||||||
|
{% if 'scheduler' in backend_services %}scheduler = TaskiqScheduler(broker, sources=[LabelScheduleSource(broker)])
|
||||||
|
{% endif %}{% if use_dishka %}
|
||||||
|
setup_dishka(container, broker)
|
||||||
|
{% endif %}
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
{% if use_dishka %}from dishka.integrations.taskiq import setup_dishka
|
{% if use_dishka %}from dishka.integrations.taskiq import setup_dishka
|
||||||
{% endif %}from taskiq import InMemoryBroker{% if 'scheduler' in backend_services %}, TaskiqScheduler
|
{% endif %}from taskiq import InMemoryBroker{% if 'scheduler' in backend_services %}, TaskiqScheduler
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user