Dict-like Interface - InjectQ Documentation
Author: InjectQ Contributors
Description: Modern Python dependency injection library

Skip to content

# Dict-like Interface

InjectQ's dict-like interface provides the simplest way to get started with dependency injection. It treats the container like a dictionary where you can store and retrieve services using keys.

## 🎯 Basic Usage

The dict-like interface is perfect for simple applications and getting started quickly:

    from injectq import InjectQ

    # Create container
    container = InjectQ.get_instance()

    # Store services like a dictionary
    container[str] = "Hello, InjectQ!"
    container[int] = 42
    container["database_url"] = "postgresql://localhost/db"

    # Retrieve services
    message = container[str]      # "Hello, InjectQ!"
    number = container[int]       # 42
    db_url = container["database_url"]  # "postgresql://localhost/db"

## 🏗️ Class Registration

Register classes for automatic instantiation:

    class DatabaseConfig:
        def __init__(self, host: str = "localhost", port: int = 5432):
            self.host = host
            self.port = port
            self.url = f"postgresql://{host}:{port}/mydb"

    class Database:
        def __init__(self, config: DatabaseConfig):
            self.config = config
            print(f"Connected to: {config.url}")

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

    # Register classes
    container[DatabaseConfig] = DatabaseConfig()
    container[Database] = Database
    container[UserRepository] = UserRepository

    # Automatic dependency resolution
    repo = container[UserRepository]  # Creates DatabaseConfig, Database, then UserRepository

## 🔄 Key Operations

### Setting Values

    # Simple values
    container[str] = "configuration"
    container[int] = 12345
    container[bool] = True

    # Complex objects
    container["config"] = AppConfig(host="prod", debug=False)

    # Classes (for automatic instantiation)
    container[Database] = Database
    container[UserService] = UserService

    # Instances (pre-created objects)
    container["cache"] = RedisCache(host="localhost")

### Getting Values

    # Simple retrieval
    config = container[str]
    number = container[int]

    # With type hints (better IDE support)
    config: str = container[str]
    service: UserService = container[UserService]

### Checking Existence

    # Check if a service is registered
    if str in container:
        config = container[str]

    if "database" in container:
        db = container["database"]

### Removing Services

    # Remove a service
    del container[str]
    del container[Database]

    # Check removal
    assert str not in container
    assert Database not in container

## 🎨 Advanced Patterns

### Factory Functions

Use lambda functions for dynamic creation:

    import uuid
    from datetime import datetime

    # Factory for unique IDs
    container["request_id"] = lambda: str(uuid.uuid4())

    # Factory for timestamps
    container["timestamp"] = lambda: datetime.now().isoformat()

    # Each access creates a new value
    id1 = container["request_id"]
    id2 = container["request_id"]
    print(f"IDs are different: {id1 != id2}")  # True

### Conditional Registration

Register services based on environment:

    if environment == "production":
        container[Database] = PostgreSQLDatabase
        container["cache"] = RedisCache(host="prod-redis")
    elif environment == "testing":
        container[Database] = SQLiteDatabase
        container["cache"] = MemoryCache()
    else:
        container[Database] = InMemoryDatabase
        container["cache"] = MemoryCache()

### Named Services

Use strings as keys for multiple implementations:

    # Multiple cache implementations
    container["redis_cache"] = RedisCache(host="localhost")
    container["memory_cache"] = MemoryCache()
    container["file_cache"] = FileCache(path="/tmp/cache")

    # Usage
    cache = container["redis_cache"]
    backup_cache = container["memory_cache"]

## 🔧 Integration with Decorators

The dict-like interface works seamlessly with InjectQ's decorators:

    from injectq import inject, singleton

    # Register services
    container[Database] = Database
    container["config"] = AppConfig()

    # Use with @inject
    @inject
    def process_data(db: Database, config: dict) -> None:
        # db and config automatically injected
        print(f"Processing with config: {config}")

    # Call without parameters
    process_data()

## 🧪 Testing with Dict Interface

The dict-like interface makes testing easy:

    from injectq.testing import test_container

    def test_user_service():
        with test_container() as container:
            # Set up test dependencies
            container[Database] = MockDatabase()
            container["config"] = {"test": True}

            # Get service under test
            service = container[UserService]

            # Test the service
            result = service.get_user(1)
            assert result is not None

## 🚀 Real-World Example

