# 32. Dependency Injection

Here are 10 Python code snippets demonstrating **dependency injection** techniques that help with managing dependencies in Python applications to improve flexibility and testability:

***

**1. Basic Constructor Injection**

```python
class Database:
    def connect(self):
        return "Connected to the database."

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

    def get_data(self):
        return self.db.connect()

# Creating instances
db = Database()
service = Service(db)
print(service.get_data())
```

***

**2. Dependency Injection via Setter Method**

```python
class Database:
    def connect(self):
        return "Connected to the database."

class Service:
    def set_db(self, db: Database):
        self.db = db

    def get_data(self):
        return self.db.connect()

# Creating instances
db = Database()
service = Service()
service.set_db(db)
print(service.get_data())
```

***

**3. Dependency Injection with Function Injection**

```python
def service_function(db: Database):
    return db.connect()

class Database:
    def connect(self):
        return "Connected to the database."

# Create the dependency instance and inject it
db = Database()
print(service_function(db))
```

***

**4. Using Dependency Injection with a Context Manager**

```python
from contextlib import contextmanager

class Database:
    def connect(self):
        return "Connected to the database."

@contextmanager
def service_context():
    db = Database()
    yield db
    # Cleanup if needed

with service_context() as db:
    print(db.connect())
```

***

**5. Dependency Injection via a Factory Function**

```python
class Database:
    def connect(self):
        return "Connected to the database."

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

    def get_data(self):
        return self.db.connect()

def create_service():
    db = Database()
    return Service(db)

# Creating and using the service
service = create_service()
print(service.get_data())
```

***

**6. Using Dependency Injection with a Simple DI Container**

```python
class Database:
    def connect(self):
        return "Connected to the database."

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

    def get_data(self):
        return self.db.connect()

class Container:
    def __init__(self):
        self._services = {}

    def register(self, name, instance):
        self._services[name] = instance

    def get(self, name):
        return self._services[name]

# Registering and using services
container = Container()
container.register("db", Database())
container.register("service", Service(container.get("db")))

service = container.get("service")
print(service.get_data())
```

***

**7. Using Dependency Injection with Class Inheritance**

```python
class Database:
    def connect(self):
        return "Connected to the database."

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

    def get_data(self):
        return self.db.connect()

class MyService(Service):
    def __init__(self, db: Database):
        super().__init__(db)

# Creating instances
db = Database()
service = MyService(db)
print(service.get_data())
```

***

**8. Using Dependency Injection with Abstract Base Classes**

```python
from abc import ABC, abstractmethod

class Database(ABC):
    @abstractmethod
    def connect(self):
        pass

class MySQLDatabase(Database):
    def connect(self):
        return "Connected to MySQL database."

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

    def get_data(self):
        return self.db.connect()

# Creating instances
db = MySQLDatabase()
service = Service(db)
print(service.get_data())
```

***

**9. Dependency Injection for Unit Testing**

```python
class Database:
    def connect(self):
        return "Connected to the database."

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

    def get_data(self):
        return self.db.connect()

# Mock Database class for testing
class MockDatabase:
    def connect(self):
        return "Mock database connection."

# Unit Test
def test_service():
    mock_db = MockDatabase()
    service = Service(mock_db)
    assert service.get_data() == "Mock database connection."

test_service()
```

***

**10. Using Dependency Injection with `injector` Library**

```python
from injector import inject, Injector, Module, provider

class Database:
    def connect(self):
        return "Connected to the database."

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

    def get_data(self):
        return self.db.connect()

class MyModule(Module):
    @provider
    def provide_database(self) -> Database:
        return Database()

# Creating injector and running the application
injector = Injector([MyModule()])
service = injector.get(Service)
print(service.get_data())
```

*Install `injector` with `pip install injector`.*

***

These examples show various ways to implement dependency injection in Python, including constructor injection, setter injection, factory methods, DI containers, and using popular libraries like `injector`. Dependency injection helps to decouple components and makes code more testable and flexible.
