Spring style dependency injection for python
Project description
AppCtx
Spring-style dependency injection for Python
Overview
AppCtx is a lightweight dependency injection (DI) container for Python, inspired by the Spring Framework. It uses decorator-based bean registration and auto-wiring via type annotations to manage dependencies in a clean, testable way.
Python Requirements: 3.8+ | License: MIT
Features
- 🚀 Decorator-based registration —
@bean/@componentto register,@post_constructfor lifecycle hooks - 🔄 Auto-wiring — Dependencies resolved from type annotations automatically
- 🔍 Circular dependency detection — Caught at startup, not runtime
- 📦 Lightweight — Zero mandatory dependencies beyond stdlib
- 🐍 Pythonic API — Feels natural in Python, not a Java port
- 🏗️ Multiple contexts — Isolated containers for different scopes
Installation
pip install appctx
Quick Start
1. Define and register beans
Use @bean on functions that create your objects. Dependencies are declared via type annotations:
from appctx import bean, get_bean, refresh
class DatabaseService:
def __init__(self, connection_string: str = "sqlite:///default.db"):
self.connection_string = connection_string
def connect(self):
return f"Connected to {self.connection_string}"
class UserService:
def __init__(self, db: DatabaseService):
self.db = db
def get_user(self, user_id: int):
return f"User {user_id} from {self.db.connect()}"
# Register beans
@bean
def database_service():
return DatabaseService("postgresql://localhost/myapp")
@bean
def user_service(db: DatabaseService): # Auto-injected by type
return UserService(db)
2. Initialize and use
# Initialize the container — resolves all dependencies
refresh()
# Retrieve the root application object (the only place you should call get_bean)
user_svc = get_bean(UserService)
print(user_svc.get_user(123))
Note:
get_bean()is only needed at the entry point to bootstrap your application. All other dependencies are injected automatically via constructor or function parameters — just like Spring.
Class-style beans
Decorate classes directly — the container instantiates them with resolved dependencies:
from appctx import bean, get_bean, refresh
@bean
class EmailService:
def __init__(self):
self.server = "smtp.example.com"
def send_email(self, to: str, subject: str):
return f"Email sent to {to} via {self.server}"
@bean
class NotificationService:
def __init__(self, email: EmailService):
self.email = email
def notify(self, user: str, message: str):
return self.email.send_email(user, "Notification", message)
refresh()
# Entry point — the only place you should call get_bean
notification_svc = get_bean(NotificationService)
print(notification_svc.notify("user@example.com", "Hello World!"))
Post-construct lifecycle
Use @post_construct to run initialization logic after all beans are created:
from appctx import post_construct
class DatabaseService:
def __init__(self):
self.connection = None
@post_construct
def init(self):
self.connection = create_connection() # Called automatically after construction
@bean
def database_service():
return DatabaseService()
Note:
@post_constructmethods run after all beans are created, so you can safely reference other beans during initialization.
How It Works
- Bean Registration: Use the
@beandecorator to register functions/classes that create beans - Auto-wiring: Dependencies are resolved from type annotations and injected automatically — no manual
get_bean()calls needed - Container Initialization: Call
refresh()to instantiate all beans in dependency order - Entry Point: Use
get_bean()once at the application root to retrieve the assembled object graph
Core Concepts
| Concept | Description |
|---|---|
| Bean | An object managed by the container |
| ApplicationContext | The DI container that holds and wires beans |
@bean |
Decorator that registers a function/class as a bean factory (alias: @component) |
refresh() |
Initializes the container, instantiates all beans in dependency order |
get_bean(T) |
Retrieve a bean by type or name (low-level; prefer auto-wiring) |
get_beans(T) |
Retrieve all beans of a given type (low-level; prefer auto-wiring) |
Dependency Resolution Rules
- Positional arguments → resolved by type annotation
- Keyword-only arguments (
*, name) → resolved by parameter name, falls back to default **kwargs→ receives all remaining beans not consumed by other parameters
Global Default Context
AppCtx provides a global default context for convenience, similar to Spring's application context. The top-level functions (bean, refresh, get_bean, add) operate on this shared instance:
from appctx import bean, refresh, get_bean
# These all use the same global ApplicationContext behind the scenes
@bean
def my_service():
return MyService()
refresh()
app = get_bean(MyService)
Custom Context (Isolation)
For libraries or tests that need isolation, create your own ApplicationContext:
from appctx import ApplicationContext
ctx = ApplicationContext()
@ctx.bean
def my_service():
return MyService()
ctx.refresh()
app = ctx.get_bean(MyService)
Note:
@beanon the global context registers beans immediately when the module is imported. For custom contexts, use@ctx.beanto register against that specific instance.
Organizing Beans Across Modules
AppCtx does not perform automatic package scanning. Instead, beans are registered at import time — when Python loads a module, any @bean decorators execute and register with the target context.
To wire beans across multiple modules, simply import them before calling refresh():
# main.py
from appctx import bean, refresh, get_bean
# Import modules so their @bean decorators fire
import myapp.services.user # registers UserService
import myapp.services.email # registers EmailService
import myapp.config # registers ConfigService
refresh()
app = get_bean(UserService)
You can also use add() to register a plain function or class (without the @bean decorator):
from appctx import add
def some_service():
return SomeService()
add(some_service) # Equivalent to @bean on some_service
Note: For modules, simply importing them is sufficient — the
@beandecorators fire at import time.add(module)is available as a semantic marker but the import alone triggers registration.
Best Practices
- Rely on auto-wiring, avoid
get_bean— Like Spring, let the container inject dependencies via function/constructor parameters. Reserveget_bean()only for bootstrapping the root application object. - Use type annotations — They are the primary mechanism for dependency resolution. Always annotate positional parameters.
- Single responsibility per bean — Each bean should have one clear purpose.
- Prefer constructor injection — Dependencies go in
__init__parameters, not hidden inside methods. - Keep beans stateless when possible — Makes testing and reasoning easier.
- Use
@post_constructfor initialization — Not__init__side-effects. Post-construct runs after all beans exist, allowing cross-bean setup. - Separate configuration from logic — Use dedicated config beans instead of hardcoding values.
- Design for testability — Beans that accept dependencies via constructor are trivially mockable in tests.
Documentation
- API Reference — Full API: decorators, container ops, dependency resolution, error handling
- Development Guide — Setup, testing, code quality, contributing
- Release Guide — Release process for maintainers
- Changelog — Version history
License
MIT — see LICENSE.
Links
- Repository: github.com/wssccc/appctx
- PyPI: pypi.org/project/appctx
- Issues: github.com/wssccc/appctx/issues
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file appctx-0.3.tar.gz.
File metadata
- Download URL: appctx-0.3.tar.gz
- Upload date:
- Size: 13.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62c56c0f4d7b5ef7d82cdce9df8ea6b5a6aa27879d8f4c9c3585359f04215418
|
|
| MD5 |
2679d012d2f158c7dcb49a5b279396aa
|
|
| BLAKE2b-256 |
ba907362d290eb91ff51e8b3aaedad481ea7509da93df0b44079c285382de2a4
|
Provenance
The following attestation bundles were made for appctx-0.3.tar.gz:
Publisher:
publish.yml on wssccc/appctx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
appctx-0.3.tar.gz -
Subject digest:
62c56c0f4d7b5ef7d82cdce9df8ea6b5a6aa27879d8f4c9c3585359f04215418 - Sigstore transparency entry: 1615464242
- Sigstore integration time:
-
Permalink:
wssccc/appctx@10a94b5e7b3cfbfbda0f46b33d6202485c93f63c -
Branch / Tag:
refs/tags/0.3 - Owner: https://github.com/wssccc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@10a94b5e7b3cfbfbda0f46b33d6202485c93f63c -
Trigger Event:
release
-
Statement type:
File details
Details for the file appctx-0.3-py3-none-any.whl.
File metadata
- Download URL: appctx-0.3-py3-none-any.whl
- Upload date:
- Size: 7.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e473fe68134a8ce766add41857bf6f3a4473ccd02bc9a222e6c363d4f1a95677
|
|
| MD5 |
0970c7217ae489d9f1e40e72bc0f05e0
|
|
| BLAKE2b-256 |
36b368ecff4b2de0f2dee09333f9ae80c4d7f03e9b4388b95ed1505e6e34eafe
|
Provenance
The following attestation bundles were made for appctx-0.3-py3-none-any.whl:
Publisher:
publish.yml on wssccc/appctx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
appctx-0.3-py3-none-any.whl -
Subject digest:
e473fe68134a8ce766add41857bf6f3a4473ccd02bc9a222e6c363d4f1a95677 - Sigstore transparency entry: 1615464246
- Sigstore integration time:
-
Permalink:
wssccc/appctx@10a94b5e7b3cfbfbda0f46b33d6202485c93f63c -
Branch / Tag:
refs/tags/0.3 - Owner: https://github.com/wssccc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@10a94b5e7b3cfbfbda0f46b33d6202485c93f63c -
Trigger Event:
release
-
Statement type: