Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eadfa3d8ec | ||
|
|
6028507874 | ||
|
|
db8184cb37 | ||
|
|
2c1dfefe34 | ||
|
|
b4125407bf | ||
|
|
e0d5defa97 | ||
|
|
dae9532ef6 | ||
|
|
e316bc92a5 |
No files matched your search
@@ -4,6 +4,8 @@ Copier template for a Python backend, either as a `backend/` part next to other
|
||||
|
||||
Ships `backend/docker-compose.yml` with root-relative paths and profiles per service, `docker-compose.override.yml.example` (published ports and the `caddy` network aliases, copied by `make env`), 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.
|
||||
|
||||
`DB__HOST`/`DB__PORT` and `REDIS__HOST`/`REDIS__PORT` say where the service really is. The bundled container takes the host as its network alias and listens on the port, the override publishes it on the machine. For an external database, point them at it and leave its profile out of `COMPOSE_PROFILES`.
|
||||
|
||||
```sh
|
||||
copier copy --trust -d project_name="My Thing" <this-repo> <dest>
|
||||
copier update -a .copier/backend.yml
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
python = "{{ prefix }}.venv"
|
||||
|
||||
[src]
|
||||
exclude = ["{{ prefix }}migrations"]
|
||||
exclude = ["docs", "{{ prefix }}migrations"]
|
||||
@@ -2,9 +2,9 @@
|
||||
{%- if layout == 'root' %}{% set _ = groups.append(["COMPOSE_PROFILES=" ~ ("external," if database != 'none' or use_redis else "") ~ "services"]) %}{% endif -%}
|
||||
{%- set _ = groups.append(["RUN_ENVIRONMENT=prod", "TZ=UTC"]) -%}
|
||||
{%- set _ = groups.append(["LOG__LEVEL=INFO", "LOG__LEVEL_EXTERNAL=WARNING", "LOG__SHOW_TIME=false", "LOG__CONSOLE_WIDTH=150"]) -%}
|
||||
{%- if database == 'mongo' %}{% set _ = groups.append(["DB__HOST=mongodb", "DB__PORT=27017", "DB__USER=" ~ project_slug, "DB__PASSWORD=" ~ project_slug, "DB__DB_NAME=" ~ project_slug]) %}{% endif -%}
|
||||
{%- if database == 'postgres' %}{% set _ = groups.append(["DB__HOST=postgres", "DB__PORT=5432", "DB__USER=" ~ project_slug, "DB__PASSWORD=" ~ project_slug, "DB__DB_NAME=" ~ project_slug]) %}{% endif -%}
|
||||
{%- if use_redis %}{% set _ = groups.append(["REDIS__HOST=redis", "REDIS__PORT=6379", "REDIS__CACHE_TTL=300"]) %}{% endif -%}
|
||||
{%- if database == 'mongo' %}{% set _ = groups.append(["DB__HOST=mongodb", "DB__PORT=27017", "DB__USER=" ~ project_slug, "DB__PASSWORD=" ~ project_slug, "DB__DB_NAME=" ~ project_slug, "DB__SCRIPTS_CONNECTION_URL=mongodb://" ~ project_slug ~ ":" ~ project_slug ~ "@localhost:27017/" ~ project_slug ~ "?authSource=admin"]) %}{% endif -%}
|
||||
{%- if database == 'postgres' %}{% set _ = groups.append(["DB__HOST=postgres", "DB__PORT=5432", "DB__USER=" ~ project_slug, "DB__PASSWORD=" ~ project_slug, "DB__DB_NAME=" ~ project_slug, "DB__SCRIPTS_CONNECTION_URL=postgresql://" ~ project_slug ~ ":" ~ project_slug ~ "@localhost:5432/" ~ project_slug]) %}{% endif -%}
|
||||
{%- if use_redis %}{% set _ = groups.append(["REDIS__HOST=redis", "REDIS__PORT=6379", "REDIS__CACHE_TTL=300", "REDIS__SCRIPTS_CONNECTION_URL=redis://localhost:6379/0"]) %}{% endif -%}
|
||||
{%- if 'api' in backend_services %}{% set _ = groups.append(["API__HOST=0.0.0.0", "API__PORT=8080", "API__WORKERS=1", "API__DOCS=false"]) %}{% endif -%}
|
||||
{%- if 'aiogram_bot' in backend_services %}{% set _ = groups.append(["BOT__TOKEN="]) %}{% endif -%}
|
||||
{%- if include_payments %}{% set _ = groups.append(["CRYPTO_PAY__TOKEN="]) %}{% endif -%}
|
||||
|
||||
@@ -4,10 +4,23 @@
|
||||
{%- if 'taskiq_worker' in backend_services %}{% set _ = svc_modules.append("worker") %}{% 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 -%}
|
||||
{%- set app_services = [] -%}
|
||||
{%- if 'api' in backend_services %}{% set _ = app_services.append("api") %}{% endif -%}
|
||||
{%- if 'aiogram_bot' in backend_services %}{% set _ = app_services.append("bot") %}{% endif -%}
|
||||
{%- if 'taskiq_worker' in backend_services %}{% set _ = app_services.append("worker") %}{% endif -%}
|
||||
{%- if 'kurigram_userbot' in backend_services %}{% set _ = app_services.append("userbot") %}{% endif -%}
|
||||
{%- if 'scheduler' in backend_services %}{% set _ = app_services.append("scheduler") %}{% endif -%}
|
||||
{%- set has_script = dynamic_config and svc_modules -%}
|
||||
COMPOSE := docker compose{% if layout == 'part' %} --project-directory ..{% endif %}
|
||||
|
||||
.PHONY: {% if layout == 'root' %}env 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 %}
|
||||
{%- set has_scripts = dynamic_config or 'kurigram_userbot' in backend_services -%}
|
||||
COMPOSE := {% if layout == 'part' %}cd .. && {% endif %}docker compose
|
||||
{% if svc_modules %}IMAGE := {{ project_slug }}/backend
|
||||
IMAGE_INPUTS := pyproject.toml uv.lock Dockerfile
|
||||
CODE := src{% if has_scripts %} scripts{% endif %}{% if serve_spa %} ../frontend{% endif %}
|
||||
SERVICES := {{ app_services | join(' ') }}
|
||||
changed = ! git diff --quiet $(1) -- $(2) 2>/dev/null
|
||||
stamp = { git update-ref $(1) HEAD 2>/dev/null || true; }
|
||||
{% endif %}
|
||||
.PHONY: {% if layout == 'root' %}env recreate rebuild restart logs down deploy {% endif %}fmt check{% if svc_modules %} build pre-deploy post-deploy{% endif %}{% if database == 'postgres' %} migrate revision{% endif %}{% if has_script %} script{% endif %}{% if 'kurigram_userbot' in backend_services %} session{% endif %}
|
||||
|
||||
fmt:
|
||||
uv run ruff format
|
||||
@@ -17,13 +30,34 @@ check:
|
||||
uv run ruff format --check
|
||||
uv run ruff check
|
||||
uv run ty check
|
||||
|
||||
{% if svc_modules %}
|
||||
build:
|
||||
$(COMPOSE) build {{ svc_modules[0] }}
|
||||
{% if layout == 'root' %}
|
||||
|
||||
pre-deploy:
|
||||
@if $(call changed,refs/deploy/backend,$(IMAGE_INPUTS)) || ! docker image inspect $(IMAGE) >/dev/null 2>&1; then \
|
||||
$(MAKE) build && $(call stamp,refs/deploy/backend) && $(call stamp,refs/deploy/backend-code); \
|
||||
fi
|
||||
{% if database == 'postgres' %} @$(COMPOSE) config --services | grep -qx postgres && $(COMPOSE) up -d postgres || true
|
||||
$(MAKE) migrate
|
||||
{% endif %}
|
||||
post-deploy:
|
||||
@if $(call changed,refs/deploy/backend-code,$(CODE)); then \
|
||||
running=$$($(COMPOSE) ps --services --status running | grep -x $(addprefix -e ,$(SERVICES))); \
|
||||
[ -z "$$running" ] || $(COMPOSE) restart $$running; \
|
||||
$(call stamp,refs/deploy/backend-code); \
|
||||
fi
|
||||
{% endif %}{% if layout == 'root' %}
|
||||
env:
|
||||
@test -f .env || cp .env.example .env
|
||||
@test -f docker-compose.override.yml || cp docker-compose.override.yml.example docker-compose.override.yml
|
||||
@fresh=; test -f .env || { cp .env.example .env; fresh=1; }; \
|
||||
if ! test -f docker-compose.override.yml; then \
|
||||
cp docker-compose.override.yml.example docker-compose.override.yml; \
|
||||
for port in $$(sed -n 's/.*127\.0\.0\.1:\([0-9]*\):.*/\1/p' docker-compose.override.yml | sort -u); do \
|
||||
random=$$((20000 + $$(od -An -N2 -tu2 /dev/urandom) % 40000)); \
|
||||
sed "s/127\.0\.0\.1:$$port:/127.0.0.1:$$random:/" docker-compose.override.yml > docker-compose.override.yml.tmp && mv docker-compose.override.yml.tmp docker-compose.override.yml; \
|
||||
test -z "$$fresh" || { sed "s|localhost:$$port/|localhost:$$random/|" .env > .env.tmp && mv .env.tmp .env; }; \
|
||||
done; \
|
||||
fi
|
||||
|
||||
recreate:
|
||||
$(COMPOSE) up -d --force-recreate
|
||||
@@ -39,16 +73,20 @@ logs:
|
||||
|
||||
down:
|
||||
$(COMPOSE) down
|
||||
{% endif %}{% if database == 'postgres' %}
|
||||
{% if svc_modules %}
|
||||
deploy: pre-deploy
|
||||
$(COMPOSE) up -d
|
||||
$(MAKE) post-deploy
|
||||
{% endif %}{% endif %}{% if database == 'postgres' %}
|
||||
migrate:
|
||||
$(COMPOSE) --profile external run --rm migrator
|
||||
$(COMPOSE) run --rm migrator $(or $(filter-out $@,$(MAKECMDGOALS)),upgrade head)
|
||||
|
||||
revision:
|
||||
$(COMPOSE) --profile external run --rm migrator revision --autogenerate -m "$(m)"
|
||||
$(COMPOSE) run --rm migrator revision --autogenerate -m "$(m)"
|
||||
{% endif %}{% if has_script %}
|
||||
script:
|
||||
@$(COMPOSE) run --rm {{ svc_modules[0] }} scripts.$(word 2,$(MAKECMDGOALS)) $(wordlist 3,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
|
||||
|
||||
{% endif %}{% if database == 'postgres' or has_script %}
|
||||
%:
|
||||
@:
|
||||
{% endif %}{% if 'kurigram_userbot' in backend_services %}
|
||||
|
||||
@@ -11,15 +11,17 @@ services:{% if not entries %} {}{% endif %}
|
||||
caddy:
|
||||
aliases:
|
||||
- {{ dash }}-api
|
||||
# ports:
|
||||
# - "127.0.0.1:8080:${API__PORT}"
|
||||
{% elif entry == 'mongodb' %} mongodb:
|
||||
ports:
|
||||
- "127.0.0.1:${DB__PORT:-27017}:${DB__PORT:-27017}"
|
||||
# ports:
|
||||
# - "127.0.0.1:27017:${DB__PORT}"
|
||||
{% elif entry == 'postgres' %} postgres:
|
||||
ports:
|
||||
- "127.0.0.1:${DB__PORT:-5432}:5432"
|
||||
# ports:
|
||||
# - "127.0.0.1:5432:${DB__PORT}"
|
||||
{% elif entry == 'redis' %} redis:
|
||||
ports:
|
||||
- "127.0.0.1:${REDIS__PORT:-6379}:6379"
|
||||
# ports:
|
||||
# - "127.0.0.1:6379:${REDIS__PORT}"
|
||||
{% endif %}{% if not loop.last %}
|
||||
{% endif %}{% endfor %}{% if 'api' in backend_services %}
|
||||
networks:
|
||||
|
||||
@@ -50,18 +50,18 @@ services:
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: ${DB__USER}
|
||||
MONGO_INITDB_ROOT_PASSWORD: ${DB__PASSWORD}
|
||||
command: mongod --port ${DB__PORT:-27017}
|
||||
command: mongod --port ${DB__PORT}
|
||||
volumes:
|
||||
- database:/data/db
|
||||
healthcheck:
|
||||
test: ["CMD", "mongosh", "--quiet", "--port", "${DB__PORT:-27017}", "--eval", "db.adminCommand('ping')"]
|
||||
test: ["CMD", "mongosh", "--quiet", "--port", "${DB__PORT}", "--eval", "db.adminCommand('ping')"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
database:
|
||||
aliases:
|
||||
- ${DB__HOST:-mongodb}
|
||||
- ${DB__HOST}
|
||||
{% endif %}
|
||||
{%- if database == 'postgres' %}
|
||||
postgres:
|
||||
@@ -72,6 +72,7 @@ services:
|
||||
POSTGRES_USER: ${DB__USER}
|
||||
POSTGRES_PASSWORD: ${DB__PASSWORD}
|
||||
POSTGRES_DB: ${DB__DB_NAME}
|
||||
PGPORT: ${DB__PORT}
|
||||
volumes:
|
||||
- database:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -82,7 +83,7 @@ services:
|
||||
networks:
|
||||
database:
|
||||
aliases:
|
||||
- ${DB__HOST:-postgres}
|
||||
- ${DB__HOST}
|
||||
|
||||
migrator:
|
||||
build: {{ part_dir }}
|
||||
@@ -95,6 +96,7 @@ services:
|
||||
required: false
|
||||
{% endif %} environment:
|
||||
RUN_ENVIRONMENT: prod
|
||||
TZ: ${TZ:-UTC}
|
||||
volumes:
|
||||
- ./{{ prefix }}src:/app/src
|
||||
- ./{{ prefix }}migrations:/app/migrations
|
||||
@@ -104,6 +106,7 @@ services:
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
required: false
|
||||
networks:
|
||||
database:
|
||||
{% endif %}
|
||||
@@ -112,9 +115,9 @@ services:
|
||||
image: redis:7-alpine
|
||||
profiles: [redis, external]
|
||||
restart: unless-stopped
|
||||
command: ["redis-server", "--appendonly", "yes"]
|
||||
command: ["redis-server", "--port", "${REDIS__PORT}", "--appendonly", "yes"]
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
test: ["CMD", "redis-cli", "-p", "${REDIS__PORT}", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
@@ -123,7 +126,7 @@ services:
|
||||
networks:
|
||||
redis:
|
||||
aliases:
|
||||
- ${REDIS__HOST:-redis}
|
||||
- ${REDIS__HOST}
|
||||
{% endif %}
|
||||
{%- if database != 'none' or use_redis %}
|
||||
volumes:
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{%- set deps = ["pydantic-settings>=2.14.2", "rich>=15.0.0"] -%}
|
||||
{%- if use_dishka %}{% set _ = deps.append("dishka>=1.10.1") %}{% endif -%}
|
||||
{%- if 'api' in backend_services %}{% set _ = deps.append("fastapi>=0.138.0") %}{% set _ = deps.append("uvicorn[standard]>=0.49.0") %}{% endif -%}
|
||||
{%- if 'api' in backend_services %}{% set _ = deps.append("fastapi>=0.139.0") %}{% set _ = deps.append("uvicorn[standard]>=0.50.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 '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.15") %}{% 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 %}{% 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 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.39") %}{% set _ = deps.append("asyncpg>=0.31.0") %}{% set _ = deps.append("alembic>=1.18.5") %}{% set _ = deps.append("greenlet>=3.5.3") %}{% endif -%}
|
||||
{%- if use_redis %}{% set _ = deps.append("redis>=5.2.0") %}{% endif -%}
|
||||
{%- if use_llm %}{% set _ = deps.append("pydantic-ai-slim[google]>=2.1.0") %}{% endif -%}
|
||||
{%- if use_llm %}{% set _ = deps.append("pydantic-ai-slim[google]>=2.15.0") %}{% endif -%}
|
||||
{%- set modules = [] -%}
|
||||
{%- if 'api' in backend_services %}{% set _ = modules.append("api") %}{% endif -%}
|
||||
{%- if 'aiogram_bot' in backend_services %}{% set _ = modules.append("bot") %}{% endif -%}
|
||||
@@ -34,7 +34,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.11.14,<0.12.0"]
|
||||
requires = ["uv_build>=0.12.10,<0.13.0"]
|
||||
build-backend = "uv_build"
|
||||
|
||||
[tool.uv.build-backend]
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
{%- macro str_field(name, value) -%}
|
||||
{%- set line = " " ~ name ~ ': str = "' ~ value ~ '"' -%}
|
||||
{%- if line | length <= 88 or value | length + 10 > 88 %}{{ line }}{% else %} {{ name }}: str = (
|
||||
"{{ value }}"
|
||||
){% endif -%}
|
||||
{%- endmacro -%}
|
||||
{% if database != 'none' or use_redis %}import os
|
||||
|
||||
{% endif %}from pydantic import Field{% if database != 'none' or 'aiogram_bot' in backend_services or include_payments or use_llm or use_redis %}, SecretStr{% endif %}
|
||||
@@ -8,30 +14,35 @@ def is_prod() -> bool:
|
||||
return os.getenv("RUN_ENVIRONMENT") == "prod"
|
||||
{% endif %}
|
||||
|
||||
class Section(BaseSettings):
|
||||
model_config = SettingsConfigDict(extra="ignore")
|
||||
|
||||
class LogSettings(BaseSettings):
|
||||
|
||||
class LogSettings(Section):
|
||||
level: str = "INFO"
|
||||
level_external: str = "WARNING"
|
||||
show_time: bool = False
|
||||
console_width: int = 150
|
||||
{% if database == 'mongo' %}
|
||||
|
||||
class DatabaseSettings(BaseSettings):
|
||||
class DatabaseSettings(Section):
|
||||
host: str = "mongodb"
|
||||
port: int = 27017
|
||||
user: str = "{{ project_slug }}"
|
||||
password: SecretStr = SecretStr("{{ project_slug }}")
|
||||
db_name: str = "{{ project_slug }}"
|
||||
connection_params: str = "?authSource=admin"
|
||||
{{ str_field("scripts_connection_url", "mongodb://" ~ project_slug ~ ":" ~ project_slug ~ "@localhost:27017/" ~ project_slug ~ "?authSource=admin") }}
|
||||
|
||||
@property
|
||||
def connection_url(self) -> str:
|
||||
host = self.host if is_prod() else "localhost"
|
||||
if not is_prod():
|
||||
return self.scripts_connection_url
|
||||
password = self.password.get_secret_value()
|
||||
return f"mongodb://{self.user}:{password}@{host}:{self.port}/{self.db_name}{self.connection_params}"
|
||||
return f"mongodb://{self.user}:{password}@{self.host}:{self.port}/{self.db_name}{self.connection_params}"
|
||||
{% elif database == 'postgres' %}
|
||||
|
||||
class DatabaseSettings(BaseSettings):
|
||||
class DatabaseSettings(Section):
|
||||
host: str = "postgres"
|
||||
port: int = 5432
|
||||
user: str = "{{ project_slug }}"
|
||||
@@ -39,33 +50,37 @@ class DatabaseSettings(BaseSettings):
|
||||
db_name: str = "{{ project_slug }}"
|
||||
min_pool_size: int = 5
|
||||
max_pool_size: int = 20
|
||||
{{ str_field("scripts_connection_url", "postgresql://" ~ project_slug ~ ":" ~ project_slug ~ "@localhost:5432/" ~ project_slug) }}
|
||||
|
||||
@property
|
||||
def connection_url(self) -> str:
|
||||
host = self.host if is_prod() else "localhost"
|
||||
if not is_prod():
|
||||
return self.scripts_connection_url
|
||||
password = self.password.get_secret_value()
|
||||
return f"postgresql://{self.user}:{password}@{host}:{self.port}/{self.db_name}"
|
||||
return f"postgresql://{self.user}:{password}@{self.host}:{self.port}/{self.db_name}"
|
||||
|
||||
@property
|
||||
def async_connection_url(self) -> str:
|
||||
return self.connection_url.replace("postgresql://", "postgresql+asyncpg://", 1)
|
||||
{% endif %}{% if use_redis %}
|
||||
|
||||
class RedisSettings(BaseSettings):
|
||||
class RedisSettings(Section):
|
||||
host: str = "redis"
|
||||
port: int = 6379
|
||||
db: int = 0
|
||||
password: SecretStr | None = None
|
||||
cache_ttl: int = 300
|
||||
scripts_connection_url: str = "redis://localhost:6379/0"
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
host = self.host if is_prod() else "localhost"
|
||||
if not is_prod():
|
||||
return self.scripts_connection_url
|
||||
auth = f":{self.password.get_secret_value()}@" if self.password else ""
|
||||
return f"redis://{auth}{host}:{self.port}/{self.db}"
|
||||
return f"redis://{auth}{self.host}:{self.port}/{self.db}"
|
||||
{% endif %}{% if 'api' in backend_services %}
|
||||
|
||||
class ApiSettings(BaseSettings):
|
||||
class ApiSettings(Section):
|
||||
host: str = "0.0.0.0" # noqa: S104
|
||||
port: int = 8080
|
||||
workers: int = 1
|
||||
@@ -75,16 +90,16 @@ class ApiSettings(BaseSettings):
|
||||
{%- endif %}
|
||||
{% endif %}{% if 'aiogram_bot' in backend_services %}
|
||||
|
||||
class BotSettings(BaseSettings):
|
||||
class BotSettings(Section):
|
||||
token: SecretStr = SecretStr("")
|
||||
admins: list[int] = Field(default_factory=list)
|
||||
{% endif %}{% if include_payments %}
|
||||
|
||||
class CryptoPaySettings(BaseSettings):
|
||||
class CryptoPaySettings(Section):
|
||||
token: SecretStr = SecretStr("")
|
||||
{% endif %}{% if use_llm %}
|
||||
|
||||
class LlmSettings(BaseSettings):
|
||||
class LlmSettings(Section):
|
||||
model: str = "google-gla:gemini-2.5-flash"
|
||||
gemini_api_key: SecretStr = SecretStr("")
|
||||
{% endif %}
|
||||
@@ -111,10 +126,14 @@ class Settings(BaseSettings):
|
||||
{%- endif %}
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
{%- if layout == 'part' %}
|
||||
case_sensitive=False,
|
||||
env_file={% if layout == 'part' %}("../.env", ".env"){% else %}".env"{% endif %},
|
||||
env_file=("../.env", ".env"),
|
||||
env_nested_delimiter="__",
|
||||
extra="ignore",
|
||||
{%- else %}
|
||||
case_sensitive=False, env_file=".env", env_nested_delimiter="__", extra="ignore"
|
||||
{%- endif %}
|
||||
)
|
||||
|
||||
|
||||
|
||||
+8
-4
@@ -40,7 +40,8 @@ app.add_middleware(
|
||||
)
|
||||
|
||||
app.include_router(routers.router)
|
||||
{% if serve_spa %}
|
||||
{%- if serve_spa %}
|
||||
|
||||
spa_dir = Path(env.api.static_dir).resolve()
|
||||
if spa_dir.is_dir():
|
||||
|
||||
@@ -52,6 +53,9 @@ if spa_dir.is_dir():
|
||||
headers = IMMUTABLE_HEADERS if immutable else NO_CACHE_HEADERS
|
||||
return FileResponse(candidate, headers=headers)
|
||||
return FileResponse(spa_dir / "index.html", headers=NO_CACHE_HEADERS)
|
||||
{% endif %}
|
||||
{% if use_dishka %}setup_dishka(container, app)
|
||||
{% endif %}
|
||||
{%- endif %}
|
||||
{%- if use_dishka %}
|
||||
|
||||
{% if serve_spa %}
|
||||
{% endif %}setup_dishka(container, app)
|
||||
{%- endif %}
|
||||
+4
-5
@@ -1,12 +1,11 @@
|
||||
from pyrogram import filters, types
|
||||
from pyrogram import Client, filters, types
|
||||
from pyrogram.handlers import MessageHandler
|
||||
|
||||
from userbot import PyroClient
|
||||
from utils.logging import logger
|
||||
|
||||
|
||||
@PyroClient.on_message(filters.me)
|
||||
async def echo(_: PyroClient, message: types.Message) -> None:
|
||||
async def echo(_: Client, message: types.Message) -> None:
|
||||
logger.info(f"[cyan]me:[/] {message.text}")
|
||||
|
||||
|
||||
handlers = echo.handlers
|
||||
handlers = [(MessageHandler(echo, filters.me), 0)]
|
||||
+4
-5
@@ -1,13 +1,12 @@
|
||||
from pyrogram import filters, types
|
||||
from pyrogram import Client, filters, types
|
||||
from pyrogram.handlers import MessageHandler
|
||||
|
||||
from userbot import PyroClient
|
||||
from utils.logging import logger
|
||||
|
||||
|
||||
@PyroClient.on_message(filters.private & ~filters.me)
|
||||
async def pm_message(_: PyroClient, message: types.Message) -> None:
|
||||
async def pm_message(_: Client, message: types.Message) -> None:
|
||||
sender = message.from_user.id if message.from_user else "unknown"
|
||||
logger.info(f"[green]pm from {sender}:[/] {message.text}")
|
||||
|
||||
|
||||
handlers = pm_message.handlers
|
||||
handlers = [(MessageHandler(pm_message, filters.private & ~filters.me), 0)]
|
||||
@@ -1,5 +1,5 @@
|
||||
{%- set targets = [] -%}
|
||||
{%- if database == 'postgres' %}{% set _ = targets.append("`make migrate`, `make revision m=\"message\"`") %}{% endif -%}
|
||||
{%- if database == 'postgres' %}{% set _ = targets.append("`make migrate` (`make migrate downgrade -1` to roll back), `make revision m=\"message\"`") %}{% endif -%}
|
||||
{%- if dynamic_config and backend_services %}{% set _ = targets.append("`make script name`") %}{% endif -%}
|
||||
{%- if 'kurigram_userbot' in backend_services %}{% set _ = targets.append("`make session`") %}{% endif -%}
|
||||
# {{ project_name }}
|
||||
@@ -15,4 +15,6 @@ make recreate
|
||||
|
||||
`make env` creates `.env` and `docker-compose.override.yml` from their examples. `make recreate` starts the selected profiles, `make rebuild` builds the image first, `make logs`, `make restart` and `make down` do what they say.
|
||||
|
||||
`make fmt` formats, `make check` runs ruff and ty.{% if targets %} Also {{ targets | join(", ") }}.{% endif %}
|
||||
{% if backend_services %}To deploy, `git pull && make deploy`. It runs `make pre-deploy`, which builds the image only when `pyproject.toml`, `uv.lock` or `Dockerfile` changed{% if database == 'postgres' %} and applies migrations{% endif %}, then `docker compose up -d`, then `make post-deploy`, which restarts the services whose mounted code changed. A deploy system with its own `up` step calls the two hooks around it. What was built and started is remembered as `refs/deploy/*` in the clone.
|
||||
|
||||
{% endif %}`make fmt` formats, `make check` runs ruff and ty.{% if targets %} Also {{ targets | join(", ") }}.{% endif %}
|
||||
Reference in new issue
Block a user