Name | ctxinject JSON |
Version |
0.1.2
JSON |
| download |
home_page | None |
Summary | Context-based dependency injection for Python |
upload_time | 2025-08-01 14:26:00 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.8 |
license | None |
keywords |
dependency
injection
context
async
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# ๐ ctxinject
A powerful, FastAPI-inspired dependency injection library for Python with async support, strong typing, and flexible injection strategies.
[](https://www.python.org/downloads/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/bellirodrigo2/ctxinject/actions)
[](https://github.com/psf/black)
[](http://mypy-lang.org/)
## โจ Key Features
- ๐ **FastAPI-style dependency injection** - Familiar `Depends()` pattern
- ๐๏ธ **Model field injection** - Direct access to model fields and methods in function signatures
- ๐ **Strongly typed** - Full type safety with automatic validation
- โก **Async/Sync support** - Works with both synchronous and asynchronous functions
- ๐ฏ **Multiple injection strategies** - By type, name, model fields, or dependencies
- โ
**Automatic validation** - Built-in Pydantic integration and custom validators
- ๐งช **Test-friendly** - Easy dependency overriding for testing
- ๐ **Python 3.8+** - Modern Python support
- ๐ **100% test coverage** - Production-ready reliability
## ๐ Quick Start
Here's a practical HTTP request processing example:
```python
import asyncio
from typing import cast
import requests
from pydantic import BaseModel
from typing_extensions import Annotated, Dict, Mapping, Optional, Protocol
from ctxinject.inject import inject_args
from ctxinject.model import DependsInject, ModelFieldInject
class PreparedRequest(Protocol):
method: str
url: str
headers: Mapping[str, str]
body: bytes
class BodyModel(BaseModel):
name: str
email: str
age: int
# Async dependency function
async def get_db() -> str:
await asyncio.sleep(0.1)
return "postgresql"
# Custom model field injector
class FromRequest(ModelFieldInject):
def __init__(self, field: Optional[str] = None, **kwargs):
super().__init__(PreparedRequest, field, **kwargs)
# Function with multiple injection strategies
def process_http(
url: Annotated[str, FromRequest()], # Extract from model field
method: Annotated[str, FromRequest()], # Extract from model field
body: Annotated[BodyModel, FromRequest()], # Extract and validate
headers: Annotated[Dict[str, str], FromRequest()], # Extract from model field
db: str = DependsInject(get_db), # Async dependency
) -> Mapping[str, str]:
return {
"url": url,
"method": method,
"body": body.name, # Pydantic model automatically validated
"headers": len(headers),
"db": db,
}
async def main():
# Create a prepared request
req = requests.Request(
method="POST",
url="https://api.example.com/user",
headers={"Content-Type": "application/json"},
json={"name": "Joรฃo Silva", "email": "joao@email.com", "age": 30}
)
prepared_req = cast(PreparedRequest, req.prepare())
# Inject dependencies
context = {PreparedRequest: prepared_req}
injected_func = await inject_args(process_http, context)
# Call with all dependencies resolved
result = injected_func()
print(result) # All dependencies automatically injected!
def mocked_get_db()->str:
return 'test'
injected_func = await inject_args(process_http, context, {get_db: mocked_get_db})
result = injected_func() # get_db mocked!
if __name__ == "__main__":
asyncio.run(main())
```
## ๐ฆ Installation
```bash
pip install ctxinject
```
For Pydantic validation support:
```bash
pip install ctxinject[pydantic]
```
## ๐ Usage Guide
### 1. Basic Dependency Injection
```python
from ctxinject.inject import inject_args
from ctxinject.model import ArgsInjectable
def greet(
name: str,
count: int = ArgsInjectable(5) # Optional with default
):
return f"Hello {name}! (x{count})"
# Inject by name and type
context = {"name": "Alice"}
injected = await inject_args(greet, context)
result = injected() # "Hello Alice! (x5)"
```
### 2. FastAPI-style Dependencies
```python
from ctxinject.model import DependsInject
def get_database_url() -> str:
return "postgresql://localhost/mydb"
async def get_user_service() -> UserService:
service = UserService()
await service.initialize()
return service
def process_request(
db_url: str = DependsInject(get_database_url),
user_service: UserService = DependsInject(get_user_service)
):
return f"Processing with {db_url}"
# Dependencies resolved automatically
injected = await inject_args(process_request, {})
result = injected()
```
### 3. Model Field Injection
```python
from ctxinject.model import ModelFieldInject
class Config:
database_url: str = "sqlite:///app.db"
debug: bool = True
def get_secret_key(self) -> str:
return "super-secret-key"
def initialize_app(
db_url: str = ModelFieldInject(Config, "database_url"),
debug: bool = ModelFieldInject(Config, "debug"),
secret: str = ModelFieldInject(Config, "get_secret_key") # Method call
):
return f"App: {db_url}, debug={debug}, secret={secret}"
config = Config()
context = {Config: config}
injected = await inject_args(initialize_app, context)
result = injected()
```
### 4. Validation and Type Conversion
```python
from typing_extensions import Annotated
from ctxinject.model import ArgsInjectable
def validate_positive(value: int, **kwargs) -> int:
if value <= 0:
raise ValueError("Must be positive")
return value
def process_data(
count: Annotated[int, ArgsInjectable(1, validate_positive)],
email: str = ArgsInjectable(...), # Automatic email validation if Pydantic available
):
return f"Processing {count} items for {email}"
context = {"count": 5, "email": "user@example.com"}
injected = await inject_args(process_data, context)
result = injected()
```
### 5. Partial Injection (Mixed Arguments)
```python
def process_user_data(
user_id: str, # Not injected - will remain as parameter
db_url: str = DependsInject(get_database_url),
config: Config = ModelFieldInject(Config)
):
return f"Processing user {user_id} with {db_url}"
# Only some arguments are injected
context = {Config: config_instance}
injected = await inject_args(process_user_data, context, allow_incomplete=True)
# user_id still needs to be provided
result = injected("user123") # "Processing user user123 with postgresql://..."
```
### 6. Function Signature Validation
Validate function signatures at bootstrap time to catch injection issues early. Unlike runtime errors, `func_signature_check()` returns all validation errors at once, giving you a complete overview of what needs to be fixed.
```python
from ctxinject.sigcheck import func_signature_check
def validate_at_startup():
# Check if function can be fully injected at bootstrap time
errors = func_signature_check(process_request, modeltype=[Config])
if errors:
print("Function cannot be fully injected:")
for error in errors:
print(f" - {error}")
else:
print("โ
Function is ready for injection!")
# Run validation before your app starts
validate_at_startup()
```
### 7. Testing with Overrides
```python
# Original dependency
async def get_real_service() -> str:
return "production-service"
def business_logic(service: str = DependsInject(get_real_service)):
return f"Using {service}"
# Test with mock
async def get_mock_service() -> str:
return "mock-service"
# Override for testing
injected = await inject_args(
business_logic,
context={},
overrides={get_real_service: get_mock_service}
)
result = injected() # "Using mock-service"
```
## ๐ฏ Injection Strategies
| Strategy | Description | Example |
|----------|-------------|---------|
| **By Name** | Match parameter name to context key | `{"param_name": value}` |
| **By Type** | Match parameter type to context type | `{MyClass: instance}` |
| **Model Field** | Extract field/method from model instance | `ModelFieldInject(Config, "field")` |
| **Dependency** | Call function to resolve value | `DependsInject(get_value)` |
| **Default** | Use default value from injectable | `ArgsInjectable(42)` |
## ๐ง Advanced Features
### Async Optimization
- Concurrent resolution of async dependencies
- Fast isinstance() checks for sync/async separation
- Optimal performance with minimal overhead
### Type Safety
- Full type checking with mypy support
- Runtime type validation
- Generic type support
### Extensible Validation
- Built-in Pydantic integration
- Custom validator functions
- Constraint validation (min/max, patterns, etc.)
## ๐๏ธ Architecture
ctxinject uses a resolver-based architecture:
1. **Analysis Phase**: Function signature is analyzed to identify injectable parameters
2. **Mapping Phase**: Parameters are mapped to appropriate resolvers based on injection strategy
3. **Resolution Phase**: Resolvers are executed (sync immediately, async concurrently)
4. **Injection Phase**: Resolved values are injected into the function
This design ensures optimal performance and flexibility.
## ๐ค Contributing
Contributions are welcome! Please check out our contributing guidelines and make sure all tests pass:
```bash
pytest --cov=ctxinject --cov-report=html
```
## ๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
## ๐ Related Projects
- [FastAPI](https://fastapi.tiangolo.com/) - The inspiration for the dependency injection pattern
- [Pydantic](https://pydantic-docs.helpmanual.io/) - Validation and serialization library
---
**ctxinject** - Powerful dependency injection for modern Python applications!
Raw data
{
"_id": null,
"home_page": null,
"name": "ctxinject",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "dependency, injection, context, async",
"author": null,
"author_email": "rodbell <bellirodrigo2@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/50/79/8aa5b570e1de32be2254f057d7a2696654c16542c631014c427a9948c57d/ctxinject-0.1.2.tar.gz",
"platform": null,
"description": "# \ud83d\ude80 ctxinject\r\nA powerful, FastAPI-inspired dependency injection library for Python with async support, strong typing, and flexible injection strategies.\r\n\r\n[](https://www.python.org/downloads/)\r\n[](https://opensource.org/licenses/MIT)\r\n[](https://github.com/bellirodrigo2/ctxinject/actions)\r\n\r\n[](https://github.com/psf/black)\r\n[](http://mypy-lang.org/)\r\n\r\n## \u2728 Key Features\r\n\r\n- \ud83d\ude80 **FastAPI-style dependency injection** - Familiar `Depends()` pattern\r\n- \ud83c\udfd7\ufe0f **Model field injection** - Direct access to model fields and methods in function signatures\r\n- \ud83d\udd12 **Strongly typed** - Full type safety with automatic validation\r\n- \u26a1 **Async/Sync support** - Works with both synchronous and asynchronous functions\r\n- \ud83c\udfaf **Multiple injection strategies** - By type, name, model fields, or dependencies\r\n- \u2705 **Automatic validation** - Built-in Pydantic integration and custom validators\r\n- \ud83e\uddea **Test-friendly** - Easy dependency overriding for testing\r\n- \ud83d\udc0d **Python 3.8+** - Modern Python support\r\n- \ud83d\udcca **100% test coverage** - Production-ready reliability\r\n\r\n## \ud83d\ude80 Quick Start\r\n\r\nHere's a practical HTTP request processing example:\r\n\r\n```python\r\nimport asyncio\r\nfrom typing import cast\r\nimport requests\r\nfrom pydantic import BaseModel\r\nfrom typing_extensions import Annotated, Dict, Mapping, Optional, Protocol\r\n\r\nfrom ctxinject.inject import inject_args\r\nfrom ctxinject.model import DependsInject, ModelFieldInject\r\n\r\n\r\nclass PreparedRequest(Protocol):\r\n method: str\r\n url: str\r\n headers: Mapping[str, str]\r\n body: bytes\r\n\r\n\r\nclass BodyModel(BaseModel):\r\n name: str\r\n email: str\r\n age: int\r\n\r\n\r\n# Async dependency function\r\nasync def get_db() -> str:\r\n await asyncio.sleep(0.1)\r\n return \"postgresql\"\r\n\r\n\r\n# Custom model field injector\r\nclass FromRequest(ModelFieldInject):\r\n def __init__(self, field: Optional[str] = None, **kwargs):\r\n super().__init__(PreparedRequest, field, **kwargs)\r\n\r\n\r\n# Function with multiple injection strategies\r\ndef process_http(\r\n url: Annotated[str, FromRequest()], # Extract from model field\r\n method: Annotated[str, FromRequest()], # Extract from model field\r\n body: Annotated[BodyModel, FromRequest()], # Extract and validate\r\n headers: Annotated[Dict[str, str], FromRequest()], # Extract from model field\r\n db: str = DependsInject(get_db), # Async dependency\r\n) -> Mapping[str, str]:\r\n return {\r\n \"url\": url,\r\n \"method\": method,\r\n \"body\": body.name, # Pydantic model automatically validated\r\n \"headers\": len(headers),\r\n \"db\": db,\r\n }\r\n\r\n\r\nasync def main():\r\n # Create a prepared request\r\n req = requests.Request(\r\n method=\"POST\",\r\n url=\"https://api.example.com/user\",\r\n headers={\"Content-Type\": \"application/json\"},\r\n json={\"name\": \"Jo\u00e3o Silva\", \"email\": \"joao@email.com\", \"age\": 30}\r\n )\r\n prepared_req = cast(PreparedRequest, req.prepare())\r\n \r\n # Inject dependencies\r\n context = {PreparedRequest: prepared_req}\r\n injected_func = await inject_args(process_http, context)\r\n \r\n # Call with all dependencies resolved\r\n result = injected_func()\r\n print(result) # All dependencies automatically injected!\r\n\r\n def mocked_get_db()->str:\r\n return 'test'\r\n\r\n injected_func = await inject_args(process_http, context, {get_db: mocked_get_db})\r\n result = injected_func() # get_db mocked!\r\n\r\nif __name__ == \"__main__\":\r\n asyncio.run(main())\r\n```\r\n\r\n## \ud83d\udce6 Installation\r\n\r\n```bash\r\npip install ctxinject\r\n```\r\n\r\nFor Pydantic validation support:\r\n```bash\r\npip install ctxinject[pydantic]\r\n```\r\n\r\n## \ud83d\udcd6 Usage Guide\r\n\r\n### 1. Basic Dependency Injection\r\n\r\n```python\r\nfrom ctxinject.inject import inject_args\r\nfrom ctxinject.model import ArgsInjectable\r\n\r\ndef greet(\r\n name: str,\r\n count: int = ArgsInjectable(5) # Optional with default\r\n):\r\n return f\"Hello {name}! (x{count})\"\r\n\r\n# Inject by name and type\r\ncontext = {\"name\": \"Alice\"}\r\ninjected = await inject_args(greet, context)\r\nresult = injected() # \"Hello Alice! (x5)\"\r\n```\r\n\r\n### 2. FastAPI-style Dependencies\r\n\r\n```python\r\nfrom ctxinject.model import DependsInject\r\n\r\ndef get_database_url() -> str:\r\n return \"postgresql://localhost/mydb\"\r\n\r\nasync def get_user_service() -> UserService:\r\n service = UserService()\r\n await service.initialize()\r\n return service\r\n\r\ndef process_request(\r\n db_url: str = DependsInject(get_database_url),\r\n user_service: UserService = DependsInject(get_user_service)\r\n):\r\n return f\"Processing with {db_url}\"\r\n\r\n# Dependencies resolved automatically\r\ninjected = await inject_args(process_request, {})\r\nresult = injected()\r\n```\r\n\r\n### 3. Model Field Injection\r\n\r\n```python\r\nfrom ctxinject.model import ModelFieldInject\r\n\r\nclass Config:\r\n database_url: str = \"sqlite:///app.db\"\r\n debug: bool = True\r\n \r\n def get_secret_key(self) -> str:\r\n return \"super-secret-key\"\r\n\r\ndef initialize_app(\r\n db_url: str = ModelFieldInject(Config, \"database_url\"),\r\n debug: bool = ModelFieldInject(Config, \"debug\"),\r\n secret: str = ModelFieldInject(Config, \"get_secret_key\") # Method call\r\n):\r\n return f\"App: {db_url}, debug={debug}, secret={secret}\"\r\n\r\nconfig = Config()\r\ncontext = {Config: config}\r\ninjected = await inject_args(initialize_app, context)\r\nresult = injected()\r\n```\r\n\r\n### 4. Validation and Type Conversion\r\n\r\n```python\r\nfrom typing_extensions import Annotated\r\nfrom ctxinject.model import ArgsInjectable\r\n\r\ndef validate_positive(value: int, **kwargs) -> int:\r\n if value <= 0:\r\n raise ValueError(\"Must be positive\")\r\n return value\r\n\r\ndef process_data(\r\n count: Annotated[int, ArgsInjectable(1, validate_positive)],\r\n email: str = ArgsInjectable(...), # Automatic email validation if Pydantic available\r\n):\r\n return f\"Processing {count} items for {email}\"\r\n\r\ncontext = {\"count\": 5, \"email\": \"user@example.com\"}\r\ninjected = await inject_args(process_data, context)\r\nresult = injected()\r\n```\r\n\r\n### 5. Partial Injection (Mixed Arguments)\r\n\r\n```python\r\ndef process_user_data(\r\n user_id: str, # Not injected - will remain as parameter\r\n db_url: str = DependsInject(get_database_url),\r\n config: Config = ModelFieldInject(Config)\r\n):\r\n return f\"Processing user {user_id} with {db_url}\"\r\n\r\n# Only some arguments are injected\r\ncontext = {Config: config_instance}\r\ninjected = await inject_args(process_user_data, context, allow_incomplete=True)\r\n\r\n# user_id still needs to be provided\r\nresult = injected(\"user123\") # \"Processing user user123 with postgresql://...\"\r\n```\r\n\r\n### 6. Function Signature Validation\r\n\r\nValidate function signatures at bootstrap time to catch injection issues early. Unlike runtime errors, `func_signature_check()` returns all validation errors at once, giving you a complete overview of what needs to be fixed.\r\n\r\n```python\r\nfrom ctxinject.sigcheck import func_signature_check\r\n\r\ndef validate_at_startup():\r\n # Check if function can be fully injected at bootstrap time\r\n errors = func_signature_check(process_request, modeltype=[Config])\r\n \r\n if errors:\r\n print(\"Function cannot be fully injected:\")\r\n for error in errors:\r\n print(f\" - {error}\")\r\n else:\r\n print(\"\u2705 Function is ready for injection!\")\r\n\r\n# Run validation before your app starts\r\nvalidate_at_startup()\r\n```\r\n\r\n### 7. Testing with Overrides\r\n\r\n```python\r\n# Original dependency\r\nasync def get_real_service() -> str:\r\n return \"production-service\"\r\n\r\ndef business_logic(service: str = DependsInject(get_real_service)):\r\n return f\"Using {service}\"\r\n\r\n# Test with mock\r\nasync def get_mock_service() -> str:\r\n return \"mock-service\"\r\n\r\n# Override for testing\r\ninjected = await inject_args(\r\n business_logic, \r\n context={},\r\n overrides={get_real_service: get_mock_service}\r\n)\r\nresult = injected() # \"Using mock-service\"\r\n```\r\n\r\n## \ud83c\udfaf Injection Strategies\r\n\r\n| Strategy | Description | Example |\r\n|----------|-------------|---------|\r\n| **By Name** | Match parameter name to context key | `{\"param_name\": value}` |\r\n| **By Type** | Match parameter type to context type | `{MyClass: instance}` |\r\n| **Model Field** | Extract field/method from model instance | `ModelFieldInject(Config, \"field\")` |\r\n| **Dependency** | Call function to resolve value | `DependsInject(get_value)` |\r\n| **Default** | Use default value from injectable | `ArgsInjectable(42)` |\r\n\r\n## \ud83d\udd27 Advanced Features\r\n\r\n### Async Optimization\r\n- Concurrent resolution of async dependencies\r\n- Fast isinstance() checks for sync/async separation\r\n- Optimal performance with minimal overhead\r\n\r\n### Type Safety\r\n- Full type checking with mypy support\r\n- Runtime type validation\r\n- Generic type support\r\n\r\n### Extensible Validation\r\n- Built-in Pydantic integration\r\n- Custom validator functions\r\n- Constraint validation (min/max, patterns, etc.)\r\n\r\n## \ud83c\udfd7\ufe0f Architecture\r\n\r\nctxinject uses a resolver-based architecture:\r\n\r\n1. **Analysis Phase**: Function signature is analyzed to identify injectable parameters\r\n2. **Mapping Phase**: Parameters are mapped to appropriate resolvers based on injection strategy\r\n3. **Resolution Phase**: Resolvers are executed (sync immediately, async concurrently)\r\n4. **Injection Phase**: Resolved values are injected into the function\r\n\r\nThis design ensures optimal performance and flexibility.\r\n\r\n## \ud83e\udd1d Contributing\r\n\r\nContributions are welcome! Please check out our contributing guidelines and make sure all tests pass:\r\n\r\n```bash\r\npytest --cov=ctxinject --cov-report=html\r\n```\r\n\r\n## \ud83d\udcc4 License\r\n\r\nThis project is licensed under the MIT License - see the LICENSE file for details.\r\n\r\n## \ud83d\udd17 Related Projects\r\n\r\n- [FastAPI](https://fastapi.tiangolo.com/) - The inspiration for the dependency injection pattern\r\n- [Pydantic](https://pydantic-docs.helpmanual.io/) - Validation and serialization library\r\n\r\n---\r\n\r\n**ctxinject** - Powerful dependency injection for modern Python applications!\r\n",
"bugtrack_url": null,
"license": null,
"summary": "Context-based dependency injection for Python",
"version": "0.1.2",
"project_urls": null,
"split_keywords": [
"dependency",
" injection",
" context",
" async"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "caaa32c094e91bf55dc689d0745a0972f117151961185043d39dccb31006b55f",
"md5": "74c0344126203f8137bcf574a83858fa",
"sha256": "d09d389d379ba9ccf9ed74e771b32cef8716fab8290ef4b383847a014ba51302"
},
"downloads": -1,
"filename": "ctxinject-0.1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "74c0344126203f8137bcf574a83858fa",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 20654,
"upload_time": "2025-08-01T14:25:59",
"upload_time_iso_8601": "2025-08-01T14:25:59.028841Z",
"url": "https://files.pythonhosted.org/packages/ca/aa/32c094e91bf55dc689d0745a0972f117151961185043d39dccb31006b55f/ctxinject-0.1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "50798aa5b570e1de32be2254f057d7a2696654c16542c631014c427a9948c57d",
"md5": "0d79a20ea90d65e2aca5cb6134682f5b",
"sha256": "4e8dcad3c59036a756ba6e6f07b048d85f1f1d5ddfa50af3ce0cf483509475a9"
},
"downloads": -1,
"filename": "ctxinject-0.1.2.tar.gz",
"has_sig": false,
"md5_digest": "0d79a20ea90d65e2aca5cb6134682f5b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 49203,
"upload_time": "2025-08-01T14:26:00",
"upload_time_iso_8601": "2025-08-01T14:26:00.156190Z",
"url": "https://files.pythonhosted.org/packages/50/79/8aa5b570e1de32be2254f057d7a2696654c16542c631014c427a9948c57d/ctxinject-0.1.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-01 14:26:00",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "ctxinject"
}