Skip to content

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.