Koin-like lightweight dependency injection container for Python with type inference support
Project description
KotInjection
Koin-like Dependency Injection Container for Python
KotInjection is a lightweight DI (Dependency Injection) container library for Python, inspired by Kotlin's Koin. It features type inference-based automatic dependency resolution and Koin-style DSL syntax.
Features
- Simple API - Intuitive DSL similar to Koin
- Type Inference Support - Automatic dependency resolution using Python type hints
- Lifecycle Management - Supports singleton and factory patterns
- Eager Initialization - Optional
created_at_startfor singleton pre-loading (like Koin) - Lazy Injection - Class attribute lazy dependency injection (like Koin's
by inject()) - Context Isolation - Independent container instances for isolated DI contexts
- Lightweight - Pure Python implementation with no external dependencies
- Type Safe - Safe dependency management through type hints
Installation
pip install kotinjection
Quick Start
Basic Usage
from kotinjection import KotInjection, KotInjectionModule
# Define dependencies
class Database:
def __init__(self):
self.connection = "db://localhost"
class UserRepository:
def __init__(self, db: Database):
self.db = db
def get_users(self):
return f"Users from {self.db.connection}"
# Create a module
module = KotInjectionModule()
with module:
module.single[Database](lambda: Database())
module.single[UserRepository](
lambda: UserRepository(db=module.get())
)
# Initialize the DI container
KotInjection.start(modules=[module])
# Retrieve dependencies
repo = KotInjection.get[UserRepository]()
print(repo.get_users()) # "Users from db://localhost"
# Stop when done
KotInjection.stop()
Lifecycle Management
# Singleton (same instance is reused)
module.single[Database](lambda: Database())
# Factory (new instance created each time)
module.factory[RequestHandler](
lambda: RequestHandler(repo=module.get())
)
Eager Initialization
By default, singletons are lazily initialized on first access. Use created_at_start=True to initialize at start() time.
# Definition level - specific singleton is eagerly initialized
module.single[Database](lambda: Database(), created_at_start=True)
# Module level - all singletons in this module are eagerly initialized
module = KotInjectionModule(created_at_start=True)
with module:
module.single[Database](lambda: Database()) # Eager
module.single[Cache](lambda: Cache(), created_at_start=False) # Override: Lazy
Lazy Injection (Class Attributes)
Similar to Koin's by inject(), you can define dependencies as class attributes that are resolved lazily on first access.
from kotinjection import KotInjection
class MyService:
# Dependency is resolved on first access, not at class definition time
repository = KotInjection.inject[UserRepository]
def get_users(self):
return self.repository.get_users()
# Initialize after class definition
KotInjection.start(modules=[module])
# Dependency is resolved when accessed
service = MyService()
service.get_users() # repository is resolved here
Context Isolation
Context Isolation provides independent DI container instances that are completely separate from the global container. Ideal for library development, multi-tenant applications, and test isolation.
Basic Usage
from kotinjection import KotInjectionCore, KotInjectionModule
# Create an isolated container instance
module = KotInjectionModule()
with module:
module.single[MyService](lambda: MyService())
app = KotInjectionCore(modules=[module])
# Retrieve dependencies from the instance
service = app.get[MyService]()
Use Case 1: Library Development
Create libraries that don't conflict with the host application's DI.
from kotinjection import (
KotInjectionCore,
IsolatedKotInjectionComponent,
KotInjectionModule
)
# Define a library-specific container
library_module = KotInjectionModule()
with library_module:
library_module.single[LibraryRepository](lambda: LibraryRepository())
library_app = KotInjectionCore(modules=[library_module])
# Base class for library components
class LibraryComponent(IsolatedKotInjectionComponent):
def get_app(self):
return library_app
# Actual service class
class MyLibraryService(LibraryComponent):
def __init__(self):
# Get dependencies from the isolated container
self.repository = self.get[LibraryRepository]()
def do_something(self):
return self.repository.fetch_data()
Use Case 2: Multi-Tenant
Each tenant can have an independent DI environment.
# Tenant 1's container
tenant1_app = KotInjectionCore(modules=[tenant1_module])
# Tenant 2's container
tenant2_app = KotInjectionCore(modules=[tenant2_module])
# Use independent dependencies for each tenant
tenant1_service = tenant1_app.get[Service]()
tenant2_service = tenant2_app.get[Service]()
Use Case 3: Test Isolation
Use an independent DI container for each test case.
import unittest
from kotinjection import KotInjectionCore
class TestMyService(unittest.TestCase):
def test_with_isolated_container(self):
# Test-specific isolated container
with KotInjectionCore(modules=[test_module]) as app:
service = app.get[MyService]()
# Run tests...
# Automatically cleaned up when exiting the context
API Reference
For complete API documentation, see API Reference.
Quick Reference
| Class | Description |
|---|---|
KotInjection |
Global DI container API |
KotInjectionCore |
Isolated container instance |
KotInjectionModule |
Dependency definitions container |
IsolatedKotInjectionComponent |
Base class for isolated components |
create_inject |
Create inject proxy for isolated containers |
Key Methods
# Global API
KotInjection.start(modules=[...]) # Initialize
KotInjection.get[Type]() # Retrieve dependency (eager)
KotInjection.inject[Type] # Lazy injection (class attribute)
KotInjection.stop() # Cleanup
# Module Definition
module = KotInjectionModule(created_at_start=True) # Eager init for all singletons
module.single[Type](factory) # Singleton (lazy by default)
module.single[Type](factory, created_at_start=True) # Singleton (eager)
module.factory[Type](factory) # Factory
module.get() # Type inference in factories
# Isolated Container
create_inject(app) # Create inject proxy for isolated containers
Advanced Usage
Multiple Modules
# Database module
db_module = KotInjectionModule()
with db_module:
db_module.single[Database](lambda: Database())
db_module.single[CacheService](lambda: CacheService())
# Repository module
repo_module = KotInjectionModule()
with repo_module:
repo_module.single[UserRepository](
lambda: UserRepository(
db=repo_module.get(),
cache=repo_module.get()
)
)
# Initialize with all modules
KotInjection.start(modules=[db_module, repo_module])
Type Inference with Isolated Containers
# Create the app first
app = KotInjectionCore()
# Use module.get() in module definitions
module = KotInjectionModule()
with module:
module.single[Repository](lambda: Repository())
module.single[Service](lambda: Service(repo=module.get()))
# Load modules
app.load_modules([module])
# Retrieve dependencies
service = app.get[Service]()
Mixing Manual Instances with Type Inference
module.get() resolves dependencies based on sequential call order. When mixing manually instantiated objects with module.get(), use keyword arguments for clarity.
Pattern That Doesn't Work
When module.get() call order doesn't match parameter order, type inference fails:
class UserRepository:
def __init__(self, redis: Redis, db: Database):
...
# module.get() returns Redis (index 0), but we want Database!
module.single[UserRepository](
lambda: UserRepository(Redis(host="localhost"), module.get())
)
Recommended: Use Keyword Arguments
module.single[UserRepository](
lambda: UserRepository(redis=Redis(host="localhost"), db=module.get())
)
Alternative: Use Index Parameter
Specify the parameter index explicitly with module.get(index):
# module.get(1) resolves the second parameter (Database)
module.single[UserRepository](
lambda: UserRepository(Redis(host="localhost"), module.get(1))
)
Comparison with Koin
| Feature | Koin (Kotlin) | KotInjection (Python) |
|---|---|---|
| DSL Syntax | single { }, factory { } |
module.single[T], module.factory[T] |
| Type Inference | get() |
module.get() |
| Lazy Injection | by inject() |
KotInjection.inject[T] |
| Context Isolation | koinApplication { } |
KotInjectionCore() |
| Scope Management | Multiple scopes | Singleton/Factory only |
| Context Manager | N/A | with statement support |
Development Guidelines
Running Tests
python -m unittest discover tests
Type Hints
All type hints are required for dependency resolution.
class MyService:
def __init__(self, repo: Repository): # Type hint required
self.repo = repo
License
MIT License
Acknowledgements
This project is inspired by Kotlin's Koin.
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 kotinjection-0.0.5.tar.gz.
File metadata
- Download URL: kotinjection-0.0.5.tar.gz
- Upload date:
- Size: 25.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.13.9 Linux/6.11.0-1018-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
908ba3e43e6eb8c3e2c35cc844f81c91d013f4a6e724f9e13b1d21af47eff03f
|
|
| MD5 |
2e859a13cf4a9e016c3a9a9e3a151d32
|
|
| BLAKE2b-256 |
974dd20353ff6db6ca19decc478ee243342d6d572d56560a8573b2e28d41a7e3
|
File details
Details for the file kotinjection-0.0.5-py3-none-any.whl.
File metadata
- Download URL: kotinjection-0.0.5-py3-none-any.whl
- Upload date:
- Size: 32.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.13.9 Linux/6.11.0-1018-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b3778140fd31f0b5f36ad8ca32b99d1800b18873c53f67911e3c8ba0e081fcb
|
|
| MD5 |
4ace331cb47f7f9518a97716a8e91e17
|
|
| BLAKE2b-256 |
0d61a5151c2e9b7a7137738da51abbc8947588fc662c35d6dbc1782909f3e54c
|