Skip to content
GitHubDiscord

Configuration

Create application.yaml in your project root:

application.yaml
app_name: my-app
debug: false
env: development # development | staging | production | test
logging:
level: INFO
format: json # text | json
web:
host: "0.0.0.0"
port: 8000
cors:
enabled: true
allow_origins: ["https://myapp.com"]
db:
url: "postgresql://localhost:5432/myapp"
pool_size: 10
cache:
backend: memory
ttl: 300
from lexigram import LexigramConfig
# Auto-discovers application.yaml in the project root
config = LexigramConfig.from_yaml()
# Or from a specific path
config = LexigramConfig.from_yaml("path/to/application.yaml")

LexigramConfig is a dataclass with typed top-level fields:

FieldTypeDefaultDescription
app_namestr"lexigram-app"Application name
debugboolFalseDebug mode (also via LEX_DEBUG)
envEnvironmentdevelopmentDeployment environment
loggingLoggingConfig{}Structured logging settings
moduleslist[str][]Enabled modules

All additional sections (e.g. web:, db:, cache:) are stored in model_extra and accessed via config.get_section().


Use ${VAR_NAME} syntax for secrets and deployment-specific values. Provide defaults with ${VAR_NAME:default}:

application.yaml
db:
url: "${DATABASE_URL:sqlite:///./dev.db}"
password: "${DB_PASSWORD}"
web:
port: "${PORT:8000}"
VariablePurposeDefault
LEX_PROFILEActive configuration profile(none)
LEX_DEBUGEnable debug modefalse
LEX_QUIETSuppress startup bannerfalse

Lexigram merges profile-specific YAML over the base config. Set LEX_PROFILE to activate:

application.yaml # Base config (always loaded)
application.development.yaml # Merged when profile=development
application.staging.yaml # Merged when profile=staging
application.production.yaml # Merged when profile=production
lexigram.test.yaml # Merged when profile=test
application.development.yaml
debug: true
logging:
level: DEBUG
format: text
db:
url: "sqlite:///./dev.db"
application.production.yaml
debug: false
logging:
level: WARNING
format: json
db:
url: "${DATABASE_URL}"
cache:
backend: redis
from lexigram import LexigramConfig
# From environment variable (reads LEX_PROFILE)
config = LexigramConfig.from_env_profile()
# Explicit profile
config = LexigramConfig.from_env_profile("production")
# With custom base path
config = LexigramConfig.from_env_profile("staging", base_path="./config")

LexigramConfig.validate_for_environment() checks environment-specific constraints:

issues = config.validate_for_environment(Environment.PRODUCTION)
# Returns ConfigIssue if debug=True in production

Providers declare config_key and config_model to automatically receive their typed config section — no manual parsing required.

from dataclasses import dataclass
from lexigram.di.provider import Provider
from lexigram.contracts.core.di import ContainerRegistrarProtocol
@dataclass
class 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:
# self.config is a typed BillingConfig instance
cfg = self.config or BillingConfig()
container.singleton(StripeClient, StripeClient(cfg.stripe_key))

During Application.start(), the ProviderOrchestrator:

  1. Checks if the provider declares config_key and config_model
  2. Calls LexigramConfig.get_section(config_key, config_model)
  3. Assigns the result to provider.config
  4. Then calls provider.register(container)

Every built-in provider uses this pattern:

Providerconfig_keyconfig_model
WebProvider"web"WebConfig
DatabaseProvider"db"DatabaseConfig
CacheProvider"cache"CacheConfig
AuthProvider"auth"AuthConfig

config = LexigramConfig.from_yaml()
# Typed access to known sections
config.app_name # "my-app"
config.debug # False
config.env # Environment.DEVELOPMENT
config.environment # alias for config.env
# Dynamic section access (for plugin/extension sections)
db_config = config.get_section("db", DatabaseConfig)
raw_dict = config.get_section("payments")
# Dotted path traversal
rag_config = config.get_section("ai.rag", RagConfig)
# Check existence
config.has_section("web") # True
# Serialize (secrets redacted by default)
config.to_dict() # {"app_name": "...", "db": {"password": "***"}}
config.to_dict(redact_secrets=False) # full values