This guide covers the fundamental usage patterns of apywire.
The Wiring class is the main entry point for dependency injection:
from apywire import Wiring
spec = {
"datetime.datetime now": {"year": 2025, "month": 1, "day": 1},
}
wired = Wiring(spec)By default, Wiring creates a non-thread-safe container suitable for single-threaded applications. For thread safety, see the Thread Safety guide.
The spec dictionary maps wiring keys to configuration. There are two types of entries:
Use the format "module.Class name" to define objects that should be lazily instantiated:
spec = {
"datetime.datetime start_time": {"year": 2025, "month": 1, "day": 1},
"pathlib.Path project_root": {0: "/home/user/project"},
"MyClass service": {"param1": "value1", "param2": 42},
}Simple keys without the module.Class format become constants:
spec = {
"host": "localhost",
"port": 8080,
"debug": True,
"api_key": "secret-key-here",
}
wired = Wiring(spec)
# Constants are primarily used as placeholder references in other wired objects!!! note
Constants are most useful as placeholder references for other wired objects using the {name} syntax.
apywire supports various parameter types:
spec = {
"datetime.datetime dt": {
"year": 2025,
"month": 6,
"day": 15,
"hour": 10,
},
}spec = {
"pathlib.Path path": ["/home/user/project"],
}spec = {
"pathlib.Path path": {0: "/home", 1: "user", 2: "project"},
}You can combine positional and keyword arguments:
spec = {
"MyClass obj": {
0: "positional_arg1",
1: "positional_arg2",
"keyword_arg": "value",
},
}Numeric keys are sorted and passed as positional arguments, while string keys become keyword arguments.
Reference other wired objects or constants using {name} syntax:
spec = {
"database_url": "postgresql://localhost/mydb",
"max_connections": 10,
"psycopg2.pool.SimpleConnectionPool pool": {
"minconn": 1,
"maxconn": "{max_connections}",
"dsn": "{database_url}",
},
}
wired = Wiring(spec)
pool = wired.pool() # placeholders are resolved to actual valuesPlaceholders work in nested structures:
spec = {
"api_key": "secret-key",
"MyClient client": {
"config": {
"auth": {
"api_key": "{api_key}", # Nested placeholder
},
"timeout": 30,
},
},
}Placeholders work in lists too:
spec = {
"datetime.datetime start": {"year": 2025, "month": 1, "day": 1},
"datetime.datetime end": {"year": 2025, "month": 12, "day": 31},
"MyReport report": {
"date_range": ["{start}", "{end}"], # List with placeholders
},
}When you access an attribute on a Wiring container, you get an Accessor:
from apywire import Wiring, Accessor
wired = Wiring(spec)
accessor = wired.my_object # Returns an Accessor instance
print(type(accessor)) # <class 'apywire.runtime.Accessor'>Call the accessor to instantiate the object:
obj = accessor() # Instantiates the objectMost commonly, you'll do both in one line:
obj = wired.my_object()Objects are instantiated once and cached:
spec = {
"datetime.datetime now": {"year": 2025, "month": 1, "day": 1},
}
wired = Wiring(spec)
dt1 = wired.now()
dt2 = wired.now()
assert dt1 is dt2 # True - same object instance!Each access returns the same instance, not a new one. This is crucial for:
- Maintaining singleton-like behavior
- Avoiding duplicate resource allocation (e.g., database connections)
- Ensuring consistent state across your application
wired = Wiring(spec)
# No objects instantiated yet!
accessor = wired.my_object
# Still nothing instantiated - just got the accessor
obj = accessor()
# NOW the object is created and cachedspec = {
"datetime.datetime start": {"year": 2025, "month": 1, "day": 1},
"datetime.timedelta delta": {"days": 7},
"pathlib.Path root": ["/home/user"],
}
wired = Wiring(spec)
# Access multiple objects
start_date = wired.start()
time_delta = wired.delta()
root_path = wired.root()When accessing an object with placeholder dependencies, apywire resolves them in order:
spec = {
"MyDatabase db": {},
"MyCache cache": {"db": "{db}"}, # Depends on db
"MyService service": {
"cache": "{cache}", # Depends on cache
"db": "{db}", # Also depends on db
},
}
wired = Wiring(spec)
service = wired.service()
# Resolution order: db → cache → serviceapywire automatically handles the dependency graph and instantiates objects in the correct order.
from apywire import Wiring, UnknownPlaceholderError
spec = {
"MyClass obj": {"dependency": "{nonexistent}"},
}
wired = Wiring(spec)
try:
obj = wired.obj()
except UnknownPlaceholderError as e:
print(f"Unknown placeholder: {e}")See Circular dependencies for examples, expected exceptions, and advice to avoid cycles.
spec = {
"nonexistent.module.Class obj": {},
}
wired = Wiring(spec)
try:
obj = wired.obj()
except ImportError as e:
print(f"Cannot import: {e}")# Good
spec = {
"psycopg2.connect db_connection": {"dsn": "{database_url}"},
"MyRepository user_repository": {"db": "{db_connection}"},
}
# Avoid
spec = {
"psycopg2.connect conn": {"dsn": "{url}"},
"MyRepository repo": {"db": "{conn}"},
}spec = {
# Database layer
"db_url": "postgresql://localhost/mydb",
"psycopg2.connect db": {"dsn": "{db_url}"},
# Cache layer
"redis_url": "redis://localhost",
"redis.Redis cache": {"url": "{redis_url}"},
# Service layer
"MyService service": {"db": "{db}", "cache": "{cache}"},
}def validate_wiring(wired: Wiring) -> None:
"""Validate all wired objects can be instantiated."""
# Access all objects to trigger any configuration errors
_ = wired.database()
_ = wired.cache()
_ = wired.service()
# In your app startup
wired = Wiring(spec)
validate_wiring(wired) # Fail fast if configuration is wrongspec = {
# Configuration constants
"debug": True,
"log_level": "INFO",
"timeout": 30,
# Wired objects using configuration
"logging.Logger logger": {
"name": "myapp",
"level": "{log_level}",
},
"MyClient client": {
"timeout": "{timeout}",
"debug": "{debug}",
},
}# production_spec.py
def get_production_spec():
return {
"psycopg2.connect db": {"dsn": "postgresql://prod/db"},
}
# test_spec.py
def get_test_spec():
return {
"unittest.mock.Mock db": {}, # Mock database for testing
}
# Usage
if os.getenv("TESTING"):
wired = Wiring(get_test_spec())
else:
wired = Wiring(get_production_spec())- Async Support - Use
await wired.aio.name()for async access - Thread Safety - Enable thread-safe instantiation
- Compilation - Generate standalone code from your spec
- Advanced Features - Factory methods and complex patterns