Configuration
fastapi-startkit uses environment variables as the source of truth for configuration. The application loads the appropriate .env file on boot, and config classes read from those variables when instantiated.
Loading Environment Variables
Application loads your .env automatically when it boots — you don't need to call anything extra:
from fastapi_startkit import Application, Config
from pathlib import Path
app = Application(base_path=Path(__file__).parent.parent)
# .env is already loaded at this pointTo target an environment-specific file, pass env to the constructor:
app = Application(base_path=Path(__file__).parent.parent, env='production').env is always loaded first. If an environment is set and .env.<environment> exists, it is loaded on top, overriding any matching keys:
.env ← always loaded
.env.production ← loaded on top when env="production" (if exists)
.env.testing ← loaded on top when env="testing" (if exists)Choosing the Environment via the CLI
In practice you rarely hardcode the environment. Pass --env on the command line instead — the artisan entry point picks it up before any command runs:
uv run artisan --env=production # .env → .env.production
uv run artisan --env=testing # .env → .env.testing
uv run artisan # .env onlyThe env() Helper
Inside any config, use env() to read a variable. It auto-casts values so you don't have to:
Raw string in .env | Python value |
|---|---|
"true" / "True" | True |
"false" / "False" | False |
| Numeric string | int |
| Everything else | str |
| Missing + default given | the default |
from fastapi_startkit.environment import env
env('REDIS_HOST') # str
env('REDIS_PORT') # int (auto-cast)
env('DEBUG', False) # False if not set
env('SOME_VALUE', cast=False) # always raw strDefining Config from Environment Variables
Define your config as a plain dataclass. Each field uses default_factory so the value is read from the environment at the moment you instantiate it — not when the class is defined. Instantiate only after the application has booted:
# config/redis.py
from dataclasses import dataclass, field
from fastapi_startkit.environment import env
@dataclass
class RedisConfig:
host: str = field(default_factory=lambda: env('REDIS_HOST'))
port: int = field(default_factory=lambda: env('REDIS_PORT'))
db: int = field(default_factory=lambda: env('REDIS_DB'))
options: dict = field(default_factory=lambda: {
'decode_responses': True
})Once defined, import and instantiate it anywhere — values are read from the active environment at that moment:
from config.redis import RedisConfig
RedisConfig().host # str — value of REDIS_HOST
RedisConfig().port # int — auto-cast from REDIS_PORT
RedisConfig().options # {'decode_responses': True}Switching Environment at Runtime
You can change the active environment after boot by calling set_environment() followed by load_environment(). Any config instantiated after that call will reflect the new environment:
# .env → REDIS_HOST=host.default
# .env.testing → REDIS_HOST=host.testing
RedisConfig().host # 'host.default'
app.set_environment('testing')
app.load_environment()
RedisConfig().host # 'host.testing'This is useful in scripts or test setups where you need to target a specific environment without restarting the process.
Registering with Config
If you want to access config via dotted keys, share it across services, or override values at runtime, use the Config static class:
from fastapi_startkit import Config
Config.set('redis', RedisConfig())
Config.get('redis.host') # 'host.testing'
Config.get('redis.options') # {'decode_responses': True}
Config.get('redis.options.decode_responses') # TrueYou can also check, override, or inspect at runtime:
Config.has('redis.host') # True
Config.set('redis.host', 'new-host') # override at runtime
Config.all() # full dict of everything registered