For detailed development workflow and standards, see docs/development.md.
apywire is a Python 3.12+ library for lazy object wiring and dependency injection. It uses spec-based configuration to instantiate objects on-demand with support for async access, thread safety, and code generation via Cython compilation.
Core API:
Wiring(spec, thread_safe=False): Main container for lazy instantiation from a spec dictwired.name(): Returns callableAccessorthat instantiates object on invocationawait wired.aio.name(): Async access viaAioAccessorusingrun_in_executorcompiler = WiringCompiler(spec): For compiling into python code instead of runtimecode = compiler.compile(aio=True, thread_safe=True): Generate python code for the spec- Spec Format:
{"module.Class name": {"param": "value", "ref": "{otherName}"}}
Lazy Loading via __getattr__:
Objects are instantiated only when accessed, using __getattr__ to intercept attribute access and return Accessor callables that handle instantiation and caching.
Placeholder Resolution:
Strings like {name} in spec values are converted to _WiredRef markers during parsing, then resolved to actual objects at instantiation time. See constants.py for delimiter patterns.
Thread Safety Model:
Uses optimistic per-attribute locking (dict[str, threading.Lock]) with a global fallback lock. Thread-local state stores lock-related per-thread state (mode and held locks) used by the ThreadSafeMixin; see threads.py for implementation details.
Compilation Equivalence:
Generated code must behave identically to runtime Wiring. The compiler generates accessor methods that replicate lazy loading, caching, and optional async/thread-safe behavior.
Strict Mypy Configuration:
The project uses extremely strict mypy settings including disallow_any_expr=true. Never use Any—use object instead. All functions need complete type annotations. See Code Standards for details.
Module Structure:
wiring.py: Base classWiringBase, type system, placeholder parsingruntime.py: RuntimeWiringclass withAccessor/AioAccessorcompiler.py: Code generation viaWiringCompilerthreads.py: Thread safety mixins and utilitiesexceptions.py: Custom exception classesconstants.py: Shared constants (placeholder patterns, delimiters)
Testing Requirements:
All changes must maintain 100% branch coverage. Test both runtime and compiled behavior, plus async and thread-safe variants where applicable. Use descriptive test names: test_<feature>_<scenario>_<expected_behavior>().
Testing Conventions:
- Compiled Objects: Use
wired = execd["compiled"]fromexec(code, execd). Do not instantiateCompiledmanually. - Type Safety: Use
typing.Protocolfor dynamic/compiled objects. AvoidAnyandtype: ignore. - Module Mocking: Use
sys.modulesinjection withtry...finallycleanup for custom classes. - Async Tests: Use
asyncio.run()explicitly within test functions.
Common Gotchas:
- SPDX headers required on all files (run
make formatto auto-add) - 79-character line limit enforced by
black - Compiled output is verified against runtime behavior in tests
- Thread-safe tests use actual threading to verify lock behavior
# Basic lazy access
wired = Wiring({"datetime.datetime now": {"year": 2025}})
dt = wired.now() # Instantiated on call, cached
# Async access
obj = await wired.aio.now()
# Thread-safe instantiation
wired = Wiring(spec, thread_safe=True)
# Code generation
compiler = WiringCompiler(spec)
code = compiler.compile(aio=True, thread_safe=True)Make Commands:
make all- Complete check (format, lint, coverage, build)make test- Run pytest suitemake coverage- Check coverage requirementsmake lint- Run reuse, flake8, mypymake format- Run black, isort, add SPDX headers