ich bin der Schöpfer von Dependency Injector . Dies ist ein Abhängigkeitsinjektionsframework für Python.
Dies ist die endgültige Anleitung zum Erstellen von Anwendungen mit dem Abhängigkeitsinjektor. In früheren Tutorials wurde beschrieben, wie Sie eine Webanwendung mit Flask , eine REST-API mit Aiohttp und die Überwachung eines Daemons mit Asyncio mithilfe der Abhängigkeitsinjektion erstellen .
Heute möchte ich zeigen, wie Sie eine Konsolenanwendung (CLI) erstellen können.
Zusätzlich habe ich Antworten auf häufig gestellte Fragen vorbereitet und werde deren Nachtrag veröffentlichen.
Das Handbuch besteht aus folgenden Teilen:
- Was werden wir bauen?
- Umwelt vorbereiten
- Projektstruktur
- Abhängigkeiten installieren
- Vorrichtungen
- Container
- Arbeiten mit CSV
- Arbeiten mit SQLite
- Anbieterauswahl
- Tests
- Fazit
- PS: Fragen und Antworten
Das abgeschlossene Projekt finden Sie auf Github .
Um zu beginnen, mĂĽssen Sie haben:
- Python 3.5+
- Virtuelle Umgebung
Und es ist wünschenswert, ein allgemeines Verständnis des Prinzips der Abhängigkeitsinjektion zu haben.
Was werden wir bauen?
Wir werden eine CLI-Anwendung (Konsole) erstellen, die nach Filmen sucht. Nennen wir es Movie Lister.
Wie funktioniert Movie Lister?
- Wir haben eine Datenbank mit Filmen
- Zu jedem Film sind folgende Informationen bekannt:
- Name
- Baujahr
- Name des Direktors
- Die Datenbank ist in zwei Formaten verteilt:
- CSV-Datei
- SQLite-Datenbank
- Die Anwendung durchsucht die Datenbank anhand der folgenden Kriterien:
- Name des Direktors
- Baujahr
- Andere Datenbankformate können in Zukunft hinzugefügt werden
Movie Lister ist eine Beispielanwendung, die in Martin Fowlers Artikel über Abhängigkeitsinjektion und Inversion der Kontrolle verwendet wird.
So sieht das Klassendiagramm der Movie Lister-Anwendung aus:
Die Verantwortlichkeiten zwischen den Klassen sind wie folgt verteilt:
MovieLister- verantwortlich für die SucheMovieFinder- verantwortlich für das Extrahieren von Daten aus der DatenbankMovie- Entitätsklasse "Film"
Umwelt vorbereiten
Beginnen wir mit der Vorbereitung der Umgebung.
Zunächst müssen wir einen Projektordner und eine virtuelle Umgebung erstellen:
mkdir movie-lister-tutorial
cd movie-lister-tutorial
python3 -m venv venv
Jetzt aktivieren wir die virtuelle Umgebung:
. venv/bin/activate
Die Umgebung ist bereit. Kommen wir nun zur Struktur des Projekts.
Projektstruktur
In diesem Abschnitt organisieren wir die Struktur des Projekts.
Lassen Sie uns die folgende Struktur im aktuellen Ordner erstellen. Lassen Sie alle Dateien vorerst leer.
Anfangsstruktur:
./
├── movies/
│ ├── __init__.py
│ ├── __main__.py
│ └── containers.py
├── venv/
├── config.yml
└── requirements.txt
Abhängigkeiten installieren
Es ist Zeit, die Abhängigkeiten zu installieren. Wir werden Pakete wie dieses verwenden:
dependency-injector- Rahmen für die Abhängigkeitsinjektionpyyaml- Bibliothek zum Parsen von YAML-Dateien, die zum Lesen der Konfiguration verwendet wirdpytest- Testrahmenpytest-cov- Hilfsbibliothek zur Messung der Codeabdeckung durch Tests
FĂĽgen wir der Datei die folgenden Zeilen hinzu
requirements.txt:
dependency-injector
pyyaml
pytest
pytest-cov
Und im Terminal ausfĂĽhren:
pip install -r requirements.txt
Die Installation der Abhängigkeiten ist abgeschlossen. Weiter zu den Spielpaarungen.
Vorrichtungen
In diesem Abschnitt werden wir Fixtures hinzufĂĽgen. Testdaten werden als Vorrichtungen bezeichnet.
Wir werden ein Skript erstellen, das Testdatenbanken erstellt.
FĂĽgen Sie
data/dem Stammverzeichnis des Projekts ein Verzeichnis hinzu und fĂĽgen Sie eine Datei hinzu fixtures.py:
./
├── data/
│ └── fixtures.py
├── movies/
│ ├── __init__.py
│ ├── __main__.py
│ └── containers.py
├── venv/
├── config.yml
└── requirements.txt
Als nächstes bearbeiten
fixtures.py:
"""Fixtures module."""
import csv
import sqlite3
import pathlib
SAMPLE_DATA = [
('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'),
('Rogue One: A Star Wars Story', 2016, 'Gareth Edwards'),
('The Jungle Book', 2016, 'Jon Favreau'),
]
FILE = pathlib.Path(__file__)
DIR = FILE.parent
CSV_FILE = DIR / 'movies.csv'
SQLITE_FILE = DIR / 'movies.db'
def create_csv(movies_data, path):
with open(path, 'w') as opened_file:
writer = csv.writer(opened_file)
for row in movies_data:
writer.writerow(row)
def create_sqlite(movies_data, path):
with sqlite3.connect(path) as db:
db.execute(
'CREATE TABLE IF NOT EXISTS movies '
'(title text, year int, director text)'
)
db.execute('DELETE FROM movies')
db.executemany('INSERT INTO movies VALUES (?,?,?)', movies_data)
def main():
create_csv(SAMPLE_DATA, CSV_FILE)
create_sqlite(SAMPLE_DATA, SQLITE_FILE)
print('OK')
if __name__ == '__main__':
main()
Lassen Sie uns nun im Terminal ausfĂĽhren:
python data/fixtures.py
Das Skript sollte
OKbei Erfolg ausgegeben werden .
Wir ĂĽberprĂĽfen, ob die Dateien
movies.csvund movies.dbim Verzeichnis erschienen sind data/:
./
├── data/
│ ├── fixtures.py
│ ├── movies.csv
│ └── movies.db
├── movies/
│ ├── __init__.py
│ ├── __main__.py
│ └── containers.py
├── venv/
├── config.yml
└── requirements.txt
Fixtures werden erstellt. Lass uns weitermachen.
Container
In diesem Abschnitt fĂĽgen wir den Hauptteil unserer Anwendung hinzu - den Container.
Mit dem Container können Sie die Struktur der Anwendung deklarativ beschreiben. Es enthält alle Anwendungskomponenten und ihre Abhängigkeiten. Alle Abhängigkeiten werden explizit angegeben. Anbieter werden verwendet, um dem Container Anwendungskomponenten hinzuzufügen. Anbieter steuern die Lebensdauer von Komponenten. Beim Erstellen eines Anbieters wird keine Komponente erstellt. Wir teilen dem Anbieter mit, wie das Objekt erstellt werden soll, und er wird es so schnell wie nötig erstellen. Wenn die Abhängigkeit eines Anbieters ein anderer Anbieter ist, wird sie entlang der Abhängigkeitskette aufgerufen und so weiter.
Lassen Sie uns bearbeiten
containers.py:
"""Containers module."""
from dependency_injector import containers
class ApplicationContainer(containers.DeclarativeContainer):
...
Der Behälter ist noch leer. In den nächsten Abschnitten werden wir Anbieter hinzufügen.
FĂĽgen wir eine weitere Funktion hinzu
main(). Ihre Aufgabe ist es, die Anwendung auszufĂĽhren. Im Moment wird sie nur einen Container erstellen.
Lassen Sie uns bearbeiten
__main__.py:
"""Main module."""
from .containers import ApplicationContainer
def main():
container = ApplicationContainer()
if __name__ == '__main__':
main()
Der Container ist das erste Objekt in der Anwendung. Es wird verwendet, um alle anderen Objekte abzurufen.
Arbeiten mit CSV
Fügen wir nun alles hinzu, was wir für die Arbeit mit CSV-Dateien benötigen.
Wir brauchen:
- Die Essenz
Movie - Basisklasse
MovieFinder - Seine Umsetzung
CsvMovieFinder - Klasse
MovieLister
Nachdem wir jede Komponente hinzugefĂĽgt haben, fĂĽgen wir sie dem Container hinzu.
Erstellen Sie eine Datei
entities.pyin einem Paket movies:
./
├── data/
│ ├── fixtures.py
│ ├── movies.csv
│ └── movies.db
├── movies/
│ ├── __init__.py
│ ├── __main__.py
│ ├── containers.py
│ └── entities.py
├── venv/
├── config.yml
└── requirements.txt
und fĂĽgen Sie die folgenden Zeilen hinzu:
"""Movie entities module."""
class Movie:
def __init__(self, title: str, year: int, director: str):
self.title = str(title)
self.year = int(year)
self.director = str(director)
def __repr__(self):
return '{0}(title={1}, year={2}, director={3})'.format(
self.__class__.__name__,
repr(self.title),
repr(self.year),
repr(self.director),
)
Jetzt mĂĽssen wir
Moviedem Container eine Fabrik hinzufügen . Dafür benötigen wir ein Modul providersvon dependency_injector.
Lassen Sie uns bearbeiten
containers.py:
"""Containers module."""
from dependency_injector import containers, providers
from . import entities
class ApplicationContainer(containers.DeclarativeContainer):
movie = providers.Factory(entities.Movie)
Vergessen Sie nicht, die Auslassungspunkte ( ...) zu entfernen . Der Container hat bereits Anbieter und wird nicht mehr benötigt.
Fahren wir mit dem Erstellen fort
finders.
Erstellen Sie eine Datei
finders.pyin einem Paket movies:
./
├── data/
│ ├── fixtures.py
│ ├── movies.csv
│ └── movies.db
├── movies/
│ ├── __init__.py
│ ├── __main__.py
│ ├── containers.py
│ ├── entities.py
│ └── finders.py
├── venv/
├── config.yml
└── requirements.txt
und fĂĽgen Sie die folgenden Zeilen hinzu:
"""Movie finders module."""
import csv
from typing import Callable, List
from .entities import Movie
class MovieFinder:
def __init__(self, movie_factory: Callable[..., Movie]) -> None:
self._movie_factory = movie_factory
def find_all(self) -> List[Movie]:
raise NotImplementedError()
class CsvMovieFinder(MovieFinder):
def __init__(
self,
movie_factory: Callable[..., Movie],
path: str,
delimiter: str,
) -> None:
self._csv_file_path = path
self._delimiter = delimiter
super().__init__(movie_factory)
def find_all(self) -> List[Movie]:
with open(self._csv_file_path) as csv_file:
csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
return [self._movie_factory(*row) for row in csv_reader]
FĂĽgen wir
CsvMovieFindernun den Container hinzu.
Lassen Sie uns bearbeiten
containers.py:
"""Containers module."""
from dependency_injector import containers, providers
from . import finders, entities
class ApplicationContainer(containers.DeclarativeContainer):
config = providers.Configuration()
movie = providers.Factory(entities.Movie)
csv_finder = providers.Singleton(
finders.CsvMovieFinder,
movie_factory=movie.provider,
path=config.finder.csv.path,
delimiter=config.finder.csv.delimiter,
)
Sie
CsvMovieFindersind von der Fabrik abhängig Movie. CsvMovieFinderbenötigt eine Factory, da sie Objekte Moviebeim Lesen von Daten aus einer Datei erstellt. Um die Fabrik zu übergeben, verwenden wir das Attribut .provider. Dies wird als Providerdelegation bezeichnet. Wenn wir eine Fabrik geben movieals Abhängigkeit, wird es aufgerufen werden , wenn es csv_findererstellt wird CsvMovieFinderund ein Objekt wird als Injektion übergeben werden Movie. Die Verwendung des Attributs .providerals Injektion wird vom Anbieter selbst übergeben.
Es
csv_finderhängt auch von mehreren Konfigurationsoptionen ab. Wir haben einen Anbieter hinzugefügt onfiguration, um diese Abhängigkeiten zu übergeben.
Wir haben die Konfigurationsparameter verwendet, bevor wir ihre Werte eingestellt haben. Dies ist das Prinzip, nach dem der Anbieter arbeitetConfiguration.
Zuerst verwenden wir, dann setzen wir die Werte.
FĂĽgen wir nun die Konfigurationswerte hinzu.
Lassen Sie uns bearbeiten
config.yml:
finder:
csv:
path: "data/movies.csv"
delimiter: ","
Die Werte werden auf die Konfigurationsdatei gesetzt. Lassen Sie uns die Funktion aktualisieren
main(), um ihren Standort anzugeben.
Lassen Sie uns bearbeiten
__main__.py:
"""Main module."""
from .containers import ApplicationContainer
def main():
container = ApplicationContainer()
container.config.from_yaml('config.yml')
if __name__ == '__main__':
main()
Lass uns gehen zu
listers.
Erstellen Sie eine Datei
listers.pyin einem Paket movies:
./
├── data/
│ ├── fixtures.py
│ ├── movies.csv
│ └── movies.db
├── movies/
│ ├── __init__.py
│ ├── __main__.py
│ ├── containers.py
│ ├── entities.py
│ ├── finders.py
│ └── listers.py
├── venv/
├── config.yml
└── requirements.txt
und fĂĽgen Sie die folgenden Zeilen hinzu:
"""Movie listers module."""
from .finders import MovieFinder
class MovieLister:
def __init__(self, movie_finder: MovieFinder):
self._movie_finder = movie_finder
def movies_directed_by(self, director):
return [
movie for movie in self._movie_finder.find_all()
if movie.director == director
]
def movies_released_in(self, year):
return [
movie for movie in self._movie_finder.find_all()
if movie.year == year
]
Wir aktualisieren
containers.py:
"""Containers module."""
from dependency_injector import containers, providers
from . import finders, listers, entities
class ApplicationContainer(containers.DeclarativeContainer):
config = providers.Configuration()
movie = providers.Factory(entities.Movie)
csv_finder = providers.Singleton(
finders.CsvMovieFinder,
movie_factory=movie.provider,
path=config.finder.csv.path,
delimiter=config.finder.csv.delimiter,
)
lister = providers.Factory(
listers.MovieLister,
movie_finder=csv_finder,
)
Alle Komponenten werden erstellt und dem Container hinzugefĂĽgt.
SchlieĂźlich aktualisieren wir die Funktion
main().
Lassen Sie uns bearbeiten
__main__.py:
"""Main module."""
from .containers import ApplicationContainer
def main():
container = ApplicationContainer()
container.config.from_yaml('config.yml')
lister = container.lister()
print(
'Francis Lawrence movies:',
lister.movies_directed_by('Francis Lawrence'),
)
print(
'2016 movies:',
lister.movies_released_in(2016),
)
if __name__ == '__main__':
main()
Alles ist fertig. Starten wir nun die Anwendung.
Lassen Sie uns im Terminal ausfĂĽhren:
python -m movies
Du wirst sehen:
Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]
Unsere Anwendung arbeitet mit einer Datenbank von Filmen in
csv. Wir müssen auch Formatunterstützung hinzufügen sqlite. Wir werden uns im nächsten Abschnitt damit befassen.
Arbeiten mit SQLite
In diesem Abschnitt fĂĽgen wir einen weiteren Typ hinzu
MovieFinder- SqliteMovieFinder.
Lassen Sie uns bearbeiten
finders.py:
"""Movie finders module."""
import csv
import sqlite3
from typing import Callable, List
from .entities import Movie
class MovieFinder:
def __init__(self, movie_factory: Callable[..., Movie]) -> None:
self._movie_factory = movie_factory
def find_all(self) -> List[Movie]:
raise NotImplementedError()
class CsvMovieFinder(MovieFinder):
def __init__(
self,
movie_factory: Callable[..., Movie],
path: str,
delimiter: str,
) -> None:
self._csv_file_path = path
self._delimiter = delimiter
super().__init__(movie_factory)
def find_all(self) -> List[Movie]:
with open(self._csv_file_path) as csv_file:
csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
return [self._movie_factory(*row) for row in csv_reader]
class SqliteMovieFinder(MovieFinder):
def __init__(
self,
movie_factory: Callable[..., Movie],
path: str,
) -> None:
self._database = sqlite3.connect(path)
super().__init__(movie_factory)
def find_all(self) -> List[Movie]:
with self._database as db:
rows = db.execute('SELECT title, year, director FROM movies')
return [self._movie_factory(*row) for row in rows]
FĂĽgen Sie den Anbieter
sqlite_finderzum Container hinzu und geben Sie ihn als Abhängigkeit für den Anbieter an lister.
Lassen Sie uns bearbeiten
containers.py:
"""Containers module."""
from dependency_injector import containers, providers
from . import finders, listers, entities
class ApplicationContainer(containers.DeclarativeContainer):
config = providers.Configuration()
movie = providers.Factory(entities.Movie)
csv_finder = providers.Singleton(
finders.CsvMovieFinder,
movie_factory=movie.provider,
path=config.finder.csv.path,
delimiter=config.finder.csv.delimiter,
)
sqlite_finder = providers.Singleton(
finders.SqliteMovieFinder,
movie_factory=movie.provider,
path=config.finder.sqlite.path,
)
lister = providers.Factory(
listers.MovieLister,
movie_finder=sqlite_finder,
)
Der Anbieter
sqlite_finderist abhängig von Konfigurationsoptionen, die wir noch nicht definiert haben. Lassen Sie uns die Konfigurationsdatei aktualisieren:
Bearbeiten
config.yml:
finder:
csv:
path: "data/movies.csv"
delimiter: ","
sqlite:
path: "data/movies.db"
Erledigt. Lass uns das PrĂĽfen.
Wir fĂĽhren im Terminal aus:
python -m movies
Du wirst sehen:
Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]
Unsere Anwendung unterstĂĽtzt beide Datenbankformate:
csvund sqlite. Jedes Mal, wenn wir das Format ändern müssen, müssen wir den Code im Container ändern. Wir werden dies im nächsten Abschnitt verbessern.
Anbieterauswahl
In diesem Abschnitt werden wir unsere Anwendung flexibler gestalten.
Sie müssen keine Codeänderungen mehr vornehmen, um zwischen
csvund sqliteFormaten zu wechseln . Wir werden einen Schalter basierend auf einer Umgebungsvariablen implementieren MOVIE_FINDER_TYPE:
- Wenn eine
MOVIE_FINDER_TYPE=csvAnwendung die verwendetcsv. - Wenn eine
MOVIE_FINDER_TYPE=sqliteAnwendung die verwendetsqlite.
Der Anbieter hilft uns dabei
Selector. Es wählt einen Anbieter basierend auf der Konfigurationsoption ( Dokumentation ).
Lassen Sie uns bearbeiten
containers.py:
"""Containers module."""
from dependency_injector import containers, providers
from . import finders, listers, entities
class ApplicationContainer(containers.DeclarativeContainer):
config = providers.Configuration()
movie = providers.Factory(entities.Movie)
csv_finder = providers.Singleton(
finders.CsvMovieFinder,
movie_factory=movie.provider,
path=config.finder.csv.path,
delimiter=config.finder.csv.delimiter,
)
sqlite_finder = providers.Singleton(
finders.SqliteMovieFinder,
movie_factory=movie.provider,
path=config.finder.sqlite.path,
)
finder = providers.Selector(
config.finder.type,
csv=csv_finder,
sqlite=sqlite_finder,
)
lister = providers.Factory(
listers.MovieLister,
movie_finder=finder,
)
Wir haben einen Anbieter erstellt
finderund als Abhängigkeit für den Anbieter angegeben lister. Der Anbieter finderwählt zwischen Anbietern csv_finderund sqlite_finderzur Laufzeit. Die Auswahl hängt vom Wert des Schalters ab.
Der Switch ist die Konfigurationsoption
config.finder.type. Wenn sein Wert csvvom Anbieter aus dem SchlĂĽssel verwendet wird csv. Ebenso fĂĽr sqlite.
Jetzt mĂĽssen wir den Wert
config.finder.typeaus der Umgebungsvariablen lesen MOVIE_FINDER_TYPE.
Lassen Sie uns bearbeiten
__main__.py:
"""Main module."""
from .containers import ApplicationContainer
def main():
container = ApplicationContainer()
container.config.from_yaml('config.yml')
container.config.finder.type.from_env('MOVIE_FINDER_TYPE')
lister = container.lister()
print(
'Francis Lawrence movies:',
lister.movies_directed_by('Francis Lawrence'),
)
print(
'2016 movies:',
lister.movies_released_in(2016),
)
if __name__ == '__main__':
main()
Erledigt.
FĂĽhren Sie die folgenden Befehle im Terminal aus:
MOVIE_FINDER_TYPE=csv python -m movies
MOVIE_FINDER_TYPE=sqlite python -m movies
Die Ausgabe fĂĽr jeden Befehl sieht folgendermaĂźen aus:
Francis Lawrence movies: [Movie(title='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
2016 movies: [Movie(title='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(title='The Jungle Book', year=2016, director='Jon Favreau')]
In diesem Abschnitt haben wir den Anbieter kennengelernt
Selector. Mit diesem Anbieter können Sie Ihre Anwendung flexibler gestalten. Der Switch-Wert kann von jeder Quelle aus festgelegt werden: Konfigurationsdatei, Wörterbuch, anderer Anbieter.
Tipp: Durch
Überschreiben eines Konfigurationswerts von einem anderen Anbieter können Sie die Konfigurationsüberladung in Ihrer Anwendung ohne einen Neustart implementieren.
Dazu mĂĽssen Sie die Providerdelegation und die.override().
Im nächsten Abschnitt werden wir einige Tests hinzufügen.
Tests
Lassen Sie uns zum Schluss einige Tests hinzufĂĽgen.
Erstellen Sie eine Datei
tests.pyin einem Paket movies:
./
├── data/
│ ├── fixtures.py
│ ├── movies.csv
│ └── movies.db
├── movies/
│ ├── __init__.py
│ ├── __main__.py
│ ├── containers.py
│ ├── entities.py
│ ├── finders.py
│ ├── listers.py
│ └── tests.py
├── venv/
├── config.yml
└── requirements.txt
und fĂĽgen Sie die folgenden Zeilen hinzu:
"""Tests module."""
from unittest import mock
import pytest
from .containers import ApplicationContainer
@pytest.fixture
def container():
container = ApplicationContainer()
container.config.from_dict({
'finder': {
'type': 'csv',
'csv': {
'path': '/fake-movies.csv',
'delimiter': ',',
},
'sqlite': {
'path': '/fake-movies.db',
},
},
})
return container
def test_movies_directed_by(container):
finder_mock = mock.Mock()
finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'),
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
]
with container.finder.override(finder_mock):
lister = container.lister()
movies = lister.movies_directed_by('Jon Favreau')
assert len(movies) == 1
assert movies[0].title == 'The Jungle Book'
def test_movies_released_in(container):
finder_mock = mock.Mock()
finder_mock.find_all.return_value = [
container.movie('The 33', 2015, 'Patricia Riggen'),
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
]
with container.finder.override(finder_mock):
lister = container.lister()
movies = lister.movies_released_in(2015)
assert len(movies) == 1
assert movies[0].title == 'The 33'
Beginnen wir nun mit dem Testen und ĂĽberprĂĽfen Sie die Abdeckung:
pytest movies/tests.py --cov=movies
Du wirst sehen:
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
plugins: cov-2.10.0
collected 2 items
movies/tests.py .. [100%]
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
Name Stmts Miss Cover
------------------------------------------
movies/__init__.py 0 0 100%
movies/__main__.py 10 10 0%
movies/containers.py 9 0 100%
movies/entities.py 7 1 86%
movies/finders.py 26 13 50%
movies/listers.py 8 0 100%
movies/tests.py 24 0 100%
------------------------------------------
TOTAL 84 24 71%
Wir haben die.override()Provider- Methode verwendetfinder. Der Anbieter wird von mock ĂĽberschrieben. Wenn Sie sich an den Anbieterfinderwenden , wird das ĂĽberschreibende Modell jetzt zurĂĽckgegeben.
Die Arbeit ist erledigt. Lassen Sie uns nun zusammenfassen.
Fazit
Wir haben eine CLI-Anwendung nach dem Prinzip der Abhängigkeitsinjektion erstellt. Wir haben Dependency Injector als Abhängigkeitsinjektions-Framework verwendet.
Der Vorteil, den Sie mit Dependency Injector erhalten, ist der Container.
Der Container beginnt sich auszuzahlen, wenn Sie die Struktur Ihrer Anwendung verstehen oder ändern müssen. Mit einem Container ist dies einfach, da alle Anwendungskomponenten und ihre Abhängigkeiten explizit an einer Stelle definiert sind:
"""Containers module."""
from dependency_injector import containers, providers
from . import finders, listers, entities
class ApplicationContainer(containers.DeclarativeContainer):
config = providers.Configuration()
movie = providers.Factory(entities.Movie)
csv_finder = providers.Singleton(
finders.CsvMovieFinder,
movie_factory=movie.provider,
path=config.finder.csv.path,
delimiter=config.finder.csv.delimiter,
)
sqlite_finder = providers.Singleton(
finders.SqliteMovieFinder,
movie_factory=movie.provider,
path=config.finder.sqlite.path,
)
finder = providers.Selector(
config.finder.type,
csv=csv_finder,
sqlite=sqlite_finder,
)
lister = providers.Factory(
listers.MovieLister,
movie_finder=finder,
)
Ein Container als Karte Ihrer Anwendung. Sie wissen immer, was von was abhängt.
PS: Fragen und Antworten
In den Kommentaren zum vorherigen Tutorial wurden coole Fragen gestellt: "Warum ist das notwendig?", "Warum brauchen wir ein Framework?", "Wie hilft das Framework bei der Implementierung?"
Ich habe Antworten vorbereitet:
Was ist Abhängigkeitsinjektion?
- Es ist das Prinzip, das die Kopplung verringert und die Kohäsion erhöht
Warum sollte ich die Abhängigkeitsinjektion verwenden?
- Ihr Code wird flexibler, verständlicher und besser testbar
- Sie haben weniger Probleme, wenn Sie verstehen müssen, wie es funktioniert, oder wenn Sie es ändern müssen
Wie beginne ich mit der Anwendung der Abhängigkeitsinjektion?
- Sie beginnen mit dem Schreiben von Code nach dem Prinzip der Abhängigkeitsinjektion
- Sie registrieren alle Komponenten und ihre Abhängigkeiten im Container
- Wenn Sie eine Komponente benötigen, erhalten Sie diese aus dem Container
Warum brauche ich dafĂĽr einen Rahmen?
- Sie benötigen ein Framework, um kein eigenes zu erstellen. Der Objekterstellungscode wird dupliziert und ist schwer zu ändern. Um dies zu vermeiden, benötigen Sie einen Container.
- Das Framework bietet Ihnen einen Container und Anbieter
- Anbieter steuern die Lebensdauer von Objekten. Sie benötigen Fabriken, Singletons und Konfigurationsobjekte
- Der Container dient als Sammlung von Anbietern
Welchen Preis zahle ich?
- Sie müssen Abhängigkeiten im Container explizit angeben
- Das ist zusätzliche Arbeit
- Es wird sich auszahlen, wenn das Projekt wächst
- oder 2 Wochen nach Abschluss (wenn Sie vergessen, welche Entscheidungen Sie getroffen haben und wie das Projekt aufgebaut ist)
Abhängigkeitsinjektor-Konzept
DarĂĽber hinaus werde ich das Konzept des Dependency Injector als Framework beschreiben.
Der Abhängigkeitsinjektor basiert auf zwei Prinzipien:
- Explizit ist besser als implizit (PEP20).
- Mach keine Magie mit deinem Code.
Wie unterscheidet sich Dependency Injector von anderen Frameworks?
- Keine automatische Verknüpfung. Das Framework verknüpft Abhängigkeiten nicht automatisch. Introspektion, Verknüpfung nach Argumentnamen und / oder Typen wird nicht verwendet. Weil "explizit ist besser als implizit (PEP20)".
- Verschmutzt Ihren Anwendungscode nicht. Ihre Anwendung kennt den Abhängigkeitsinjektor nicht und ist von ihm unabhängig. Keine
@injectDekorateure, Anmerkungen, Patches oder andere Zaubertricks.
Dependency Injector bietet einen einfachen Vertrag:
- Sie zeigen dem Framework, wie Objekte gesammelt werden
- Das Framework sammelt sie
Die Stärke des Abhängigkeitsinjektors liegt in seiner Einfachheit und Unkompliziertheit. Es ist ein einfaches Werkzeug, um ein leistungsfähiges Prinzip zu implementieren.
Was weiter?
Wenn Sie interessiert sind, aber zögern, lautet meine Empfehlung:
Versuchen Sie diesen Ansatz 2 Monate lang. Er ist nicht intuitiv. Es braucht Zeit, um sich daran zu gewöhnen und zu fühlen. Die Vorteile werden spürbar, wenn das Projekt auf mehr als 30 Komponenten in einem Container anwächst. Wenn es Ihnen nicht gefällt, verlieren Sie nicht viel. Wenn es Ihnen gefällt, erhalten Sie einen erheblichen Vorteil.
- Erfahren Sie mehr ĂĽber Dependency Injector auf GitHub
- Lesen Sie die Dokumentation unter Lesen Sie die Dokumente
Ich wĂĽrde mich ĂĽber Feedback und Beantwortung von Fragen in den Kommentaren freuen.