Skip to content

Configuration

Wireup containers can store and inject configuration. This enables self-contained definitions without having to create factories for every injectable.

Loading Configuration

Configuration is passed to the container during creation as a dictionary.

import wireup
import os

container = wireup.create_sync_container(
    config={
        "database_url": os.environ["DB_CONNECTION_STRING"],
        "env": os.environ.get("APP_ENV", "production"),
        "max_connections": 100,
    }
)

Injecting Configuration

Primitives (Key-Value)

Inject specific values by key using Inject(config="key").

from typing import Annotated
from wireup import injectable, Inject


@injectable
class DatabaseService:
    def __init__(
        self,
        # Injects the value of "database_url" from the config dict
        url: Annotated[str, Inject(config="database_url")],
    ) -> None:
        self.url = url

Structured Objects

You are not limited to primitives. You can inject entire configuration objects, such as Dataclasses or Pydantic models. This allows you to group related settings and inject only what a service needs.

from dataclasses import dataclass


@dataclass
class DatabaseConfig:
    url: str
    max_connections: int


container = wireup.create_sync_container(
    config={"db_config": DatabaseConfig(url="...", max_connections=10)},
    injectables=[...],
)
from pydantic_settings import BaseSettings


class DatabaseSettings(BaseSettings):
    url: str
    max_connections: int = 10


container = wireup.create_sync_container(
    config={"db": DatabaseSettings()},  # Loads from env automatically
    injectables=[...],
)

Then inject the configuration object:

import sqlite3


@injectable
class DatabaseService:
    def __init__(
        self, config: Annotated[DatabaseConfig, Inject(config="db_config")]
    ) -> None:
        self.connection = sqlite3.connect(config.url)

Interpolation

You can create dynamic configuration values by interpolating other configuration keys using the ${key} syntax.

# config = {"env": "prod", "host": "localhost", "port": 5432}


@injectable
class FileStorageService:
    def __init__(
        self,
        # Becomes "/tmp/uploads/prod"
        upload_path: Annotated[str, Inject(expr="/tmp/uploads/${env}")],
        # Becomes "postgresql://localhost:5432/mydb"
        db_url: Annotated[
            str, Inject(expr="postgresql://${host}:${port}/mydb")
        ],
    ) -> None:
        self.upload_path = upload_path

Expression results are strings

Configuration expressions always return strings. Non-string configuration values are converted using str() before interpolation.

Aliasing Configuration Keys

Avoid repeating string keys across your codebase by creating type aliases. This also makes refactoring easier if configuration keys change.

# Create an alias for the configuration injection
EnvConfig = Annotated[str, Inject(config="env")]


# Use the alias instead of repeating Inject(config="env")
def list_users(env: EnvConfig) -> None: ...
def get_users(env: EnvConfig) -> None: ...

Next Steps

  • Lifetimes & Scopes - Control how long objects live.
  • Factories - Create complex dependencies and third-party objects.
  • Testing - Override configuration values for testing.