From db8184cb371d17967823ff0648d9a3012240888b Mon Sep 17 00:00:00 2001 From: h Date: Thu, 1 Oct 2026 02:01:58 +0200 Subject: [PATCH] feat(env): scripts connection urls for runs outside docker, sections ignore unknown keys --- template/{{ part_dir }}/.env.example.jinja | 6 +-- .../{{ part_dir }}/src/utils/env.py.jinja | 49 +++++++++++++------ 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/template/{{ part_dir }}/.env.example.jinja b/template/{{ part_dir }}/.env.example.jinja index abf6bad..5a44a74 100644 --- a/template/{{ part_dir }}/.env.example.jinja +++ b/template/{{ part_dir }}/.env.example.jinja @@ -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 -%} diff --git a/template/{{ part_dir }}/src/utils/env.py.jinja b/template/{{ part_dir }}/src/utils/env.py.jinja index 7235165..5692494 100644 --- a/template/{{ part_dir }}/src/utils/env.py.jinja +++ b/template/{{ part_dir }}/src/utils/env.py.jinja @@ -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 %} )