@inject Decorator - InjectQ Documentation
Author: InjectQ Contributors
Description: Modern Python dependency injection library

Skip to content

# @inject Decorator

The `@inject` decorator is InjectQ's most powerful and recommended way to inject dependencies. It automatically resolves and injects dependencies based on type hints, making your code clean and declarative.

## 🎯 Basic Usage

The `@inject` decorator eliminates manual dependency management:

    from injectq import InjectQ, inject

    # Set up container
    container = InjectQ.get_instance()
    container[Database] = Database
    container[UserService] = UserService

    # Use @inject decorator
    @inject
    def process_user(service: UserService, db: Database) -> None:
        # Dependencies automatically injected
        user = service.get_user(1)
        db.save(user)

    # Call without parameters - dependencies injected automatically
    process_user()

## 🏗️ How It Works

### Automatic Resolution

The `@inject` decorator analyzes function signatures and resolves dependencies:

    @inject
    def create_report(
        user_service: UserService,
        analytics: AnalyticsService,
        cache: Cache,
        config: AppConfig
    ) -> Report:
        # InjectQ automatically:
        # 1. Gets UserService from container
        # 2. Gets AnalyticsService from container
        # 3. Gets Cache from container
        # 4. Gets AppConfig from container
        # 5. Calls the function with all dependencies
        pass

### Type-Based Resolution

Dependencies are resolved based on type hints:

    class IUserRepository(Protocol):
        def get_by_id(self, id: int) -> Optional[User]: ...

    class UserRepository:
        def get_by_id(self, id: int) -> Optional[User]:
            # Implementation
            pass

    # Register implementation
    container.bind(IUserRepository, UserRepository)

    @inject
    def get_user(repo: IUserRepository) -> Optional[User]:
        # InjectQ finds UserRepository for IUserRepository
        return repo.get_by_id(1)

## 🎨 Advanced Patterns

### Async Functions

Works seamlessly with async functions:

    @inject
    async def process_user_async(service: UserService, user_id: int) -> User:
        # All dependencies injected
        user = await service.get_user_async(user_id)
        return user

    # Usage
    result = await process_user_async(user_id=123)

### Class Methods

Can be used on class methods:

    class UserController:
        @inject
        def get_user(self, service: UserService, user_id: int) -> User:
            # 'self' is not injected, other parameters are
            return service.get_user(user_id)

        @classmethod
        @inject
        def create_user(cls, service: UserService, data: dict) -> User:
            # 'cls' is not injected
            return service.create_user(data)

### Static Methods

Works with static methods:

    class UserUtils:
        @staticmethod
        @inject
        def validate_user(service: UserService, user_id: int) -> bool:
            user = service.get_user(user_id)
            return user is not None and user.is_active

### Nested Injection

Dependencies can have their own dependencies:

    class Database:
        def __init__(self, config: DatabaseConfig):
            self.config = config

    class UserRepository:
        def __init__(self, db: Database):
            self.db = db

    class UserService:
        def __init__(self, repo: UserRepository):
            self.repo = repo

    # Register only the top-level service
    container[UserService] = UserService

    @inject
    def use_service(service: UserService) -> None:
        # InjectQ automatically creates:
        # 1. DatabaseConfig
        # 2. Database (with DatabaseConfig)
        # 3. UserRepository (with Database)
        # 4. UserService (with UserRepository)
        pass

## 🔧 Integration Patterns

### With FastAPI

    from fastapi import FastAPI
    from injectq.integrations.fastapi import Injected

    app = FastAPI()

    @app.get("/users/{user_id}")
    @inject
    def get_user(user_id: int, service: UserService) -> User:
        return service.get_user(user_id)

    # Or using Injected type
    @app.get("/users/{user_id}")
    def get_user(user_id: int, service: Injected[UserService]) -> User:
        return service.get_user(user_id)

### With Classes

Use `@inject` on `__init__` methods:

    class UserController:
        @inject
        def __init__(self, service: UserService, logger: Logger):
            self.service = service
            self.logger = logger

        def get_user(self, user_id: int) -> User:
            self.logger.info(f"Getting user {user_id}")
            return self.service.get_user(user_id)

    # Usage
    controller = UserController()  # Dependencies automatically injected

### With Context Managers

    class DatabaseTransaction:
        @inject
        def __init__(self, db: Database):
            self.db = db

        def __enter__(self):
            self.db.begin_transaction()
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type:
                self.db.rollback()
            else:
                self.db.commit()

    @inject
    def process_with_transaction(service: UserService) -> None:
        with DatabaseTransaction() as tx:
            # Transaction automatically injected
            service.update_user(1, {"name": "New Name"})

## 🧪 Testing with @inject

### Mock Dependencies

    from injectq.testing import override_dependency

    def test_user_service():
        mock_service = MockUserService()

        with override_dependency(UserService, mock_service):
            # @inject decorated functions use the mock
            result = get_user(user_id=1)
            assert result.name == "Mock User"

