RetroSys Core
A powerful, flexible Python dependency injection framework with support for synchronous and asynchronous services.
Overview
RetroSys Core provides a modern dependency injection system for Python applications, designed to:
- Simplify application architecture through dependency inversion
- Support both synchronous and asynchronous dependency resolution
- Provide lifecycle management (singleton, transient, and scoped instances)
- Support property and method injection alongside constructor injection
- Enable modular application design
- Facilitate testing through mock capabilities
The framework is designed to be lightweight yet powerful, with a focus on type safety and developer experience.
Features
Installation
pip install retrosys-core
Getting Started
Basic Usage
Here's a simple example that demonstrates the basics of dependency injection:
from retrosys.core.dependency_injection import Container, injectable
@injectable()
class DatabaseService:
def get_data(self):
return ["item1", "item2", "item3"]
@injectable()
class UserService:
def __init__(self, database_service: DatabaseService):
self.database_service = database_service
def get_user_data(self):
return self.database_service.get_data()
container = Container()
user_service = container.resolve(UserService)
data = user_service.get_user_data()
print(data)
Lifecycle Management
Control how your services are instantiated:
from retrosys.core.dependency_injection import Container, injectable, Lifecycle
@injectable(lifecycle=Lifecycle.SINGLETON)
class ConfigService:
def __init__(self):
print("ConfigService initialized")
self.settings = {"api_url": "https://api.example.com"}
@injectable(lifecycle=Lifecycle.TRANSIENT)
class RequestHandler:
def __init__(self, config: ConfigService):
print("RequestHandler initialized")
self.config = config
container = Container()
handler1 = container.resolve(RequestHandler)
handler2 = container.resolve(RequestHandler)
Property Injection
Use property injection when constructor injection isn't suitable:
from retrosys.core.dependency_injection import Container, injectable, inject_property
@injectable()
class LogService:
def log(self, message):
print(f"LOG: {message}")
@injectable()
class UserController:
@inject_property(LogService)
def logger(self):
pass
def create_user(self, username):
self.logger.log(f"Creating user: {username}")
return {"id": 1, "username": username}
container = Container()
controller = container.resolve(UserController)
controller.create_user("john")
Async Support
Use async for service initialization and resolution:
import asyncio
from retrosys.core.dependency_injection import Container, injectable
@injectable(is_async=True)
class AsyncDatabaseService:
async def __init__(self):
await asyncio.sleep(0.1)
self.connection = "db_connection"
print("Database connected")
async def get_data(self):
await asyncio.sleep(0.1)
return ["async_item1", "async_item2"]
@injectable(is_async=True)
class AsyncUserService:
def __init__(self, db: AsyncDatabaseService):
self.db = db
async def get_users(self):
return await self.db.get_data()
async def main():
container = Container()
user_service = await container.resolve_async(AsyncUserService)
users = await user_service.get_users()
print(users)
asyncio.run(main())
Modules
Organize your registrations using modules:
from retrosys.core.dependency_injection import Container, injectable, register_module
@injectable()
class Service1:
pass
@injectable()
class Service2:
pass
container = Container()
@register_module(container)
class InfrastructureModule:
@injectable()
class DatabaseService:
def get_connection(self):
return "database_connection"
@injectable()
class CacheService:
def cache(self, key, value):
print(f"Caching {key}: {value}")
db_service = container.resolve(InfrastructureModule.DatabaseService)
print(db_service.get_connection())
Testing with Mocks
Easily mock dependencies for testing:
from retrosys.core.dependency_injection import Container, injectable
import unittest
@injectable()
class EmailService:
def send_email(self, to, subject, body):
return True
@injectable()
class UserService:
def __init__(self, email_service: EmailService):
self.email_service = email_service
def register_user(self, email):
self.email_service.send_email(
email,
"Welcome!",
"Thank you for registering."
)
return True
class TestUserService(unittest.TestCase):
def test_register_user(self):
container = Container().enable_test_mode()
class MockEmailService:
def __init__(self):
self.emails_sent = []
def send_email(self, to, subject, body):
self.emails_sent.append((to, subject, body))
return True
mock_email = MockEmailService()
container.mock(EmailService, mock_email)
user_service = container.resolve(UserService)
result = user_service.register_user("user@example.com")
self.assertTrue(result)
self.assertEqual(len(mock_email.emails_sent), 1)
self.assertEqual(mock_email.emails_sent[0][0], "user@example.com")
container.disable_test_mode()
Advanced Usage
Factory Registration
Use factory functions for complex initialization:
from retrosys.core.dependency_injection import Container, Lifecycle
container = Container()
def create_database_connection(container):
connection_string = "db://example"
max_connections = 10
return {"connection": connection_string, "pool_size": max_connections}
container.register_factory(
dict,
create_database_connection,
lifecycle=Lifecycle.SINGLETON,
context_key="db_config"
)
db_config = container.resolve(dict, context_key="db_config")
print(db_config)
Scoped Lifecycle
Manage dependencies for specific operations:
from retrosys.core.dependency_injection import Container, injectable, Lifecycle
@injectable(lifecycle=Lifecycle.SCOPED)
class RequestContext:
def __init__(self):
self.user_id = None
self.request_id = None
@injectable()
class UserRepository:
def __init__(self, context: RequestContext):
self.context = context
def get_user_data(self):
return f"Data for user {self.context.user_id}"
container = Container()
scope = container.create_scope()
request_context = scope.resolve(RequestContext)
request_context.user_id = "user123"
request_context.request_id = "req456"
repo = scope.resolve(UserRepository)
print(repo.get_user_data())
scope2 = container.create_scope()
request_context2 = scope2.resolve(RequestContext)
request_context2.user_id = "user999"
repo2 = scope2.resolve(UserRepository)
print(repo2.get_user_data())
API Reference Highlights
Main Components
- Container: The central dependency container
- Lifecycle: Enum defining service lifecycles (SINGLETON, TRANSIENT, SCOPED)
- @injectable: Decorator for auto-registration of services
- @inject_property: Decorator for property injection
- @inject_method: Decorator for method injection
Container Methods
- register: Register a service with the container
- register_instance: Register an existing instance
- register_factory: Register a factory function
- resolve: Resolve a service instance
- resolve_async: Asynchronously resolve a service
- create_scope: Create a dependency scope
- register_module: Register a module with the container
- enable_test_mode / disable_test_mode: Methods for testing
- mock: Register a mock instance for testing
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.