diff --git a/examples/miniapps/movie-lister/README.rst b/examples/miniapps/movie-lister/README.rst index 65576c6c..0f776e48 100644 --- a/examples/miniapps/movie-lister/README.rst +++ b/examples/miniapps/movie-lister/README.rst @@ -2,38 +2,48 @@ Movie lister - a naive example of dependency injection in Python ================================================================ This is a Python implementation of the dependency injection example from Martin Fowler's -article about dependency injection and inversion of control: +article: http://www.martinfowler.com/articles/injection.html -Create virtual environment: +Run +--- + +Create a virtual environment: .. code-block:: bash virtualenv venv . venv/bin/activate -Install requirements: +Install the requirements: .. code-block:: bash pip install -r requirements.txt +To create the fixtures do: + +.. code-block:: bash + + python data/fixtures.py + To run the application do: .. code-block:: bash - MOVIE_STORAGE_TYPE=csv python -m movies - MOVIE_STORAGE_TYPE=sqlite python -m movies + MOVIE_FINDER_TYPE=csv python -m movies + MOVIE_FINDER_TYPE=sqlite python -m movies The output should be something like: .. code-block:: bash - [Movie(name='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')] - [Movie(name='The 33', year=2015, director='Patricia Riggen')] - [Movie(name='Star Wars: Episode VII - The Force Awakens', year=2015, director='JJ Abrams')] - [Movie(name='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence'), Movie(name='The 33', year=2015, director='Patricia Riggen'), Movie(name='Star Wars: Episode VII - The Force Awakens', year=2015, director='JJ Abrams')] + Francis Lawrence movies: [Movie(name='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')] + 2016 movies: [Movie(name='Rogue One: A Star Wars Story', year=2016, director='Gareth Edwards'), Movie(name='The Jungle Book', year=2016, director='Jon Favreau')] + +Test +---- To run the tests do: @@ -55,13 +65,11 @@ The output should be something like: Name Stmts Miss Cover ------------------------------------------ movies/__init__.py 0 0 100% - movies/__main__.py 15 15 0% + movies/__main__.py 10 10 0% movies/containers.py 9 0 100% - movies/finders.py 9 0 100% - movies/fixtures.py 1 0 100% + movies/entities.py 7 1 86% + movies/finders.py 26 13 50% movies/listers.py 8 0 100% - movies/models.py 7 1 86% - movies/storages.py 32 17 47% movies/tests.py 24 0 100% ------------------------------------------ - TOTAL 105 33 69% + TOTAL 84 24 71% diff --git a/examples/miniapps/movie-lister/config.yml b/examples/miniapps/movie-lister/config.yml index 530061ce..35421f1b 100644 --- a/examples/miniapps/movie-lister/config.yml +++ b/examples/miniapps/movie-lister/config.yml @@ -1,4 +1,4 @@ -storage: +finder: csv: path: "data/movies.csv" diff --git a/examples/miniapps/movie-lister/data/.gitignore b/examples/miniapps/movie-lister/data/.gitignore index bc208b8f..ba68614f 100644 --- a/examples/miniapps/movie-lister/data/.gitignore +++ b/examples/miniapps/movie-lister/data/.gitignore @@ -1,5 +1,6 @@ -# Ignore everything in this directory +# Everything * # Except this file: !.gitignore +!fixtures.py diff --git a/examples/miniapps/movie-lister/data/fixtures.py b/examples/miniapps/movie-lister/data/fixtures.py new file mode 100644 index 00000000..ee9241d7 --- /dev/null +++ b/examples/miniapps/movie-lister/data/fixtures.py @@ -0,0 +1,43 @@ +"""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 ' + '(name 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) + + +if __name__ == '__main__': + main() diff --git a/examples/miniapps/movie-lister/movies/__main__.py b/examples/miniapps/movie-lister/movies/__main__.py index fbae50ff..b51c4010 100644 --- a/examples/miniapps/movie-lister/movies/__main__.py +++ b/examples/miniapps/movie-lister/movies/__main__.py @@ -5,18 +5,20 @@ from .containers import ApplicationContainer def main(): container = ApplicationContainer() - container.config.from_yaml('config.yml') - container.config.storage.type.from_env('MOVIE_STORAGE_TYPE') - storage = container.storage() - fixtures = container.fixtures() - storage.load_all(fixtures) + container.config.from_yaml('config.yml') + container.config.finder.type.from_env('MOVIE_FINDER_TYPE') lister = container.lister() - print(lister.movies_directed_by('Francis Lawrence')) - print(lister.movies_directed_by('Patricia Riggen')) - print(lister.movies_directed_by('JJ Abrams')) - print(lister.movies_released_in(2015)) + + print( + 'Francis Lawrence movies:', + lister.movies_directed_by('Francis Lawrence'), + ) + print( + '2016 movies:', + lister.movies_released_in(2016), + ) if __name__ == '__main__': diff --git a/examples/miniapps/movie-lister/movies/containers.py b/examples/miniapps/movie-lister/movies/containers.py index a1fbeaec..0ffd7f16 100644 --- a/examples/miniapps/movie-lister/movies/containers.py +++ b/examples/miniapps/movie-lister/movies/containers.py @@ -2,33 +2,32 @@ from dependency_injector import containers, providers -from . import finders, listers, storages, models, fixtures +from . import finders, listers, entities class ApplicationContainer(containers.DeclarativeContainer): config = providers.Configuration() - fixtures = providers.Object(fixtures.MOVIES_SAMPLE_DATA) + movie = providers.Factory(entities.Movie) - storage = providers.Selector( - config.storage.type, - csv=providers.Singleton( - storages.CsvMovieStorage, - options=config.storage[config.storage.type], - ), - sqlite=providers.Singleton( - storages.SqliteMovieStorage, - options=config.storage[config.storage.type], - ), + csv_finder = providers.Singleton( + finders.CsvMovieFinder, + movie_factory=movie.provider, + path=config.finder.csv.path, + delimiter=config.finder.csv.delimiter, ) - movie = providers.Factory(models.Movie) - - finder = providers.Factory( - finders.MovieFinder, + sqlite_finder = providers.Singleton( + finders.SqliteMovieFinder, movie_factory=movie.provider, - movie_storage=storage, + path=config.finder.sqlite.path, + ) + + finder = providers.Selector( + config.finder.type, + csv=csv_finder, + sqlite=sqlite_finder, ) lister = providers.Factory( diff --git a/examples/miniapps/movie-lister/movies/models.py b/examples/miniapps/movie-lister/movies/entities.py similarity index 100% rename from examples/miniapps/movie-lister/movies/models.py rename to examples/miniapps/movie-lister/movies/entities.py diff --git a/examples/miniapps/movie-lister/movies/finders.py b/examples/miniapps/movie-lister/movies/finders.py index abb11074..0df196c3 100644 --- a/examples/miniapps/movie-lister/movies/finders.py +++ b/examples/miniapps/movie-lister/movies/finders.py @@ -1,23 +1,50 @@ """Movie finders module.""" +import csv +import sqlite3 from typing import Callable, List -from .models import Movie -from .storages import MovieStorage +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], - movie_storage: MovieStorage, + path: str, + delimiter: str, ) -> None: - self._movie_factory = movie_factory - self._movie_storage = movie_storage + self._csv_file_path = path + self._delimiter = delimiter + super().__init__(movie_factory) def find_all(self) -> List[Movie]: - return [ - self._movie_factory(*row) - for row in self._movie_storage.get_all() - ] + 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 name, year, director FROM movies') + return [self._movie_factory(*row) for row in rows] diff --git a/examples/miniapps/movie-lister/movies/fixtures.py b/examples/miniapps/movie-lister/movies/fixtures.py deleted file mode 100644 index 8f7843d6..00000000 --- a/examples/miniapps/movie-lister/movies/fixtures.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Fixtures module.""" - - -MOVIES_SAMPLE_DATA = [ - ('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'), - ('The 33', 2015, 'Patricia Riggen'), - ('Star Wars: Episode VII - The Force Awakens', 2015, 'JJ Abrams'), -] diff --git a/examples/miniapps/movie-lister/movies/storages.py b/examples/miniapps/movie-lister/movies/storages.py deleted file mode 100644 index f37d35e3..00000000 --- a/examples/miniapps/movie-lister/movies/storages.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Movie storages module.""" - -import csv -import sqlite3 -from typing import List, Tuple, Any - -Row = Tuple[Any] - - -class MovieStorage: - - def load_all(self, movie_data: List[Row]): - raise NotImplementedError() - - def get_all(self) -> List[Row]: - raise NotImplementedError() - - -class CsvMovieStorage(MovieStorage): - - def __init__(self, options) -> None: - self._csv_file_path = options.pop('path') - self._delimiter = options.pop('delimiter') - - def load_all(self, movie_data: List[Row]) -> None: - with open(self._csv_file_path, 'w') as csv_file: - csv.writer(csv_file, delimiter=self._delimiter).writerows(movie_data) - - def get_all(self) -> List[Row]: - with open(self._csv_file_path) as csv_file: - csv_reader = csv.reader(csv_file, delimiter=self._delimiter) - return [row for row in csv_reader] - - -class SqliteMovieStorage(MovieStorage): - - def __init__(self, options) -> None: - self._database = sqlite3.connect(database=options.pop('path')) - - def load_all(self, movie_data: List[Row]) -> None: - with self._database as db: - db.execute( - 'CREATE TABLE IF NOT EXISTS movies ' - '(name text, year int, director text)', - ) - db.execute('DELETE FROM movies') - db.executemany('INSERT INTO movies VALUES (?,?,?)', movie_data) - - def get_all(self) -> List[Row]: - with self._database as db: - rows = db.execute( - 'SELECT name, year, director ' - 'FROM movies', - ) - return [row for row in rows] diff --git a/examples/miniapps/movie-lister/movies/tests.py b/examples/miniapps/movie-lister/movies/tests.py index 2a2989a3..a992bf92 100644 --- a/examples/miniapps/movie-lister/movies/tests.py +++ b/examples/miniapps/movie-lister/movies/tests.py @@ -11,7 +11,7 @@ from .containers import ApplicationContainer def container(): container = ApplicationContainer() container.config.from_dict({ - 'storage': { + 'finder': { 'type': 'csv', 'csv': { 'path': '/fake-movies.csv', @@ -26,13 +26,13 @@ def container(): def test_movies_directed_by(container): - storage_mock = mock.Mock() - storage_mock.get_all.return_value = [ - ('The 33', 2015, 'Patricia Riggen'), - ('The Jungle Book', 2016, 'Jon Favreau'), + 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.storage.override(storage_mock): + with container.finder.override(finder_mock): lister = container.lister() movies = lister.movies_directed_by('Jon Favreau') @@ -41,13 +41,13 @@ def test_movies_directed_by(container): def test_movies_released_in(container): - storage_mock = mock.Mock() - storage_mock.get_all.return_value = [ - ('The 33', 2015, 'Patricia Riggen'), - ('The Jungle Book', 2016, 'Jon Favreau'), + 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.storage.override(storage_mock): + with container.finder.override(finder_mock): lister = container.lister() movies = lister.movies_released_in(2015)