Name | ctxinject JSON |
Version |
0.1.12
JSON |
| download |
home_page | None |
Summary | Context-based dependency injection for Python |
upload_time | 2025-08-22 23:28:43 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.8 |
license | None |
keywords |
di
context
async
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# ๐ ctxinject
A flexible dependency injection library for Python that adapts to your function signatures. Write functions however you want - ctxinject figures out the dependencies.
[](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
- ๐ **Context managers** - Automatic resource management for dependencies
- โก **Priority-based async execution** - Control execution order with async batching
- โ
**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 with Context Managers
```python
from ctxinject.model import DependsInject
from contextlib import asynccontextmanager
def get_database_url() -> str:
return "postgresql://localhost/mydb"
@asynccontextmanager
async def get_user_service():
service = UserService()
await service.initialize()
try:
yield service
finally:
await service.close()
def process_request(
db_url: str = DependsInject(get_database_url),
user_service: UserService = DependsInject(get_user_service, order=1) # Priority order
):
return f"Processing with {db_url}"
# Dependencies resolved automatically, resources managed
async with AsyncExitStack() as stack:
injected = await inject_args(process_request, {}, stack=stack)
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
- Priority-based execution with `order` parameter
- Fast isinstance() checks for sync/async separation
- Optimized mode with pre-computed execution plans
### Context Manager Support
- Automatic resource management for dependencies
- Support for both sync and async context managers
- Proper cleanup even on exceptions
### 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.)
### Performance Optimization
```python
# Use ordered=True for maximum performance
injected = await inject_args(func, context, ordered=True)
```
## ๐๏ธ 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": "DI, context, async",
"author": null,
"author_email": "rodbell <bellirodrigo2@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/2a/4c/caa178775658930be0f0125d68a5e5dae0776b45e2e6875056a4f4d155b6/ctxinject-0.1.12.tar.gz",
"platform": null,
"description": "# \ud83d\ude80 ctxinject\r\nA flexible dependency injection library for Python that adapts to your function signatures. Write functions however you want - ctxinject figures out the dependencies.\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- \ud83d\udd04 **Context managers** - Automatic resource management for dependencies\r\n- \u26a1 **Priority-based async execution** - Control execution order with async batching\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 with Context Managers\r\n\r\n```python\r\nfrom ctxinject.model import DependsInject\r\nfrom contextlib import asynccontextmanager\r\n\r\ndef get_database_url() -> str:\r\n return \"postgresql://localhost/mydb\"\r\n\r\n@asynccontextmanager\r\nasync def get_user_service():\r\n service = UserService()\r\n await service.initialize()\r\n try:\r\n yield service\r\n finally:\r\n await service.close()\r\n\r\ndef process_request(\r\n db_url: str = DependsInject(get_database_url),\r\n user_service: UserService = DependsInject(get_user_service, order=1) # Priority order\r\n):\r\n return f\"Processing with {db_url}\"\r\n\r\n# Dependencies resolved automatically, resources managed\r\nasync with AsyncExitStack() as stack:\r\n injected = await inject_args(process_request, {}, stack=stack)\r\n result = 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- Priority-based execution with `order` parameter\r\n- Fast isinstance() checks for sync/async separation\r\n- Optimized mode with pre-computed execution plans\r\n\r\n### Context Manager Support\r\n- Automatic resource management for dependencies\r\n- Support for both sync and async context managers\r\n- Proper cleanup even on exceptions\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### Performance Optimization\r\n```python\r\n# Use ordered=True for maximum performance\r\ninjected = await inject_args(func, context, ordered=True)\r\n```\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.12",
"project_urls": null,
"split_keywords": [
"di",
" context",
" async"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "8209e6471a843292e1a38f3531ee6a56f509196e812c749adc10cbc11443729a",
"md5": "392a33b445618d3a09d711ad33bd046f",
"sha256": "c54bcde6dea941cb1a94b70421d8758111fd146483da4b62fa28bba536997275"
},
"downloads": -1,
"filename": "ctxinject-0.1.12-py3-none-any.whl",
"has_sig": false,
"md5_digest": "392a33b445618d3a09d711ad33bd046f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 27296,
"upload_time": "2025-08-22T23:28:41",
"upload_time_iso_8601": "2025-08-22T23:28:41.732670Z",
"url": "https://files.pythonhosted.org/packages/82/09/e6471a843292e1a38f3531ee6a56f509196e812c749adc10cbc11443729a/ctxinject-0.1.12-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "2a4ccaa178775658930be0f0125d68a5e5dae0776b45e2e6875056a4f4d155b6",
"md5": "6426da683ed4b1912addd548c95802ea",
"sha256": "248acce7edf4489ffcf6ac19a2048583b574b55a1ad8ff69d36b594bcd033159"
},
"downloads": -1,
"filename": "ctxinject-0.1.12.tar.gz",
"has_sig": false,
"md5_digest": "6426da683ed4b1912addd548c95802ea",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 64218,
"upload_time": "2025-08-22T23:28:43",
"upload_time_iso_8601": "2025-08-22T23:28:43.244302Z",
"url": "https://files.pythonhosted.org/packages/2a/4c/caa178775658930be0f0125d68a5e5dae0776b45e2e6875056a4f4d155b6/ctxinject-0.1.12.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-22 23:28:43",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "ctxinject"
}