Spring style dependency injection for python
Project description
AppCtx
Spring-style dependency injection for Python
Overview
AppCtx is a lightweight dependency injection container inspired by the Spring Framework, providing a clean and elegant dependency management solution for Python applications. It makes it easy to manage dependencies and create maintainable, testable code.
Python Requirements: 3.8+
License: MIT
Features
- 🚀 Easy to Use - Register and inject dependencies with simple decorators
- 🔄 Auto-wiring - Automatic dependency resolution based on type annotations
- 🏗️ Flexible Configuration - Support for both function and class bean definitions
- 📦 Lightweight - Minimal dependencies, focused on core functionality
- 🐍 Pythonic - API design that follows Python conventions
- 🔧 Python 3.8+ - Compatible with Python 3.8, 3.9, 3.10, and 3.11
- 🎯 Decorator-based bean registration - Simple
@beandecorator for registration - 🔍 Circular dependency detection - Detects and reports circular dependencies
Installation
pip install appctx
Development Installation
For development, clone the repository and install with development dependencies:
git clone https://github.com/wssccc/appctx.git
cd appctx
pip install -e .[dev]
This will install the following development tools:
pytest>=7.0- Testing frameworkpytest-cov>=4.0- Coverage reportingblack>=22.0- Code formattingflake8>=5.0- Lintingmypy>=1.0- Type checkingisort>=5.0- Import sorting
Basic Concepts
AppCtx provides a simple way to manage dependencies in your Python applications:
- Beans: Objects managed by the container
- Container: The
ApplicationContextthat manages beans - Dependency Injection: Automatic wiring of dependencies based on type annotations
Quick Start
Basic Usage
from appctx import bean, get_bean, refresh
# Define service classes
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):
connection = self.db.connect()
return f"User {user_id} from {connection}"
# Register beans using decorators
@bean
def database_service():
return DatabaseService("postgresql://localhost/myapp")
@bean
def user_service(db: DatabaseService): # Auto-inject DatabaseService
return UserService(db)
# Initialize the container
refresh()
# Get and use beans
user_svc = get_bean(UserService)
print(user_svc.get_user(123))
Class Decorator Usage
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()
notification_svc = get_bean(NotificationService)
print(notification_svc.notify("user@example.com", "Hello World!"))
How It Works
- Bean Registration: Use the
@beandecorator to register functions that create beans - Type Annotations: Use type annotations to declare dependencies
- Container Initialization: Call
refresh()to initialize the container and resolve dependencies - Bean Retrieval: Use
get_bean()to retrieve beans by type or name
Advanced Usage
Custom Application Context
from appctx import ApplicationContext
# Create custom context
ctx = ApplicationContext()
@ctx.bean
def my_service():
return MyService()
ctx.refresh()
service = ctx.get_bean(MyService)
Multiple Beans of Same Type
@bean
def primary_db():
return DatabaseService("primary://db")
@bean
def secondary_db():
return DatabaseService("secondary://db")
refresh()
# Get all database services
dbs = get_beans(DatabaseService)
print(f"Found {len(dbs)} database services")
# Get all beans of a specific type
databases = get_beans(DatabaseService)
print(len(databases)) # 2
Named Bean Retrieval
@bean
def database_service():
return DatabaseService("app.db")
refresh()
# Get bean by name (function name)
db = get_bean("database_service")
Post-Construct Initialization
Use the @post_construct decorator to execute initialization logic after bean construction:
class EmailService:
def __init__(self, server: str):
self.server = server
self.connected = False
@post_construct
def connect(self):
# Automatically called after construction
self.connection = f"Connected to {self.server}"
self.connected = True
@post_construct
def verify_connection(self):
# Multiple post_construct methods are supported
assert self.connected
@bean
def config_service():
return ConfigService("smtp.example.com")
@bean
def email_service(config: ConfigService):
# Config is injected before post_construct runs
return EmailService(config.smtp_server)
refresh()
email = get_bean(EmailService)
print(email.connection) # "Connected to smtp.example.com"
API Reference
Core Decorators
@bean
Register a function or class as a bean.
@bean
def my_service():
return MyService()
@bean
class MyComponent:
def __init__(self, dependency: SomeDependency):
self.dependency = dependency
@post_construct
Mark a method to be called automatically after the bean has been constructed and all dependencies have been injected. The method should only accept self as a parameter and should not return any value. If the method raises an exception, the bean will not be registered in the container.
class DatabaseService:
def __init__(self):
self.connection = None
@post_construct
def init(self):
# Initialize the database connection
self.connection = create_connection()
self.setup_tables()
@bean
def database_service():
return DatabaseService()
Important notes:
- Only non-private methods (not starting with
_) are considered - Multiple methods can be annotated with
@post_constructin the same class - If a
@post_constructmethod raises an exception, the bean is removed from the container @post_constructmethods are called after all beans are created, similar to Spring's@PostConstruct- All beans exist in the container during
@post_constructexecution - Order of
@post_constructcalls depends on bean registration order, not dependency relationships
Container Operations
refresh()
Initialize the container and instantiate all beans. Must be called before getting beans.
refresh()
get_bean(key)
Get a bean by type or name.
# Get by type
service = get_bean(MyService)
# Get by name
service = get_bean("my_service")
get_beans(type)
Get all beans of a specific type.
services = get_beans(MyService)
Dependency Resolution
AppCtx uses a sophisticated dependency resolution strategy that handles different parameter types:
- Positional Arguments - Resolved by type annotations only. Must have type annotations to be resolved.
- Keyword-only Arguments - Resolved by parameter name first, then use default values if available.
- **Variable Keyword Arguments (kwargs) - Injects all remaining beans that haven't been used as other parameters.
- Auto-wiring - Container automatically resolves and injects dependencies based on the above rules.
- Circular Dependency Detection - Detects and reports circular dependency issues.
Parameter Resolution Examples
@bean
def config_service():
return "config_value"
@bean
def database_service():
return "database_url"
# Positional args - resolved by type annotations
@bean
def service_with_positional(config_service: str):
return f"Service: {config_service}"
# Keyword-only args - resolved by name
@bean
def service_with_keyword_only(*, config_service, timeout=30):
return f"Service: {config_service}, timeout={timeout}"
# **kwargs - gets all remaining beans
@bean
def flexible_service(**kwargs):
return f"Flexible: {kwargs}"
Error Handling
Common Errors
# Bean not found
try:
service = get_bean(UnknownService)
except KeyError as e:
print(f"Bean not found: {e}")
# Multiple beans of same type conflict
try:
refresh()
except RuntimeError as e:
print(f"Bean instantiation failed: {e}")
# Circular dependency
try:
refresh()
except RuntimeError as e:
print(f"Circular dependency detected: {e}")
Best Practices
- Use Type Annotations - Specify dependency types clearly for better code readability
- Single Responsibility - Each bean should have a clear responsibility
- Interface Abstraction - Use abstract base classes to define service interfaces
- Configuration Separation - Centralize bean configuration management
- Test-Friendly - Design beans that are easy to test
Development
Requirements
- Python 3.8 or higher
- pip
Running Tests
pytest tests/
Running Tests with Coverage
pytest --cov=src/appctx tests/
Code Formatting
Format code with Black (line length: 88):
black src/ tests/
Import Sorting
Sort imports with isort (Black profile):
isort src/ tests/
Linting
Check code quality with flake8:
flake8 src/ tests/
Type Checking
Run type checking with mypy:
mypy src/
Run All Quality Checks
# Format code
black src/ tests/
isort src/ tests/
# Run linting and type checking
flake8 src/ tests/
mypy src/
# Run tests with coverage
pytest --cov=src/appctx tests/
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
We welcome contributions! Please see our contributing guidelines below.
Development Setup
- Fork the project
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Create a Pull Request
Running Tests
# Run all tests
pytest
# Run tests with coverage
pytest --cov=appctx
# Run linting
black --check src/ tests/
flake8 src/ tests/
mypy src/
Release Process
For maintainers, see RELEASE.md for detailed release instructions.
Links
- Homepage: https://github.com/wssccc/appctx
- Repository: https://github.com/wssccc/appctx
- PyPI: https://pypi.org/project/appctx/
- Issues: https://github.com/wssccc/appctx/issues
Changelog
v0.1.0
- Initial release
- Basic dependency injection functionality
- Decorator API
- Auto-wiring support
- Python 3.8+ support
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.2.1.tar.gz.
File metadata
- Download URL: appctx-0.2.1.tar.gz
- Upload date:
- Size: 14.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aec206fe54ea47e698656484f530b6e44efecabf05b87d54bd67771a013ac789
|
|
| MD5 |
99dce6c0f211695c0d5213ac56bc5c0a
|
|
| BLAKE2b-256 |
32606cff104424a0e41422a7625d3a8ebf295a64a715ffc6eb29a69518ea6c6d
|
Provenance
The following attestation bundles were made for appctx-0.2.1.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.2.1.tar.gz -
Subject digest:
aec206fe54ea47e698656484f530b6e44efecabf05b87d54bd67771a013ac789 - Sigstore transparency entry: 947889493
- Sigstore integration time:
-
Permalink:
wssccc/appctx@225b6be9ac21381bb57a01f646b3d946f9c5a86d -
Branch / Tag:
refs/tags/0.2.1 - Owner: https://github.com/wssccc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@225b6be9ac21381bb57a01f646b3d946f9c5a86d -
Trigger Event:
release
-
Statement type:
File details
Details for the file appctx-0.2.1-py3-none-any.whl.
File metadata
- Download URL: appctx-0.2.1-py3-none-any.whl
- Upload date:
- Size: 8.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d3136c067d7a5c98c1cbdf2709c11ade6a739a2bdc65b1ea8af71e626104f46f
|
|
| MD5 |
d4ad91621d5ac9b06770e8eb622b4acf
|
|
| BLAKE2b-256 |
388ab6acb2db1182db033aa2005c24798093505ce9f6513cdc4ce6b9ef3e3c09
|
Provenance
The following attestation bundles were made for appctx-0.2.1-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.2.1-py3-none-any.whl -
Subject digest:
d3136c067d7a5c98c1cbdf2709c11ade6a739a2bdc65b1ea8af71e626104f46f - Sigstore transparency entry: 947889542
- Sigstore integration time:
-
Permalink:
wssccc/appctx@225b6be9ac21381bb57a01f646b3d946f9c5a86d -
Branch / Tag:
refs/tags/0.2.1 - Owner: https://github.com/wssccc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@225b6be9ac21381bb57a01f646b3d946f9c5a86d -
Trigger Event:
release
-
Statement type: