Metadata-Version: 2.4
Name: pyinj
Version: 1.0.1b1
Summary: Type-safe, production-ready dependency injection for Python 3.13+
Project-URL: Homepage, https://github.com/qriusglobal/pyinj
Project-URL: Repository, https://github.com/qriusglobal/pyinj
Project-URL: Documentation, https://github.com/qriusglobal/pyinj#readme
Project-URL: Bug Tracker, https://github.com/qriusglobal/pyinj/issues
Project-URL: Changelog, https://github.com/qriusglobal/pyinj/releases
Project-URL: CI, https://github.com/qriusglobal/pyinj/actions
Author-email: Qrius Global <mishal@qrius.global>
License-Expression: MIT
License-File: LICENSE
Keywords: async,dependency-injection,di,ioc,python3.13,type-safe
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.13
Provides-Extra: dev
Requires-Dist: aiosqlite>=0.19; extra == 'dev'
Requires-Dist: asyncpg>=0.29; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: playwright>=1.44; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.2; extra == 'dev'
Requires-Dist: sqlalchemy>=2.0; extra == 'dev'
Description-Content-Type: text/markdown

# PyInj - Production-Ready Dependency Injection

[![Python Version](https://img.shields.io/badge/python-3.13+-blue.svg)](https://python.org)
[![Type Checked](https://img.shields.io/badge/type--checked-basedpyright-blue.svg)](https://github.com/DetachHead/basedpyright)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A **type-safe**, **production-ready** dependency injection container for Python 3.13+ that provides:

- 🚀 **Thread-safe and async-safe** resolution  
- ⚡ **O(1) performance** for type lookups
- 🔍 **Circular dependency detection**
- 🧹 **Automatic resource cleanup**
- 🛡️ **Protocol-based type safety**
- 🏭 **Metaclass auto-registration**
- 📦 **Zero external dependencies**

## Quick Start

```bash
# Install with UV (recommended)
uv add pyinj

# Or with pip
pip install pyinj
```

```python
from pyinj import Container, Token, Scope

# Create container
container = Container()

# Define token
DB_TOKEN = Token[Database]("database")

# Register provider
container.register(DB_TOKEN, create_database, Scope.SINGLETON)

# Resolve dependency
db = container.get(DB_TOKEN)

# Cleanup
await container.dispose()
```

## Why PyInj?

**Traditional DI libraries are over-engineered:**
- 20,000+ lines of code for simple dependency injection
- Heavy frameworks with steep learning curves  
- Poor async support and race conditions
- Memory leaks and thread safety issues

**PyInj is different:**
- ~200 lines of pure Python - easy to understand and debug
- Designed specifically for Python 3.13+ with no-GIL support
- Production-tested patterns with comprehensive safety guarantees
- Can be vendored directly or installed as a package

## Core Features

### 1. Type-Safe Dependencies

```python
from typing import Protocol, runtime_checkable
from pyinj import Container, Token

@runtime_checkable
class Logger(Protocol):
    def info(self, message: str) -> None: ...

class ConsoleLogger:
    def info(self, message: str) -> None:
        print(f"INFO: {message}")

container = Container()
logger_token = Token[Logger]("logger", protocol=Logger)
container.register(logger_token, ConsoleLogger, Scope.SINGLETON)

# Type-safe resolution
logger = container.get(logger_token)  # Type: Logger
logger.info("Hello, World!")
```

### 2. Automatic Dependency Injection

```python
from pyinj import Injectable

class EmailService(metaclass=Injectable):
    __injectable__ = True
    __token_name__ = "email_service" 
    __scope__ = Scope.SINGLETON
    
    def __init__(self, logger: Logger):
        self.logger = logger
    
    def send_email(self, to: str, subject: str) -> None:
        self.logger.info(f"Sending email to {to}")

# Automatically registered and dependencies resolved!
email_service = container.get(Injectable.get_registry()[EmailService])
```

### 3. Async-Safe with Proper Cleanup

```python
class DatabaseConnection:
    async def connect(self) -> None:
        print("Connecting to database...")
    
    async def aclose(self) -> None:
        print("Closing database connection...")

container.register(
    Token[DatabaseConnection]("db"), 
    DatabaseConnection, 
    Scope.SINGLETON
)

# Async resolution
db = await container.aget(Token[DatabaseConnection]("db"))
await db.connect()

# Automatic cleanup
await container.dispose()  # Safely closes all resources
```

### 4. Testing Made Easy

```python
# Production setup
container.register(logger_token, ConsoleLogger)

# Test override
test_logger = Mock(spec=Logger)
container.override(logger_token, test_logger)

# Test your code
service = container.get(service_token)
service.do_something()

# Verify interactions
test_logger.info.assert_called_with("Expected message")

# Cleanup
container.clear_overrides()
```

## Advanced Usage

### Protocol-Based Resolution

```python
# Resolve by protocol instead of token
@container.inject
def business_logic(logger: Logger, db: Database) -> str:
    logger.info("Processing business logic")
    return db.query("SELECT * FROM users")

# Dependencies automatically injected based on type hints
result = business_logic()
```

### Multiple Scopes

```python
from pyinj import Scope

# Singleton - one instance per container
container.register(config_token, load_config, Scope.SINGLETON)

# Transient - new instance every time  
container.register(request_token, create_request, Scope.TRANSIENT)

# Request/Session - scoped to request/session context
container.register(user_token, get_current_user, Scope.REQUEST)
```

### Async Patterns

```python
# Async providers
async def create_async_service() -> AsyncService:
    service = AsyncService()
    await service.initialize()
    return service

container.register(service_token, create_async_service, Scope.SINGLETON)

# Concurrent resolution with race condition protection
results = await asyncio.gather(*[
    container.aget(service_token) for _ in range(100)
])

# All results are the same instance (singleton)
assert all(r is results[0] for r in results)
```

## Performance

PyInj is optimized for production workloads:

- **O(1) type lookups** - Constant time resolution regardless of container size
- **Cached injection metadata** - Function signatures parsed once at decoration time  
- **Lock-free fast paths** - Singletons use double-checked locking pattern
- **Memory efficient** - Minimal overhead per registered dependency

```python
# Benchmark: 1000 services registered
# Resolution time: ~0.0001ms (O(1) guaranteed)
# Memory overhead: ~500 bytes per service
```

## Framework Integration

### FastAPI

```python
from fastapi import FastAPI, Depends
from pyinj import Container

app = FastAPI()
container = Container()

def get_service(container: Container = Depends(lambda: container)) -> MyService:
    return container.get(service_token)

@app.post("/users")
async def create_user(service: MyService = Depends(get_service)):
    return await service.create_user()
```

### Django/Flask

```python
# Django settings.py
from pyinj import Container

# Global container
DI_CONTAINER = Container()

# In views
def my_view(request):
    service = DI_CONTAINER.get(service_token)
    return service.handle_request(request)
```

### CLI Applications

```python
import click
from pyinj import Container

@click.command()
@click.pass_context
def cli(ctx):
    ctx.obj = Container()
    # Register services...

@cli.command()
@click.pass_context  
def process(ctx):
    container = ctx.obj
    service = container.get(service_token)
    service.process()
```

## Error Handling

PyInj provides clear, actionable error messages:

```python
# Circular dependency detection
Container Error: Cannot resolve token 'service_a':
  Resolution chain: service_a -> service_b -> service_a
  Cause: Circular dependency detected

# Missing provider
Container Error: Cannot resolve token 'missing_service':
  Resolution chain: root
  Cause: No provider registered for token 'missing_service'

# Type validation failure  
Container Error: Provider for token 'logger' returned <Mock>, expected <Logger>
```

## Migration Guide

### From dependency-injector

```python
# Before (dependency-injector)
from dependency_injector import containers, providers

class Container(containers.DeclarativeContainer):
    config = providers.Configuration()
    logger = providers.Singleton(Logger)

# After (PyInj)
from pyinj import Container, Token, Scope

container = Container()
logger_token = Token[Logger]("logger")
container.register(logger_token, Logger, Scope.SINGLETON)
```

### From injector

```python
# Before (injector)  
from injector import Injector, inject, singleton

injector = Injector()
injector.binder.bind(Logger, to=ConsoleLogger, scope=singleton)

@inject
def my_function(logger: Logger) -> None: ...

# After (PyInj)
container = Container()
container.register(Token[Logger]("logger"), ConsoleLogger, Scope.SINGLETON)

@container.inject
def my_function(logger: Logger) -> None: ...
```

## Development

```bash
# Clone repository
git clone <repo-url>
cd pyinj

# Install dependencies  
uv sync

# Run tests
pytest

# Type checking
basedpyright src/

# Format code
ruff format .

# Run all quality checks
ruff check . && basedpyright src/ && pytest
```

## Contributing

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make changes with tests
4. Ensure all quality checks pass
5. Submit a pull request

## License

MIT License - see [LICENSE](LICENSE) file for details.

## Why "PyInj"?

**Py** - Python-first design for modern Python 3.13+  
**Inj** - Injection (Dependency Injection)

PyInj follows the philosophy that **good software is simple software**. We provide exactly what you need for dependency injection - nothing more, nothing less.

---

**Ready to simplify your Python dependency injection?**

```bash
uv add pyinj
```