Configuration
Configuration File
Section titled “Configuration File”Create application.yaml in your project root. Top-level keys are core settings; each extension reads its own section (the section name is the provider’s config_key):
app_name: my-appdebug: falseenv: development # development | staging | production | test
logging: level: INFO format: json # text | json
# lexigram-web (config_key: "web")web: server: host: "0.0.0.0" port: 8000 cors: enabled: true allow_origins: ["https://myapp.com"]
# lexigram-sql (config_key: "db")db: backend: url: "${DATABASE_URL}" pool: min_size: 2 max_size: 10
# lexigram-cache (config_key: "cache")cache: backends: - name: memory type: memory # memory | redis | memcached default: trueLoading Config
Section titled “Loading Config”from lexigram import LexigramConfig
# Auto-discovers application.yaml in the project rootconfig = LexigramConfig.from_yaml()
# Or from a specific pathconfig = LexigramConfig.from_yaml("path/to/application.yaml")Application loads configuration for you when you don’t pass one — it calls LexigramConfig.from_env_profile() by default.
LexigramConfig has typed top-level fields:
| Field | Type | Default | Description |
|---|---|---|---|
app_name | str | "lexigram-app" | Application name |
debug | bool | False | Debug mode |
env | Environment | development | Deployment environment |
logging | LoggingConfig | — | Structured logging settings |
modules | list[str] | [] | Enabled modules |
All extension sections (web:, db:, cache:, …) are accessed via config.get_section().
Environment Variables
Section titled “Environment Variables”There are two complementary mechanisms.
1. Interpolation inside YAML
Section titled “1. Interpolation inside YAML”Use ${VAR} for secrets and deployment values, with optional defaults via ${VAR:default}:
db: backend: url: "${DATABASE_URL:sqlite+aiosqlite:///./dev.db}"auth: secret_key: "${LEX_AUTH__SECRET_KEY}"2. Override any key with LEX_ env vars
Section titled “2. Override any key with LEX_ env vars”Any configuration key can be overridden by an environment variable using the LEX_ prefix and double underscores for nesting. Env vars win over YAML:
LEX_WEB__SERVER__PORT=9000 # web.server.port = 9000LEX_SQL__BACKEND__URL=postgresql+asyncpg://... # db backend urlLEX_AI_LLM__PROVIDERS__0__API_KEY=sk-... # list items use numeric indicesStandard variables
Section titled “Standard variables”| Variable | Purpose | Default |
|---|---|---|
LEX_PROFILE | Active configuration profile | (none) |
LEX_DEBUG | Enable debug mode | false |
LEX_QUIET | Suppress startup banner | false |
LEX_ENV | Deployment environment | development |
Profile Overlays
Section titled “Profile Overlays”Lexigram merges a profile-specific YAML over the base config. Set LEX_PROFILE to activate it:
application.yaml # Base config (always loaded)application.development.yaml # Merged when LEX_PROFILE=developmentapplication.staging.yaml # Merged when LEX_PROFILE=stagingapplication.production.yaml # Merged when LEX_PROFILE=productionapplication.test.yaml # Merged when LEX_PROFILE=testExample profiles
Section titled “Example profiles”debug: truelogging: level: DEBUG format: textdb: backend: url: "sqlite+aiosqlite:///./dev.db"debug: falselogging: level: WARNING format: jsoncache: backends: - name: redis type: redis default: true redis_url: "${REDIS_URL}"Loading with a profile
Section titled “Loading with a profile”from lexigram import LexigramConfig
# Reads LEX_PROFILE from the environmentconfig = LexigramConfig.from_env_profile()
# Explicit profileconfig = LexigramConfig.from_env_profile("production")
# With a custom base pathconfig = LexigramConfig.from_env_profile("staging", base_path="./config")Environment validation
Section titled “Environment validation”validate_for_environment() checks environment-specific constraints (for example, debug=True in production):
from lexigram.contracts.core.config import Environment
issues = config.validate_for_environment(Environment.PRODUCTION)Provider Config Auto-Injection
Section titled “Provider Config Auto-Injection”A provider declares config_key and config_model to automatically receive its typed config section — no manual parsing:
from dataclasses import dataclassfrom lexigram import Providerfrom lexigram.contracts.core.di import ContainerRegistrarProtocol
@dataclassclass BillingConfig: stripe_key: str = "" currency: str = "usd"
class BillingProvider(Provider): name = "billing" config_key = "billing" # reads "billing:" from application.yaml config_model = BillingConfig # coerces it into BillingConfig
async def register(self, container: ContainerRegistrarProtocol) -> None: cfg = self.config or BillingConfig() # self.config is a typed BillingConfig container.singleton(StripeClient, StripeClient(cfg.stripe_key))Before calling register(), the framework reads the matching section via LexigramConfig.get_section(config_key, config_model) and assigns it to provider.config. Built-in providers use the same mechanism:
| Provider | config_key |
|---|---|
WebProvider | "web" |
DatabaseProvider | "db" |
CacheProvider | "cache" |
AuthProvider | "auth" |
Config API
Section titled “Config API”config = LexigramConfig.from_yaml()
# Typed top-level accessconfig.app_name # "my-app"config.debug # Falseconfig.environment # Environment.DEVELOPMENT
# Section access (extension config)db_config = config.get_section("db", DatabaseConfig)rag_config = config.get_section("ai_rag", RAGConfig) # dotted paths also supported
# Existence + serialization (secrets redacted by default)config.has_section("web") # Trueconfig.to_dict() # {"app_name": "...", "auth": {"secret_key": "***"}}config.to_dict(redact_secrets=False) # full valuesNext Steps
Section titled “Next Steps”- YAML Configuration — interpolation, precedence, and profiles in depth
- Core Concepts — Providers, DI, and the Result type
- Your First App — Build a working API