InjectQ
InjectQ is a modern, lightweight Python dependency injection library focused on clarity, type-safety, and seamless framework integration.
Documentation
Full documentation is hosted at Documentation and the repository docs/ contains the source.
Key features
- Simplicity-first dict-like API for quick starts
- Flexible decorator- and type-based injection (
@inject,Inject[T]) - Type-friendly: designed to work with static type checkers
- Built-in integrations for frameworks (FastAPI, Taskiq) as optional extras
- Factory and async factory support
- 🆕 Hybrid factory methods combining DI with manual arguments (
invoke(),ainvoke()) - Scope management and testing utilities
Quick Start (recommended pattern)
Prefer the exported global InjectQ.get_instance() container in examples and application code. It uses the active context container when present, otherwise falls back to a global singleton.
from injectq import InjectQ, inject, singleton
container = InjectQ.get_instance()
# Basic value binding
container[str] = "Hello, World!"
@singleton
class UserService:
def __init__(self, message: str):
self.message = message
def greet(self) -> str:
return f"Service says: {self.message}"
@inject
def main(service: UserService) -> None:
print(service.greet())
if __name__ == "__main__":
main() # Prints: Service says: Hello, World!
Notes:
- Use
container[...]for simple bindings and values. - Use
@injectandInject[T]for function/class injection.
Enhanced Features
Nullable Dependencies
InjectQ supports binding None values for optional dependencies using the allow_none parameter:
from injectq import InjectQ
container = InjectQ()
# Optional service - can be None
class EmailService:
def send_email(self, to: str, message: str) -> str:
return f"Email sent to {to}: {message}"
class NotificationService:
def __init__(self, email_service: EmailService | None = None):
self.email_service = email_service
def notify(self, message: str) -> str:
if self.email_service:
return self.email_service.send_email("user", message)
return f"Basic notification: {message}"
# Bind None for optional dependency
container.bind(EmailService, None, allow_none=True)
container.bind(NotificationService, NotificationService)
service = container.get(NotificationService)
print(service.notify("Hello")) # Prints: Basic notification: Hello
Abstract Class Validation
InjectQ automatically prevents binding abstract classes and raises a BindingError during binding (not at resolution time):
from abc import ABC, abstractmethod
from injectq import InjectQ
from injectq.utils.exceptions import BindingError
class PaymentProcessor(ABC): # Abstract class
@abstractmethod
def process_payment(self, amount: float) -> str:
pass
class CreditCardProcessor(PaymentProcessor): # Concrete implementation
def process_payment(self, amount: float) -> str:
return f"Processing ${amount} via credit card"
container = InjectQ()
# This will raise BindingError immediately
try:
container.bind(PaymentProcessor, PaymentProcessor) # Error!
except BindingError:
print("Cannot bind abstract class")
# This works fine
container.bind(PaymentProcessor, CreditCardProcessor) # OK
See examples/enhanced_features_demo.py for a complete demonstration.
🆕 Hybrid Factory Methods
The new invoke() and ainvoke() methods combine dependency injection with manual arguments:
from injectq import InjectQ
container = InjectQ()
container.bind(Database, Database)
container.bind(Cache, Cache)
# Factory that needs both DI dependencies and runtime arguments
def create_user_service(db: Database, cache: Cache, user_id: str):
return UserService(db, cache, user_id)
container.bind_factory("user_service", create_user_service)
# ❌ Old way - verbose
db = container[Database]
cache = container[Cache]
service = container.call_factory("user_service", db, cache, "user123")
# ✅ New way - automatic DI + manual args
service = container.invoke("user_service", user_id="user123")
# Database and Cache auto-injected, only provide user_id!
# Also works with async
service = await container.ainvoke("async_service", batch_size=100)
When to use invoke():
- Factory needs some DI dependencies + some runtime arguments
- You want cleaner code without manual resolution
- Mix configuration from container with user input
See examples/factory_api_showcase.py and docs/injection-patterns/factory-methods.md for details.
Installation
Install from PyPI:
pip install injectq
Optional framework integrations (install only what you need):
pip install injectq[fastapi] # FastAPI integration (optional)
pip install injectq[taskiq] # Taskiq integration (optional)
Where to look next
docs/getting-started/installation.md— installation and verificationdocs/injection-patterns/dict-interface.md— dict-like APIdocs/injection-patterns/inject-decorator.md—@injectusagedocs/injection-patterns/factory-methods.md— factory patterns (DI, parameterized, hybrid)docs/integrations/— integration guides for FastAPI and Taskiq
License
MIT — see the LICENSE file.
Run tests with coverage
Activate the project's virtualenv and run pytest (coverage threshold is configured to 73%):
source .venv/bin/activate
python -m pytest
Coverage reports are written to htmlcov/ and coverage.xml.
Performance Benchmarks
InjectQ includes comprehensive performance benchmarks to ensure production-ready performance:
# Run all benchmarks
pytest tests/test_benchmarks.py --benchmark-only
# Run with verbose statistics
pytest tests/test_benchmarks.py --benchmark-only --benchmark-verbose
# Save results for comparison
pytest tests/test_benchmarks.py --benchmark-only --benchmark-autosave
Performance Highlights
- Ultra-fast operations: Basic operations (bind, get, has) execute in 270-780 nanoseconds
- Efficient resolution: Dependency resolution completes in ~1 microsecond
- Excellent scalability: Handles 1,000+ operations with sub-millisecond performance
- Thread-safe: Concurrent access with minimal overhead (~24 μs)
- Production-ready: Web request simulation (10 services) completes in 142 microseconds
📊 See BENCHMARK_REPORT.md for detailed analysis and BENCHMARK_QUICK_GUIDE.md for usage guide.
Metadata
Release files for injectq 0.4.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| injectq-0.4.2.tar.gz | 2.2 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| injectq-0.4.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.3 MB
Release files / injectq-0.4.2.tar.gz
| Download URL | injectq-0.4.2.tar.gz |
|---|---|
| Size | 2.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
efcb6416e3b80d5a0b10c32138b05fe7128640287da15482e5314372d32222df
|
|
BLAKE2b-256 checksum How to use checksums |
c7618399d1a71eeab69af49566e89401092dd59d70807b721b0a4fb73db63dfb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.9
|
Release files / injectq-0.4.2-py3-none-any.whl
| Download URL | injectq-0.4.2-py3-none-any.whl |
|---|---|
| Size | 74.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a8b8873e3b1d53ea2a3020478f42ba2698b39436eed2855fac4ee6698b5e650f
|
|
BLAKE2b-256 checksum How to use checksums |
34a7329aa7afeb0617b413fcd8b10d0e9c11eef8c9bfd8db5a254b15bd7d5b73
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.9
|