Tools
Tools: Stop Wiring Dependencies by Hand - Meet InjectQ, Python DI Done Right
2026-02-22
0 views
admin
What is InjectQ? ## Install in one line ## Quick Start - Zero config, maximum clarity ## Core Features ## π§ Dict-like API ## π― Decorator + Type-based Injection ## π Scopes and Lifetimes ## π Hybrid Factories - The Feature That Changes Everything ## π FastAPI Integration ## π§ͺ Testing β Mocking Without the Pain ## ποΈ Modules and Providers ## π‘οΈ Abstract Class Validation ## Performance Benchmarks ## Why InjectQ Over Alternatives? 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. "We wanted DI that feels like Python - not like a Java framework that got lost on its way to PyPI." InjectQ is a modern, lightweight Python dependency injection library focused on: For framework integrations: That's it. @singleton = one instance app-wide. @inject = auto-resolve from type hints. No XML, no 500-line config, no magic. The simplest mental model possible: Also supports Inject[T] for inline type annotations that work with static type checkers. 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: New way with invoke(): InjectQ resolves what it knows from the container. You provide only the runtime-specific values. Clean. Pro tip: Use InjectQ.test_mode() with pytest fixtures to auto-reset your container between tests. For larger apps, organize your bindings into modules: InjectQ validates at bind time, not at resolution time: Fail fast. Debug less. InjectQ isn't just clean β it's fast. Thread-safe by default. Production-ready from day one. 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. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK:
pip install injectq Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
pip install injectq COMMAND_BLOCK:
pip install injectq COMMAND_BLOCK:
pip install injectq[fastapi] # FastAPI support
pip install injectq[taskiq] # Taskiq support Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
pip install injectq[fastapi] # FastAPI support
pip install injectq[taskiq] # Taskiq support COMMAND_BLOCK:
pip install injectq[fastapi] # FastAPI support
pip install injectq[taskiq] # Taskiq support COMMAND_BLOCK:
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! Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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! COMMAND_BLOCK:
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! COMMAND_BLOCK:
from injectq import InjectQ container = InjectQ.get_instance() # Bind anything
container[str] = "config_value"
container[Database] = Database() # Retrieve
db = container[Database] Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
from injectq import InjectQ container = InjectQ.get_instance() # Bind anything
container[str] = "config_value"
container[Database] = Database() # Retrieve
db = container[Database] COMMAND_BLOCK:
from injectq import InjectQ container = InjectQ.get_instance() # Bind anything
container[str] = "config_value"
container[Database] = Database() # Retrieve
db = container[Database] COMMAND_BLOCK:
@inject
def process(service: UserService, db: Database): # Both auto-resolved from the container ... Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
@inject
def process(service: UserService, db: Database): # Both auto-resolved from the container ... COMMAND_BLOCK:
@inject
def process(service: UserService, db: Database): # Both auto-resolved from the container ... COMMAND_BLOCK:
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 β Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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 β COMMAND_BLOCK:
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 β COMMAND_BLOCK:
# β Old way β manually resolve everything
db = container[Database]
cache = container[Cache]
svc = container.call_factory("user_service", db, cache, "user123") Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
# β Old way β manually resolve everything
db = container[Database]
cache = container[Cache]
svc = container.call_factory("user_service", db, cache, "user123") COMMAND_BLOCK:
# β Old way β manually resolve everything
db = container[Database]
cache = container[Cache]
svc = container.call_factory("user_service", db, cache, "user123") COMMAND_BLOCK:
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) Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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) COMMAND_BLOCK:
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) COMMAND_BLOCK:
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) Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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) COMMAND_BLOCK:
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) COMMAND_BLOCK:
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 Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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 COMMAND_BLOCK:
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 COMMAND_BLOCK:
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()]) Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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()]) COMMAND_BLOCK:
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()]) COMMAND_BLOCK:
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) Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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) COMMAND_BLOCK:
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) COMMAND_BLOCK:
pip install injectq Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
pip install injectq COMMAND_BLOCK:
pip install injectq - β
Clarity and simplicity
- β
Type safety (works with mypy, pyright)
- β
Async-first APIs
- β
Seamless FastAPI & Taskiq integration
- β
Production-grade performance (270ns per bind) - Manually wiring dependencies
- Global state leaking into tests
- Framework integrations that require 200 lines of glue code - π GitHub β star us if this helps!
- π Full Documentation
how-totutorialguidedev.toaimlpythondatabasegitgithub