You've been there.
A service that needs a database, which needs a config, which needs an env variable that someone hardcoded three months ago and nobody remembers where. You're passing objects down ten layers of constructors. Testing means faking half your app.
It's messy. And it doesn't have to be.
Dependency Injection is the fix - but most Python DI libraries feel like they were designed for a different language. Overly complex, decorator-heavy, or magical in ways that make debugging a nightmare.
So we built InjectQ.
"We wanted DI that feels like Python - not like a Java framework that got lost on its way to PyPI."
What is InjectQ?
InjectQ is a modern, lightweight Python dependency injection library focused on:
- ✅ Clarity and simplicity
- ✅ Type safety (works with mypy, pyright)
- ✅ Async-first APIs
- ✅ Seamless FastAPI & Taskiq integration
- ✅ Production-grade performance (270ns per bind)
Links:
Install in one line
pip install injectq
For framework integrations:
pip install injectq[fastapi] # FastAPI support
pip install injectq[taskiq] # Taskiq support
Quick Start - Zero config, maximum clarity
from injectq import InjectQ, inject, singleton
container = InjectQ.get_instance()
# Bind a value — dict-style, no ceremony
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())
main() # → Service says: Hello, World!
That's it. @singleton = one instance app-wide. @inject = auto-resolve from type hints. No XML, no 500-line config, no magic.
Core Features
🔧 Dict-like API
The simplest mental model possible:
from injectq import InjectQ
container = InjectQ.get_instance()
# Bind anything
container[str] = "config_value"
container[Database] = Database()
# Retrieve
db = container[Database]
🎯 Decorator + Type-based Injection
@inject
def process(service: UserService, db: Database):
# Both auto-resolved from the container
...
Also supports Inject[T] for inline type annotations that work with static type checkers.
🔄 Scopes and Lifetimes
from injectq import singleton, transient, scoped
@singleton
class Database: ... # One instance, lives forever
@transient
class Validator: ... # New instance every resolution
@scoped("request")
class RequestContext: ... # One per request scope
# Async scopes work too
async with container.scope("request"):
ctx1 = container.get(RequestContext)
ctx2 = container.get(RequestContext)
assert ctx1 is ctx2 # Same instance ✓
🆕 Hybrid Factories - The Feature That Changes Everything
This is new in v0.4 and it's genuinely great.
The problem: You have a factory that needs a database (DI-managed) and a user ID (runtime value). Old way is verbose:
# ❌ Old way — manually resolve everything
db = container[Database]
cache = container[Cache]
svc = container.call_factory("user_service", db, cache, "user123")
New way with invoke():
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)
# ✅ Auto-inject db and cache, you only pass what you know
svc = container.invoke("user_service", user_id="user123")
# Async version
svc = await container.ainvoke("async_service", batch_size=100)
InjectQ resolves what it knows from the container. You provide only the runtime-specific values. Clean.
🚀 FastAPI Integration
from typing import Annotated
from fastapi import FastAPI
from injectq import InjectQ, singleton
from injectq.integrations.fastapi import setup_fastapi, InjectFastAPI
app = FastAPI()
container = InjectQ.get_instance()
setup_fastapi(container, app)
@singleton
class UserService:
def get_user(self, user_id: int) -> dict:
return {"id": user_id}
@app.get("/users/{user_id}")
async def get_user(
user_id: int,
user_service: Annotated[UserService, InjectFastAPI(UserService)],
):
return user_service.get_user(user_id)
🧪 Testing — Mocking Without the Pain
from injectq.testing import override_dependency, test_container
# Override a specific dep for the duration of a block
with override_dependency(Database, MockDatabase()):
service = container.get(UserService)
# UserService gets MockDatabase here ✓
# Fully isolated test container — no global state bleed
with test_container() as tc:
tc.bind(Database, MockDatabase)
# Clean slate for each test
Pro tip: Use
InjectQ.test_mode()with pytest fixtures to auto-reset your container between tests.
🏗️ Modules and Providers
For larger apps, organize your bindings into modules:
from injectq.modules import Module, SimpleModule, ProviderModule, provider
class AppModule(Module):
def configure(self, binder):
binder.bind(Config, Config())
binder.bind(Database, Database)
class Providers(ProviderModule):
@provider
def make_notifier(self, db: Database, cfg: Config) -> Notifier:
return Notifier(db, cfg)
container = InjectQ(modules=[AppModule(), Providers()])
🛡️ Abstract Class Validation
InjectQ validates at bind time, not at resolution time:
from abc import ABC, abstractmethod
from injectq.utils.exceptions import BindingError
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount: float) -> str: ...
# ❌ Raises BindingError immediately — no surprises at runtime
container.bind(PaymentProcessor, PaymentProcessor)
# ✅ Correct — bind the concrete implementation
container.bind(PaymentProcessor, CreditCardProcessor)
Fail fast. Debug less.
Performance Benchmarks
InjectQ isn't just clean — it's fast.
| Operation | Speed |
|---|---|
| Basic bind / get | 270–780 nanoseconds |
| Dependency resolution | ~1 microsecond |
| 10-service web request simulation | 142 microseconds |
| 1,000+ concurrent operations | Sub-millisecond |
Thread-safe by default. Production-ready from day one.
Why InjectQ Over Alternatives?
| Feature | InjectQ | dependency-injector | injector |
|---|---|---|---|
| Dict-like API | ✅ | ❌ | ❌ |
| FastAPI integration | ✅ Built-in | ✅ | ❌ Manual |
| Hybrid factories (invoke) | ✅ | ❌ | ❌ |
| Async scope contexts | ✅ | ⚠️ Limited | ❌ |
| Testing utilities | ✅ Built-in | ✅ | ❌ Manual |
| Taskiq integration | ✅ | ❌ | ❌ |
| Abstract class guard | ✅ Bind-time | ❌ Runtime | ❌ Runtime |
TL;DR
If you're tired of:
- Manually wiring dependencies
- Global state leaking into tests
- Framework integrations that require 200 lines of glue code
InjectQ is for you.
pip install injectq
Built with ♥ by the 10xHub team. MIT Licensed. Contributions welcome.
Have questions or feature requests? Drop them in the comments or open an issue on GitHub. We read everything.
Top comments (0)