Configuration¶
Every pack of velociraptors needs ground rules: which territory to hunt, how loud to call, whether to run in the open or stay camouflaged. VelociPy's Config class is a small, immutable container for those rules. It does not parse .env files, read TOML, or phone home - it just gives you typed, frozen settings and a registry for picking the right class at runtime.
See examples/config.py for a runnable demo.
Why not just a dict?¶
A plain dict is easy to start with and painful to maintain. Config gives you:
- typed fields with defaults
- immutable instances, so a setting cannot drift mid-request
- a registry for environment-specific variants (
development,testing,production) - predictable overrides through keyword arguments
It stays out of the way. You bring your own loader (os.getenv, tomllib, pydantic-settings, or hard-coded defaults for tests).
A basic config den¶
Subclass Config, declare annotated fields, and create an instance.
from velocipy.config import Config
class AppConfig(Config):
service_name: str = "hunt_api"
debug: bool = False
host: str = "0.0.0.0"
port: int = 8000
config = AppConfig()
print(config.port) # 8000
Note
Fields must be annotated with type hints. Class-level values become defaults; annotated fields without defaults must be supplied at construction time.
Loading from the environment¶
In a real app you usually read os.environ and fall back to sensible defaults.
import os
from velocipy.config import Config
class AppConfig(Config):
debug: bool = os.getenv("DEBUG", "false").lower() == "true"
database_url: str = os.getenv("DATABASE_URL", "sqlite:///app.db")
redis_url: str | None = os.getenv("REDIS_URL")
port: int = int(os.getenv("PORT", "8000"))
config = AppConfig()
Tip
Keep environment parsing inside the class body. That keeps defaults visible and makes the config class self-documenting.
Environment-specific variants¶
Register subclasses for different terrains and retrieve them by key.
from velocipy.config import Config
class AppConfig(Config):
debug: bool = False
testing: bool = False
environment: str = "development"
host: str = "0.0.0.0"
port: int = 5000
@Config.register("production")
class ProductionConfig(AppConfig):
debug: bool = False
testing: bool = False
environment: str = "production"
@Config.register("testing")
class TestingConfig(AppConfig):
debug: bool = True
testing: bool = True
environment: str = "testing"
ConfigClass = Config.get("testing", default=AppConfig)
config = ConfigClass()
assert config.environment == "testing"
Config.get("testing") returns the registered subclass. If the key is missing, passing default=AppConfig falls back to the base class instead of raising KeyError.
Overrides and immutability¶
Instances are frozen after construction. Any attempt to assign or delete an attribute raises AttributeError.
config = AppConfig(port=9000)
assert config.port == 9000
config.port = 8080 # AttributeError: Config is immutable
Override only the fields you need; everything else keeps its class default.
config = AppConfig(debug=True)
assert config.debug is True
assert config.port == 5000 # default unchanged
Wiring config into the app¶
Pass settings into VelociPy and handlers like any other object.
from velocipy import VelociPy
from velocipy.status import HTTP_200_OK
from velocipy.testing import TestClient
class AppConfig(Config):
debug: bool = False
environment: str = "development"
port: int = 5000
config = AppConfig(debug=True)
app = VelociPy(debug=config.debug)
@app.get("/config")
async def get_config() -> dict[str, object]:
return {
"environment": config.environment,
"debug": config.debug,
"port": config.port,
}
with TestClient(app) as client:
response = client.get("/config")
assert response.status_code == HTTP_200_OK
Advanced: config-driven feature selection¶
Because config instances are immutable and cheap to construct, you can select JSON encoders, rate-limit storage, or middleware based on settings at startup.
from velocipy import VelociPy
from velocipy.adapters.json.msgspec import MsgspecJSONAdapter
from velocipy.adapters.json.stdlib import StdlibJSONAdapter
from velocipy.config import Config
class AppConfig(Config):
debug: bool = False
fast_json: bool = True
config = AppConfig()
json_encoder = MsgspecJSONAdapter() if config.fast_json else StdlibJSONAdapter()
app = VelociPy(
debug=config.debug,
json_encoder=json_encoder,
)
At a glance¶
| Tool | Purpose |
|---|---|
class AppConfig(Config) |
Declare a typed, immutable config class |
@Config.register("key") |
Register a subclass under a lookup key |
Config.get("key") |
Retrieve the registered subclass, or raise KeyError |
Config.get("key", default=BaseConfig) |
Retrieve the registered subclass, falling back to default |
AppConfig(port=9000) |
Create an instance with overrides |
| Immutability | Attribute assignment/deletion is blocked after construction |
VelociPy's Config is deliberately small: it holds your ground rules, keeps them frozen, and lets you pick the right variant for the terrain. For a complete working den, see examples/config.py.