### Test Containers

    from injectq.testing import test_container

    def test_with_isolation():
        with test_container() as container:
            # Set up test dependencies
            container.bind(UserService, MockUserService)
            container.bind(Database, MockDatabase)

            # Test the function
            result = get_user(user_id=1)
            assert result is not None

## 🚨 Error Handling

### Missing Dependencies

    @inject
    def process_data(service: UserService) -> None:
        pass

    # If UserService is not registered
    try:
        process_data()
    except DependencyNotFoundError as e:
        print(f"Missing dependency: {e}")

### Circular Dependencies

    class A:
        def __init__(self, b: B):
            self.b = b

    class B:
        def __init__(self, a: A):  # Circular!
            self.a = a

    container.bind(A, A)
    container.bind(B, B)

    @inject
    def use_a(a: A) -> None:
        pass

    # Will raise CircularDependencyError
    use_a()

## ⚡ Performance Considerations

### Compilation

For better performance in production:

    # Pre-compile dependency resolution
    container.compile()

    # Now @inject functions resolve faster
    @inject
    def fast_function(service: UserService) -> None:
        pass

### Caching

Resolved instances are cached based on scope:

    @inject
    def use_service(service: UserService) -> None:
        pass

    # First call - creates UserService
    use_service()

    # Second call - reuses cached UserService (if singleton)
    use_service()

## 🏆 Best Practices

### 1. Use Type Hints

    # ✅ Good - explicit type hints
    @inject
    def process_user(service: UserService, user_id: int) -> User:
        pass

    # ❌ Avoid - missing type hints
    @inject
    def process_user(service, user_id):
        pass

### 2. Prefer Protocols

    # ✅ Good - depend on abstractions
    class IUserService(Protocol):
        def get_user(self, id: int) -> User: ...

    @inject
    def process_user(service: IUserService) -> None:
        pass

    # ❌ Avoid - depend on concrete classes
    @inject
    def process_user(service: UserService) -> None:
        pass

### 3. Keep Functions Focused

    # ✅ Good - single responsibility
    @inject
    def create_user(service: UserService, data: CreateUserRequest) -> User:
        return service.create_user(data)

    @inject
    def send_welcome_email(email_service: EmailService, user: User) -> None:
        email_service.send_welcome(user)

    # ❌ Avoid - multiple responsibilities
    @inject
    def create_user_and_send_email(
        user_service: UserService,
        email_service: EmailService,
        data: CreateUserRequest
    ) -> User:
        user = user_service.create_user(data)
        email_service.send_welcome(user)  # Multiple concerns
        return user

### 4. Handle Optional Dependencies

    # ✅ Good - optional dependencies
    @inject
    def log_request(logger: Optional[Logger], request: Request) -> None:
        if logger:
            logger.info(f"Request: {request.path}")

    # ✅ Good - default values
    @inject
    def process_data(cache: Optional[Cache] = None) -> None:
        if cache:
            # Use cache
            pass
        else:
            # Cache not available
            pass

### 5. Use Descriptive Names

    # ✅ Good - descriptive parameter names
    @inject
    def authenticate_user(
        auth_service: AuthenticationService,
        user_credentials: UserCredentials
    ) -> AuthResult:
        pass

    # ❌ Avoid - unclear names
    @inject
    def auth(s: AuthenticationService, c: UserCredentials) -> AuthResult:
        pass

## 🔄 Comparison with Other Patterns

### @inject vs Dict Interface

    # Dict interface - manual resolution
    def process_user(user_id: int) -> User:
        service = container[UserService]
        return service.get_user(user_id)

    # @inject - automatic resolution
    @inject
    def process_user(service: UserService, user_id: int) -> User:
        return service.get_user(user_id)

### @inject vs Inject() Function

    # Inject() function - explicit injection
    def process_user(user_id: int, service=Inject(UserService)) -> User:
        return service.get_user(user_id)

    # @inject - implicit injection
    @inject
    def process_user(service: UserService, user_id: int) -> User:
        return service.get_user(user_id)

## 🎯 When to Use @inject

### ✅ Ideal For

- Most applications - Recommended default approach
- Complex dependency graphs - Automatic resolution
- Type safety - Full mypy support
- Clean code - Declarative dependency specification
- Testing - Easy to mock and override

### ⚠️ Considerations

- Performance - Slight overhead for resolution (can be optimized)
- Debugging - Dependencies not visible in function calls
- Learning curve - Need to understand type hints

## 🎉 Summary

The `@inject` decorator provides:

- Automatic dependency resolution - No manual wiring
- Type-based injection - Uses type hints for resolution
- Clean syntax - Declarative and readable
- Full type safety - Works with mypy and IDEs
- Async support - Works with async functions
- Testing friendly - Easy to override dependencies

Key benefits: - Eliminates boilerplate dependency management - Makes dependencies explicit through type hints - Enables easy testing with dependency overrides - Works seamlessly with all InjectQ features - Provides excellent IDE support and autocomplete

Ready to explore the Inject() function?