Here's a complete example using the dict-like interface:

    from injectq import InjectQ
    from typing import List, Optional
    from dataclasses import dataclass

    @dataclass
    class User:
        id: int
        name: str
        email: str

    class UserRepository:
        def __init__(self, db_url: str):
            self.db_url = db_url
            self.users = {}  # In-memory storage

        def save(self, user: User) -> User:
            self.users[user.id] = user
            return user

        def find_by_id(self, user_id: int) -> Optional[User]:
            return self.users.get(user_id)

        def find_all(self) -> List[User]:
            return list(self.users.values())

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

        def create_user(self, name: str, email: str) -> User:
            user_id = len(self.repo.users) + 1
            user = User(id=user_id, name=name, email=email)
            return self.repo.save(user)

        def get_user(self, user_id: int) -> Optional[User]:
            return self.repo.find_by_id(user_id)

    # Application setup
    container = InjectQ.get_instance()

    # Configuration
    container[str] = "postgresql://localhost:5432/myapp"  # Database URL
    container[int] = 300  # Cache timeout in seconds

    # Services
    container[UserRepository] = UserRepository
    container[UserService] = UserService

    # Usage
    service = container[UserService]

    # Create users
    user1 = service.create_user("John Doe", "john@example.com")
    user2 = service.create_user("Jane Smith", "jane@example.com")

    # Retrieve users
    found_user = service.get_user(1)
    print(f"Found user: {found_user}")

    # List all users
    all_users = container[UserRepository].find_all()
    print(f"All users: {all_users}")

## ⚖️ When to Use Dict Interface

### ✅ Good For

- Simple applications - Quick setup without complex configuration
- Configuration values - Storing strings, numbers, settings
- Prototyping - Fast iteration and testing
- Small projects - When you don't need advanced features
- Learning DI - Easiest way to understand the concepts

### ❌ Not Ideal For

- Large applications - Can become messy with many services
- Complex dependencies - Hard to manage intricate dependency graphs
- Type safety - Less type-safe than other approaches
- Advanced scoping - Limited lifetime management
- Team development - Less explicit about dependencies

## 🔄 Migration Path

You can start with the dict interface and migrate to more advanced patterns:

    # Phase 1: Simple dict interface
    container = InjectQ()
    container[Database] = Database
    container[UserService] = UserService

    # Phase 2: Add modules for organization
    class DatabaseModule(Module):
        def configure(self, binder):
            binder.bind(Database, Database)

    container = InjectQ([DatabaseModule()])

    # Phase 3: Add type safety with protocols
    class IDatabase(Protocol):
        def connect(self) -> None: ...

    container.bind(IDatabase, PostgreSQLDatabase)

## 🏆 Best Practices

### 1. Use Descriptive Keys

    # ✅ Good - descriptive keys
    container["database_url"] = "postgresql://..."
    container["redis_host"] = "localhost"
    container["api_timeout"] = 30

    # ❌ Avoid - unclear keys
    container["url"] = "postgresql://..."
    container["host"] = "localhost"
    container["num"] = 30

### 2. Group Related Configuration

    # ✅ Good - grouped configuration
    container["database"] = {
        "host": "localhost",
        "port": 5432,
        "name": "myapp"
    }
    container["cache"] = {
        "host": "redis",
        "ttl": 3600
    }

    # ❌ Avoid - scattered configuration
    container["db_host"] = "localhost"
    container["db_port"] = 5432
    container["cache_host"] = "redis"

### 3. Use Factories for Dynamic Values

    # ✅ Good - factories for dynamic values
    container["request_id"] = lambda: str(uuid.uuid4())
    container["timestamp"] = lambda: datetime.now()

    # ❌ Avoid - static values that should be dynamic
    container["request_id"] = "static-id"  # Same for all requests

### 4. Document Your Services

    # ✅ Good - documented services
    container["database"] = PostgreSQLDatabase()  # Main application database
    container["cache"] = RedisCache()            # Redis cache for performance
    container["logger"] = StructuredLogger()     # JSON structured logging

## 🎯 Summary

The dict-like interface is:

- Simple - Easy to understand and use
- Flexible - Store any type of value or service
- Fast - Quick setup for small projects
- Intuitive - Familiar dictionary-like API

Key features: - Store simple values, objects, classes, or factories - Automatic dependency resolution for registered classes - Easy testing with dependency overrides - Seamless integration with other InjectQ features

When to use: - Learning dependency injection - Small to medium applications - Prototyping and experimentation - Simple configuration management

Ready to explore the @inject decorator?