ctxinject


Namectxinject JSON
Version 0.1.2 PyPI version JSON
download
home_pageNone
SummaryContext-based dependency injection for Python
upload_time2025-08-01 14:26:00
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseNone
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.

[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Tests](https://github.com/bellirodrigo2/ctxinject/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/bellirodrigo2/ctxinject/actions)

[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Checked with mypy](https://img.shields.io/badge/mypy-checked-blue)](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[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)\r\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\r\n[![Tests](https://github.com/bellirodrigo2/ctxinject/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/bellirodrigo2/ctxinject/actions)\r\n\r\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\r\n[![Checked with mypy](https://img.shields.io/badge/mypy-checked-blue)](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"
}
        
Elapsed time: 1.65633s