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 with package scanning and auto-wiring via type annotations to manage dependencies in a clean, testable way.
Python Requirements: 3.8+ | License: MIT
Features
- 🏷️ Pure marker decorators —
@beanfor functions,@componentfor classes — no coupling to container instances - 🔍 Package scanning —
ApplicationContext.scan()auto-discovers annotated objects - 🔄 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
- ✨ Custom bean names —
@bean(name="custom")/@component(name="custom")
Installation
pip install appctx
Quick Start
1. Define beans and components
Use @bean on functions (factories) and @component on classes:
# myapp/config.py
from appctx.decorators import bean
@bean
def db_config():
return {"host": "localhost", "port": 5432}
@bean(name="app_config") # Custom bean name
def application_config():
return {"name": "my_app", "debug": False}
# myapp/services.py
from appctx.decorators import component
@component
class UserService:
def __init__(self, db_config: dict):
self.db_config = db_config
def get_user(self, user_id: int):
return f"User {user_id} from {self.db_config['host']}"
@component(name="notification_svc")
class NotificationService:
def __init__(self, db_config: dict, app_config: dict):
self.db_config = db_config
self.app_config = app_config
2. Scan, refresh, and use
# main.py
from appctx import ApplicationContext
ctx = ApplicationContext()
ctx.scan("myapp").refresh()
# Entry point — the only place you should call get_bean
user_svc = ctx.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.
Post-construct lifecycle
Use @post_construct to run initialization logic after all beans are created:
from appctx.decorators import component, post_construct
@component
class DatabaseService:
def __init__(self):
self.connection = None
@post_construct
def init(self):
self.connection = create_connection() # Called after all beans are created
Core Concepts
| Concept | Description |
|---|---|
| Bean | An object managed by the container |
| ApplicationContext | The DI container that holds and wires beans |
@bean |
Pure marker decorator for functions (bean factories) |
@component |
Pure marker decorator for classes (components) |
scan() |
Discovers @bean/@component annotated objects in packages |
refresh() |
Instantiates all beans in dependency order |
add() |
Manually register an object without annotation |
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
Scanning
Package scanning
Scan an entire package recursively:
ctx = ApplicationContext()
ctx.scan("myapp").refresh()
Module scanning
Scan a single module:
ctx = ApplicationContext()
ctx.scan("myapp.services").refresh()
Auto-detection
Call scan() without arguments to auto-detect the caller's package:
# Called from within myapp/main.py
ctx = ApplicationContext()
ctx.scan().refresh() # Auto-detects "myapp" package
Exclude patterns
Exclude packages or modules using fnmatch glob patterns:
ctx = ApplicationContext()
ctx.scan("myapp", exclude=["myapp.tests", "myapp.internal_*"]).refresh()
Decorators
@bean — Function bean factories
from appctx.decorators import bean
@bean
def my_service():
return MyService()
@bean(name="custom_service")
def another_service():
return MyService()
- Only accepts functions — raises
TypeErroron classes - Sets
_is_bean = Trueand_bean_nameattributes - Use
@componentfor classes
@component — Class components
from appctx.decorators import component
@component
class MyComponent:
def __init__(self, dependency: SomeDependency):
self.dependency = dependency
@component(name="my_comp")
class AnotherComponent:
pass
- Only accepts classes — raises
TypeErroron functions - Sets
_is_component = Trueand_bean_nameattributes - Use
@beanfor functions
@post_construct — Lifecycle hooks
from appctx.decorators import post_construct
class MyService:
@post_construct
def init(self):
self.connection = create_connection()
Organizing Beans Across Modules
Define beans in separate modules, scan to discover them:
# myapp/__init__.py — empty or minimal
# myapp/config.py
from appctx.decorators import bean
@bean
def db_config():
return {"host": "localhost"}
# myapp/services.py
from appctx.decorators import component
@component
class UserService:
def __init__(self, db_config: dict):
self.db_config = db_config
# main.py
from appctx import ApplicationContext
ctx = ApplicationContext()
ctx.scan("myapp").refresh()
You can also manually register objects with add():
ctx = ApplicationContext()
ctx.add(my_factory_func).add(MyComponentClass)
ctx.refresh()
Best Practices
- Rely on auto-wiring, avoid
get_bean— Let the container inject dependencies via parameters. - Use type annotations — They are the primary mechanism for dependency resolution.
- Use
@beanfor functions,@componentfor classes — Semantic separation like Spring. - Prefer
scan()overadd()— Let the container discover beans automatically. - Use
@post_constructfor initialization — Not__init__side-effects. - Separate configuration from logic — Use dedicated config beans.
- Design for testability — Beans that accept dependencies via constructor are trivially mockable.
Documentation
- API Reference — Full API: decorators, container ops, dependency resolution
- 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.5.tar.gz.
File metadata
- Download URL: appctx-0.5.tar.gz
- Upload date:
- Size: 16.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a3b44b98f3145f24027c2297786ca60a3dd42133ca8f13a8c0a23d36f89de644
|
|
| MD5 |
e8f0c539502d98816e79da95cfad8283
|
|
| BLAKE2b-256 |
90cb86dc4150e1717b6dcddf920a1fc71e0e7866da52cbeb61b4004b879624b8
|
Provenance
The following attestation bundles were made for appctx-0.5.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.5.tar.gz -
Subject digest:
a3b44b98f3145f24027c2297786ca60a3dd42133ca8f13a8c0a23d36f89de644 - Sigstore transparency entry: 1616641504
- Sigstore integration time:
-
Permalink:
wssccc/appctx@a0c3525a374450e9d19a255b2e849916fb26d578 -
Branch / Tag:
refs/tags/0.5 - Owner: https://github.com/wssccc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a0c3525a374450e9d19a255b2e849916fb26d578 -
Trigger Event:
release
-
Statement type:
File details
Details for the file appctx-0.5-py3-none-any.whl.
File metadata
- Download URL: appctx-0.5-py3-none-any.whl
- Upload date:
- Size: 8.7 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 |
74a17bfe4821989f0b0f71d1bfe6061a5b6ccb1f0e24ce47c25f7ae23a30736a
|
|
| MD5 |
1a3fbbf1a3cd3c23fb14cc80462f6472
|
|
| BLAKE2b-256 |
1b73d70eb4319c70b9826681b75e632a7e4363656aabf31669c24b1220190787
|
Provenance
The following attestation bundles were made for appctx-0.5-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.5-py3-none-any.whl -
Subject digest:
74a17bfe4821989f0b0f71d1bfe6061a5b6ccb1f0e24ce47c25f7ae23a30736a - Sigstore transparency entry: 1616641754
- Sigstore integration time:
-
Permalink:
wssccc/appctx@a0c3525a374450e9d19a255b2e849916fb26d578 -
Branch / Tag:
refs/tags/0.5 - Owner: https://github.com/wssccc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a0c3525a374450e9d19a255b2e849916fb26d578 -
Trigger Event:
release
-
Statement type: