mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-05-24 23:39:11 +03:00
Make second round of the refactoring
This commit is contained in:
parent
d5fd46b159
commit
59ca0f8ace
|
@ -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
|
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
|
http://www.martinfowler.com/articles/injection.html
|
||||||
|
|
||||||
Create virtual environment:
|
Run
|
||||||
|
---
|
||||||
|
|
||||||
|
Create a virtual environment:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
virtualenv venv
|
virtualenv venv
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
|
||||||
Install requirements:
|
Install the requirements:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
To create the fixtures do:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python data/fixtures.py
|
||||||
|
|
||||||
To run the application do:
|
To run the application do:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
MOVIE_STORAGE_TYPE=csv python -m movies
|
MOVIE_FINDER_TYPE=csv python -m movies
|
||||||
MOVIE_STORAGE_TYPE=sqlite python -m movies
|
MOVIE_FINDER_TYPE=sqlite python -m movies
|
||||||
|
|
||||||
The output should be something like:
|
The output should be something like:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
[Movie(name='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
|
Francis Lawrence movies: [Movie(name='The Hunger Games: Mockingjay - Part 2', year=2015, director='Francis Lawrence')]
|
||||||
[Movie(name='The 33', year=2015, director='Patricia Riggen')]
|
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')]
|
||||||
[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')]
|
Test
|
||||||
|
----
|
||||||
|
|
||||||
To run the tests do:
|
To run the tests do:
|
||||||
|
|
||||||
|
@ -55,13 +65,11 @@ The output should be something like:
|
||||||
Name Stmts Miss Cover
|
Name Stmts Miss Cover
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
movies/__init__.py 0 0 100%
|
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/containers.py 9 0 100%
|
||||||
movies/finders.py 9 0 100%
|
movies/entities.py 7 1 86%
|
||||||
movies/fixtures.py 1 0 100%
|
movies/finders.py 26 13 50%
|
||||||
movies/listers.py 8 0 100%
|
movies/listers.py 8 0 100%
|
||||||
movies/models.py 7 1 86%
|
|
||||||
movies/storages.py 32 17 47%
|
|
||||||
movies/tests.py 24 0 100%
|
movies/tests.py 24 0 100%
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
TOTAL 105 33 69%
|
TOTAL 84 24 71%
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
storage:
|
finder:
|
||||||
|
|
||||||
csv:
|
csv:
|
||||||
path: "data/movies.csv"
|
path: "data/movies.csv"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Ignore everything in this directory
|
# Everything
|
||||||
*
|
*
|
||||||
|
|
||||||
# Except this file:
|
# Except this file:
|
||||||
!.gitignore
|
!.gitignore
|
||||||
|
!fixtures.py
|
||||||
|
|
43
examples/miniapps/movie-lister/data/fixtures.py
Normal file
43
examples/miniapps/movie-lister/data/fixtures.py
Normal file
|
@ -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()
|
|
@ -5,18 +5,20 @@ from .containers import ApplicationContainer
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
container = ApplicationContainer()
|
container = ApplicationContainer()
|
||||||
container.config.from_yaml('config.yml')
|
|
||||||
container.config.storage.type.from_env('MOVIE_STORAGE_TYPE')
|
|
||||||
|
|
||||||
storage = container.storage()
|
container.config.from_yaml('config.yml')
|
||||||
fixtures = container.fixtures()
|
container.config.finder.type.from_env('MOVIE_FINDER_TYPE')
|
||||||
storage.load_all(fixtures)
|
|
||||||
|
|
||||||
lister = container.lister()
|
lister = container.lister()
|
||||||
print(lister.movies_directed_by('Francis Lawrence'))
|
|
||||||
print(lister.movies_directed_by('Patricia Riggen'))
|
print(
|
||||||
print(lister.movies_directed_by('JJ Abrams'))
|
'Francis Lawrence movies:',
|
||||||
print(lister.movies_released_in(2015))
|
lister.movies_directed_by('Francis Lawrence'),
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
'2016 movies:',
|
||||||
|
lister.movies_released_in(2016),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -2,33 +2,32 @@
|
||||||
|
|
||||||
from dependency_injector import containers, providers
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
from . import finders, listers, storages, models, fixtures
|
from . import finders, listers, entities
|
||||||
|
|
||||||
|
|
||||||
class ApplicationContainer(containers.DeclarativeContainer):
|
class ApplicationContainer(containers.DeclarativeContainer):
|
||||||
|
|
||||||
config = providers.Configuration()
|
config = providers.Configuration()
|
||||||
|
|
||||||
fixtures = providers.Object(fixtures.MOVIES_SAMPLE_DATA)
|
movie = providers.Factory(entities.Movie)
|
||||||
|
|
||||||
storage = providers.Selector(
|
csv_finder = providers.Singleton(
|
||||||
config.storage.type,
|
finders.CsvMovieFinder,
|
||||||
csv=providers.Singleton(
|
movie_factory=movie.provider,
|
||||||
storages.CsvMovieStorage,
|
path=config.finder.csv.path,
|
||||||
options=config.storage[config.storage.type],
|
delimiter=config.finder.csv.delimiter,
|
||||||
),
|
|
||||||
sqlite=providers.Singleton(
|
|
||||||
storages.SqliteMovieStorage,
|
|
||||||
options=config.storage[config.storage.type],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
movie = providers.Factory(models.Movie)
|
sqlite_finder = providers.Singleton(
|
||||||
|
finders.SqliteMovieFinder,
|
||||||
finder = providers.Factory(
|
|
||||||
finders.MovieFinder,
|
|
||||||
movie_factory=movie.provider,
|
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(
|
lister = providers.Factory(
|
||||||
|
|
|
@ -1,23 +1,50 @@
|
||||||
"""Movie finders module."""
|
"""Movie finders module."""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import sqlite3
|
||||||
from typing import Callable, List
|
from typing import Callable, List
|
||||||
|
|
||||||
from .models import Movie
|
from .entities import Movie
|
||||||
from .storages import MovieStorage
|
|
||||||
|
|
||||||
|
|
||||||
class MovieFinder:
|
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
movie_factory: Callable[..., Movie],
|
movie_factory: Callable[..., Movie],
|
||||||
movie_storage: MovieStorage,
|
path: str,
|
||||||
|
delimiter: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._movie_factory = movie_factory
|
self._csv_file_path = path
|
||||||
self._movie_storage = movie_storage
|
self._delimiter = delimiter
|
||||||
|
super().__init__(movie_factory)
|
||||||
|
|
||||||
def find_all(self) -> List[Movie]:
|
def find_all(self) -> List[Movie]:
|
||||||
return [
|
with open(self._csv_file_path) as csv_file:
|
||||||
self._movie_factory(*row)
|
csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
|
||||||
for row in self._movie_storage.get_all()
|
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]
|
||||||
|
|
|
@ -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'),
|
|
||||||
]
|
|
|
@ -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]
|
|
|
@ -11,7 +11,7 @@ from .containers import ApplicationContainer
|
||||||
def container():
|
def container():
|
||||||
container = ApplicationContainer()
|
container = ApplicationContainer()
|
||||||
container.config.from_dict({
|
container.config.from_dict({
|
||||||
'storage': {
|
'finder': {
|
||||||
'type': 'csv',
|
'type': 'csv',
|
||||||
'csv': {
|
'csv': {
|
||||||
'path': '/fake-movies.csv',
|
'path': '/fake-movies.csv',
|
||||||
|
@ -26,13 +26,13 @@ def container():
|
||||||
|
|
||||||
|
|
||||||
def test_movies_directed_by(container):
|
def test_movies_directed_by(container):
|
||||||
storage_mock = mock.Mock()
|
finder_mock = mock.Mock()
|
||||||
storage_mock.get_all.return_value = [
|
finder_mock.find_all.return_value = [
|
||||||
('The 33', 2015, 'Patricia Riggen'),
|
container.movie('The 33', 2015, 'Patricia Riggen'),
|
||||||
('The Jungle Book', 2016, 'Jon Favreau'),
|
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
|
||||||
]
|
]
|
||||||
|
|
||||||
with container.storage.override(storage_mock):
|
with container.finder.override(finder_mock):
|
||||||
lister = container.lister()
|
lister = container.lister()
|
||||||
movies = lister.movies_directed_by('Jon Favreau')
|
movies = lister.movies_directed_by('Jon Favreau')
|
||||||
|
|
||||||
|
@ -41,13 +41,13 @@ def test_movies_directed_by(container):
|
||||||
|
|
||||||
|
|
||||||
def test_movies_released_in(container):
|
def test_movies_released_in(container):
|
||||||
storage_mock = mock.Mock()
|
finder_mock = mock.Mock()
|
||||||
storage_mock.get_all.return_value = [
|
finder_mock.find_all.return_value = [
|
||||||
('The 33', 2015, 'Patricia Riggen'),
|
container.movie('The 33', 2015, 'Patricia Riggen'),
|
||||||
('The Jungle Book', 2016, 'Jon Favreau'),
|
container.movie('The Jungle Book', 2016, 'Jon Favreau'),
|
||||||
]
|
]
|
||||||
|
|
||||||
with container.storage.override(storage_mock):
|
with container.finder.override(finder_mock):
|
||||||
lister = container.lister()
|
lister = container.lister()
|
||||||
movies = lister.movies_released_in(2015)
|
movies = lister.movies_released_in(2015)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user