Configuration
A flexible, environment-aware configuration system. Python modules for structure, .env files for secrets — with automatic type casting and multi-environment support.
Config files are just Python modules — full language power.
Override any value with environment variables. Auto-discovery.
Separate configs for dev, test, production.
Strings auto-converted to bool, int, float.
Introduction
FastKit Core's configuration system combines the flexibility of Python modules with the security of environment variables. Configuration files define the structure and defaults; .env files provide environment-specific values without touching code.
Zero extra dependencies — uses only Python's standard library plus python-dotenv, which is already included with FastKit Core.
Quick Example
# config/app.py
import os
APP_NAME = os.getenv('APP_NAME', 'My FastKit App')
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
VERSION = '1.0.0'
from fastkit_core.config import config
app_name = config('app.APP_NAME') # "My FastKit App"
debug = config('app.DEBUG') # False
version = config('app.VERSION') # "1.0.0"
Configuration Files
By default FastKit Core looks for config modules in a config/ directory at the project root:
your-project/
├── config/
│ ├── __init__.py # Makes it a Python package
│ ├── app.py # Application settings
│ ├── database.py # Database connections
│ ├── cache.py # Cache configuration
│ └── services.py # External services
├── .env # Environment variables (never commit!)
├── .env.example # Template — commit this
└── main.py
A typical config/app.py
# config/app.py
import os
APP_NAME = os.getenv('APP_NAME', 'FastKit App')
DEBUG = os.getenv('DEBUG', 'False').lower() in ('true', '1', 'yes')
API_VERSION = 'v1'
API_PREFIX = '/api/v1'
ALLOWED_ORIGINS = [
'http://localhost:3000',
'http://localhost:8080',
]
DEFAULT_PAGE_SIZE = 20
MAX_PAGE_SIZE = 100
Environment Variables
Create a .env file in your project root to set environment-specific values:
# .env
APP_NAME=My Production App
DEBUG=False
SECRET_KEY=your-secret-key-here
# Database
DB_DRIVER=postgresql
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DB_USERNAME=postgres
DB_PASSWORD=secret
Auto-discovery
FastKit Core automatically searches for .env files, starting from the current directory and walking up to the root:
./.env
Current directory
../.env
Parent directory
../../.env
Grandparent directory
up to root
Continues until found
Priority
Environment variables always override config file values:
# config/app.py
APP_NAME = 'Default Name' # fallback
# .env
APP_NAME="Production App" # this wins
# result
config('app.APP_NAME') # → "Production App"
Type Casting
FastKit Core automatically converts environment variable strings to the correct Python type:
# .env
DEBUG=true
PORT=8000
RATE_LIMIT=100.5
config('app.DEBUG') # bool: True
config('app.PORT') # int: 8000
config('app.RATE_LIMIT') # float: 100.5
| Input string | Python type | Value |
|---|---|---|
"true", "yes", "1" | bool | True |
"false", "no", "0" | bool | False |
"123" | int | 123 |
"123.45" | float | 123.45 |
| anything else | str | as-is |
Accessing Configuration
Convenience functions
The simplest way — imported and used anywhere:
from fastkit_core.config import config, config_has, config_all, config_set
# Get a value — format: 'module.KEY'
app_name = config('app.APP_NAME')
# With a default
api_key = config('app.API_KEY', default='not-set')
# Check existence
if config_has('app.SECRET_KEY'):
secret = config('app.SECRET_KEY')
# Get all config for a module
app_config = config('app') # returns dict
# Get all modules
all_config = config_all()
# Set at runtime (useful in tests)
config_set('app.TEST_MODE', True)
ConfigManager directly
For more control — create your own manager instance:
from fastkit_core.config import ConfigManager
manager = ConfigManager(
modules=['app', 'database'], # which modules to load
env_file='.env', # which .env file to use
auto_load=True # load immediately
)
app_name = manager.get('app.APP_NAME')
debug = manager.get('app.DEBUG', default=False)
manager.set('app.TEST_MODE', True)
has_key = manager.has('app.SECRET_KEY')
manager.reload()
Multiple Environments
Create separate .env files for each environment:
your-project/
├── .env # Default (never commit!)
├── .env.development # Development overrides
├── .env.test # Test environment
├── .env.production # Production settings
└── .env.example # Template — commit this ✓
# .env.development
DEBUG=True
DB_HOST=localhost
DB_NAME=myapp_dev
# .env.test
DEBUG=False
DB_NAME=myapp_test
# .env.production
DEBUG=False
DB_HOST=prod-db.example.com
DB_NAME=myapp_prod
# config/__init__.py
import os
from fastkit_core.config import ConfigManager
ENV = os.getenv('APP_ENV', 'development')
config_manager = ConfigManager(env_file=f'.env.{ENV}')
uvicorn main:app --reload # development (default)
APP_ENV=test pytest # test
APP_ENV=production uvicorn main:app # production
from fastkit_core.config import ConfigManager
dev_config = ConfigManager(env_file='.env.development')
test_config = ConfigManager(env_file='.env.test')
prod_config = ConfigManager(env_file='.env.production')
Testing with Custom Config
Override configuration in pytest fixtures to isolate tests:
# tests/conftest.py
import pytest
from fastkit_core.config import ConfigManager, set_config_manager
@pytest.fixture
def test_config():
config = ConfigManager(
modules=['app', 'database'],
env_file='.env.test',
auto_load=True
)
# Override specific values
config.set('app.DEBUG', False)
config.set('database.CONNECTIONS', {
'default': {'url': 'sqlite:///:memory:'}
})
set_config_manager(config)
yield config
set_config_manager(None) # cleanup
Caching & Reload
Configuration is loaded once on startup and cached for the lifetime of the process. This means every call to config() is fast — no file I/O after the initial load.
from fastkit_core.config import config_reload
# Force reload from files (e.g. after .env changes)
config_reload()
# Or reload a specific manager
config_manager.reload()
When running with uvicorn --reload, configuration reloads automatically on file changes during development.
Best Practices
1. Never hardcode secrets
# ❌ Never do this
SECRET_KEY = 'my-hardcoded-secret'
# ✅ Always use environment variables
SECRET_KEY = os.getenv('SECRET_KEY')
if not SECRET_KEY:
raise ValueError("SECRET_KEY environment variable is required")
2. Always provide defaults
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
PORT = int(os.getenv('PORT', 8000))
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
3. Create a .env.example
# .env.example ← commit this, not .env
APP_NAME=My App
DEBUG=False
SECRET_KEY= # required — generate with: openssl rand -hex 32
DB_DRIVER=postgresql
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DB_USERNAME=postgres
DB_PASSWORD=
4. Validate required settings on startup
# config/app.py
SECRET_KEY = os.getenv('SECRET_KEY')
DATABASE_URL = os.getenv('DATABASE_URL')
if not DEBUG: # fail fast in production
if not SECRET_KEY:
raise ValueError("SECRET_KEY must be set in production")
if not DATABASE_URL:
raise ValueError("DATABASE_URL must be set in production")
API Reference
ConfigManager
ConfigManager(
modules: list[str] | None = None, # e.g. ['app', 'database']
config_package: str = 'config', # directory name
env_file: str | Path | None = None, # path to .env file
auto_load: bool = True # load on init
)
manager.get('app.APP_NAME')manager.set('app.DEBUG', True)manager.has('app.SECRET_KEY') → bool.envConvenience functions
from fastkit_core.config import configConfigManager instance.Complete Example
A full production-ready configuration setup:
# config/app.py
import os
APP_NAME = os.getenv('APP_NAME', 'FastKit App')
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
VERSION = '1.0.0'
SECRET_KEY = os.getenv('SECRET_KEY')
if not SECRET_KEY and not DEBUG:
raise ValueError("SECRET_KEY is required in production")
API_PREFIX = '/api/v1'
DEFAULT_PAGE_SIZE = 20
MAX_PAGE_SIZE = 100
ALLOWED_ORIGINS = (
os.getenv('ALLOWED_ORIGINS', '').split(',')
if os.getenv('ALLOWED_ORIGINS')
else ['http://localhost:3000', 'http://localhost:8080']
)
# config/database.py
import os
CONNECTIONS = {
'default': {
'driver': os.getenv('DB_DRIVER', 'postgresql'),
'host': os.getenv('DB_HOST', 'localhost'),
'port': int(os.getenv('DB_PORT', 5432)),
'database': os.getenv('DB_NAME', 'myapp'),
'username': os.getenv('DB_USERNAME', 'postgres'),
'password': os.getenv('DB_PASSWORD', ''),
'pool_size': int(os.getenv('DB_POOL_SIZE', 10)),
'max_overflow': int(os.getenv('DB_MAX_OVERFLOW', 20)),
'echo': os.getenv('DB_ECHO', 'False').lower() == 'true',
}
}
# main.py
from fastapi import FastAPI
from fastkit_core.config import config
app = FastAPI(
title=config('app.APP_NAME'),
version=config('app.VERSION'),
debug=config('app.DEBUG')
)
@app.get("/")
def root():
return {
"app": config('app.APP_NAME'),
"version": config('app.VERSION'),
"debug": config('app.DEBUG'),
}
# .env
APP_NAME=My Awesome API
DEBUG=True
SECRET_KEY=dev-secret-key-change-in-production
DB_DRIVER=postgresql
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_dev
DB_USERNAME=postgres
DB_PASSWORD=postgres