Docs / fastkit-core / Configuration

Configuration

A flexible, environment-aware configuration system. Python modules for structure, .env files for secrets — with automatic type casting and multi-environment support.

Python-based

Config files are just Python modules — full language power.

Secrets via .env

Override any value with environment variables. Auto-discovery.

Multi-environment

Separate configs for dev, test, production.

Type Casting

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

python
# 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'
python
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:

text
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

python
# 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
UPPER_CASE
Use uppercase for all configuration keys — standard Python convention.
os.getenv()
Use for values that should come from the environment. Always provide a sensible default.
Hard-coded values
Fine for non-sensitive values like version numbers, prefixes, and defaults.

Environment Variables

Create a .env file in your project root to set environment-specific values:

bash
# .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:

1 ./.env Current directory
2 ../.env Parent directory
3 ../../.env Grandparent directory
up to root Continues until found

Priority

Environment variables always override config file values:

python
# 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:

bash
# .env
DEBUG=true
PORT=8000
RATE_LIMIT=100.5
python
config('app.DEBUG')       # bool:  True
config('app.PORT')        # int:   8000
config('app.RATE_LIMIT')  # float: 100.5
Input stringPython typeValue
"true", "yes", "1"boolTrue
"false", "no", "0"boolFalse
"123"int123
"123.45"float123.45
anything elsestras-is

Accessing Configuration

Convenience functions

The simplest way — imported and used anywhere:

python
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:

python
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:

text
your-project/
├── .env                 # Default (never commit!)
├── .env.development     # Development overrides
├── .env.test            # Test environment
├── .env.production      # Production settings
└── .env.example         # Template — commit this ✓
bash
# .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
python
# 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}')
bash
uvicorn main:app --reload              # development (default)
APP_ENV=test pytest                    # test
APP_ENV=production uvicorn main:app    # production
python
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:

python
# 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.

python
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

python
# ❌ 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

python
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

bash
# .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

python
# 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

python
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
)
.get(key, default)
Get value by dot notation: manager.get('app.APP_NAME')
.set(key, value)
Set value at runtime: manager.set('app.DEBUG', True)
.has(key)
Check existence: manager.has('app.SECRET_KEY')bool
.all()
Returns all config as a nested dict
.reload()
Re-reads all config files and .env

Convenience functions

config(key, default)
Get from global manager. from fastkit_core.config import config
config_set(key, value)
Set in global manager.
config_has(key)
Check existence in global manager.
config_all()
Get all from global manager.
config_reload()
Reload global manager.
get_config_manager()
Returns the global ConfigManager instance.
set_config_manager(mgr)
Replace the global instance — useful in tests.

Complete Example

A full production-ready configuration setup:

python
# 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']
)
python
# 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',
    }
}
python
# 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'),
    }
bash
# .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