# Dependency Injection in Python mit *Dependency Injector* - (29.11.2024)
Bei einer grösseren Python Applikation kommt früher oder später das Thema Testing auf. Bei einer Applikation ohne Infrastruktur stellt sich keine grössere Hürde in den Weg. Sobald jedoch auf externe Schnittstellen zugegriffen werden muss, ist das Schreiben von Tests nicht mehr so einfach.
Dieses Problem existiert in jeder Sprache und es gibt auch eine ziemlich klare Lösung dafür: Inversion of Control und Dependency Injection. In Python ist die Umsetzung davon jedoch nicht sehr intuitiv.
## Der *pythonische* Weg
Es gibt keinen klaren bevorzugten Weg, um Dependency Injection in Python zu erreichen. Der einfachste Weg, welcher als einziger auch in reinem Python umgesetzt werden kann, ist über Vererbung.[^1] Da man so zwar eine gewisse Inversion of Control erreichen kann, sollte es bei Möglichkeit auch genutzt werden. Für eine richtige Dependency Injection reicht es jedoch nicht.
## Dependency Injector
Diese Lücke an guter Dependency Injection wurde schnell durch die Community gefüllt und es wurden dutzende Bibliotheken mit dieser Funktionalität erstellt. Eine dieser Bibliotheken ist *Dependency Injector*.
Das Projekt wurde von *Roman Mogylatov* erstellt und hat auf GitHub (zum Zeitpunkt der Verfassung) 4'013 Sterne. Lizensiert ist es mit der 3-Klausel-BSD[^2] Lizenz.
### Features
Mit dieser Bibliothek gewinnen wir viele neue Features. Die wichtigsten für die normale Anwendung sind:
#### Providers
Dependency Injector gibt uns die Möglichkeit sogenannte Providers zu nutzen. Providers helfen uns dabei, unsere Objekte für die Injektion zusammen zu bauen. Die Unterstützten Providers sind:[^3]
- Factory
- Singleton
- Callable
- Coroutine
- Object
- List
- Dict
- Configuration
- Resource
- Dependency
- Selector
#### Containers
Dazu erhalten wir auch sogenannte Containers. In einem Container können wir unsere Provider sammeln und auch unterteilen. Im Normalfall reicht ein Container, es gibt jedoch auch Szenarien in denen mehrere Container genutzt werden können. So können beispielsweise Services, Helpers und Utils in eigene Container gespeichert werden.[^4]
### Anwendung
Zuerst muss die Bibliothek installiert werden:
```python
pip install dependency-injector
```
Anschliessend können wir schon loslegen:
#### Ordnerstruktur
Angenommen wir wollen eine Autovermietung implementieren. Unsere Ordnerstruktur könnte folgendermassen aussehen:
```
./
├── carrental/
│ ├── __init__.py
│ ├── __main__.py
│ ├── containers.py
│ ├── cars.py
| └── carrental.py
├── .venv/
├── config.yml
└── requirements.txt
```
#### Car Rental
zuerst erstellen wir unsere Logik für die Autovermietung:
```python
""" cars.py """
class Car:
def __init__(self, name: str, price: int):
self.name = str(name)
self.price = int(price)
```
```python
""" carrental.py """
from typing import List, Callable
from .cars improt Car
class CarRental:
def __init__(self, car_factoy: Callable[..., Car]) -> None:
self._car_factory = car_factory
def find_car(self, name: str) -> Car:
for car in cars:
if name == car.name:
return car
return None
```
#### Container
In unserer `containers.py` Datei definieren wir unseren Container, also die Abhängigkeiten welche injiziert werden sollten.
```python
""" containers.py """
from dependency_injector improt containers, providers
from .cars import Car
from .carrental import CarRental
class Container(containers.DeclarativeContainer):
car = providers.Factory(Car)
carrental = providers.Singleton(
CarRental,
car_factory = car.provider,
)
```
Aktuell hat der Container noch keine Services, diese kommen jedoch gleich.
In unserer Main Datei, also die auszuführende Datei erstellen wir unseren Container erstellen:
```python
""" __main__.py """
from .containers import Container
def main() -> None:
...
if __name__ == "__main__":
container = Container()
main()
```
## Fazit
Dependency Injector ist eine sehr umfangreiche Bibliothek welche uns eine komplexere Dependency Injection erlaubt. Der initiale Aufwand für die Einrichtung kann je nach Projekt nicht lohnenswert sein. Bei grösseren Projekten die auch umfangreich Unit Tests haben sollten, bietet es eine sehr solide Lösung.
[^1]: vgl: What is a Pythonic way for Dependency Injection?, in: Stack Overflow, 29.11.2024, https://stackoverflow.com/questions/31678827/what-is-a-pythonic-way-for-dependency-injection
[^2]: vgl: python-dependency-injector, in: GitHub, 29.11.2024, https://github.com/ets-labs/python-dependency-injector
[^3]: vgl: Providers, in: Dependency Injector, 29.11.2024, https://python-dependency-injector.ets-labs.org/providers/index.html
[^4]: vgl: Containers, in: Dependency Injector, 29.11.2024, https://python-dependency-injector.ets-labs.org/containers/index.html