Movie lister example rework (#280)

* Rework movie lister example app

* Code style fix

* Doc block fix

* Update the container

* Make second round of the refactoring

* Rename name to title

* Remove old movie lister docs from the examples

* Add fixtures generator output on success

* Update docblock in the entities module

* Update example readme

* Add CLI app tutorial

* Update some wording in the other tutorials

* Spread link to the tutorial

* Fix code indentation issue
This commit is contained in:
Roman Mogylatov 2020-08-12 17:04:46 -04:00 committed by GitHub
parent ca18fea26c
commit 5358dd85f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1416 additions and 619 deletions

View File

@ -157,6 +157,7 @@ Choose one of the following:
- `Flask web application tutorial <http://python-dependency-injector.ets-labs.org/tutorials/flask.html>`_
- `Aiohttp REST API tutorial <http://python-dependency-injector.ets-labs.org/tutorials/aiohttp.html>`_
- `Asyncio monitoring daemon tutorial <http://python-dependency-injector.ets-labs.org/tutorials/asyncio-daemon.html>`_
- `CLI application tutorial <http://python-dependency-injector.ets-labs.org/tutorials/cli.html>`_
Installation
============

View File

@ -15,7 +15,6 @@ and powered by *Dependency Injector* framework.
.. toctree::
:maxdepth: 2
movie_lister
services_miniapp_v1
services_miniapp_v2
bundles_miniapp

View File

@ -1,124 +0,0 @@
Movie lister naive example
--------------------------
.. meta::
:description: Movie lister - is a naive example of dependency injection and
inversion of control containers on Python. Original example
was taken from Martin Fowler's article about dependency
injection and inversion of control.
This naive example was taken from Martin Fowler's article about dependency
injection and inversion of control: http://www.martinfowler.com/articles/injection.html
Like Martin says:
.. pull-quote::
*Like all of my examples it's one of those super-simple examples;
small enough to be unreal, but hopefully enough for you to visualize
what's going on without falling into the bog of a real example.*
While original Martin's MovieLister example was a bit modified here, it
makes sense to provide some description. So, the idea of this example is to
create ``movies`` library that can be configured to work with different
movie databases (csv, sqlite, etc...) and provide 2 main features:
1. List all movies that were directed by certain person.
2. List all movies that were released in certain year.
Also this example contains 3 mini applications that are based on ``movies``
library:
1. ``app_csv.py`` - list movies by certain criteria from csv file database.
2. ``app_db.py`` - list movies by certain criteria from sqlite database.
3. ``app_db_csv.py`` - list movies by certain criteria from csv file and
sqlite databases.
Instructions for running:
.. code-block:: bash
python app_csv.py
python app_db.py
python app_db_csv.py
Full code of example could be found on GitHub_.
Movies library
~~~~~~~~~~~~~~
Classes diagram:
.. image:: /images/miniapps/movie_lister/classes.png
:width: 100%
:align: center
Movies library structure:
.. code-block:: bash
/movies
/__init__.py
/finders.py
/listers.py
/models.py
Listing of ``movies/__init__.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/movies/__init__.py
:language: python
Example application
~~~~~~~~~~~~~~~~~~~
Example application structure:
.. code-block:: bash
/example
/__init__.py
/db.py
/main.py
Listing of ``examples/main.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/example/main.py
:language: python
Listing of ``examples/db.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/example/db.py
:language: python
Csv application
~~~~~~~~~~~~~~~
Listing of ``app_csv.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/app_csv.py
:language: python
Database application
~~~~~~~~~~~~~~~~~~~~
Listing of ``app_db.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/app_db.py
:language: python
Csv and database application
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Listing of ``app_db_csv.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/app_db_csv.py
:language: python
.. disqus::
.. _GitHub: https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/movie_lister

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View File

@ -135,8 +135,9 @@ Choose one of the following as a next step:
+ :ref:`flask-tutorial`
+ :ref:`aiohttp-tutorial`
+ :ref:`asyncio-daemon-tutorial`
+ Know more about the :ref:`providers`.
+ Go to the :ref:`contents`.
+ :ref:`cli-tutorial`
+ Know more about the :ref:`providers`
+ Go to the :ref:`contents`
Useful links
~~~~~~~~~~~~

View File

@ -1,3 +1,5 @@
.. _selector-provider:
Selector providers
------------------

View File

@ -922,7 +922,7 @@ We've used the ``Dependency Injector`` as a dependency injection framework.
The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff
when you need to understand or change your application structure. It's easy with the container,
cause you have everything in one place:
cause you have everything defined explicitly in one place:
.. code-block:: python
@ -962,8 +962,8 @@ cause you have everything in one place:
What's next?
- Look at the other :ref:`tutorials`.
- Know more about the :ref:`providers`.
- Go to the :ref:`contents`.
- Look at the other :ref:`tutorials`
- Know more about the :ref:`providers`
- Go to the :ref:`contents`
.. disqus::

View File

@ -1030,7 +1030,7 @@ We've used the ``Dependency Injector`` as a dependency injection framework.
The benefit you get with the ``Dependency Injector`` is the container. It starts to payoff
when you need to understand or change your application structure. It's easy with the container,
cause you have everything in one place:
cause you have everything defined explicitly in one place:
.. code-block:: python
@ -1080,8 +1080,8 @@ cause you have everything in one place:
What's next?
- Look at the other :ref:`tutorials`.
- Know more about the :ref:`providers`.
- Go to the :ref:`contents`.
- Look at the other :ref:`tutorials`
- Know more about the :ref:`providers`
- Go to the :ref:`contents`
.. disqus::

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

1054
docs/tutorials/cli.rst Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1097,7 +1097,7 @@ In this tutorial we've built a ``Flask`` application following the dependency in
We've used the ``Dependency Injector`` as a dependency injection framework.
The main part of this application is the container. It keeps all the application components and
their dependencies in one place:
their dependencies defined explicitly in one place:
.. code-block:: python
@ -1141,9 +1141,9 @@ their dependencies in one place:
What's next?
- Look at the other :ref:`tutorials`.
- Know more about the :ref:`providers`.
- Go to the :ref:`contents`.
- Look at the other :ref:`tutorials`
- Know more about the :ref:`providers`
- Go to the :ref:`contents`
.. disqus::

View File

@ -12,5 +12,6 @@ frameworks.
flask
aiohttp
asyncio-daemon
cli
.. disqus::

View File

@ -1,2 +1,2 @@
[pydocstyle]
ignore = D100,D101,D102,D103,D107,D203,D213
ignore = D100,D101,D102,D103,D105,D107,D203,D213

View File

@ -0,0 +1,75 @@
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:
http://www.martinfowler.com/articles/injection.html
Run
---
Create a virtual environment:
.. code-block:: bash
virtualenv venv
. venv/bin/activate
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_FINDER_TYPE=csv python -m movies
MOVIE_FINDER_TYPE=sqlite python -m movies
The output should be something like this for each command:
.. code-block:: bash
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')]
Test
----
To run the tests do:
.. code-block:: bash
pytest movies/tests.py --cov=movies
The output should be something like:
.. code-block::
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%

View File

@ -0,0 +1,8 @@
finder:
csv:
path: "data/movies.csv"
delimiter: ","
sqlite:
path: "data/movies.db"

View File

@ -0,0 +1,6 @@
# Everything
*
# Except this file:
!.gitignore
!fixtures.py

View File

@ -0,0 +1,44 @@
"""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()

View File

@ -0,0 +1 @@
"""Top-level package."""

View File

@ -0,0 +1,25 @@
"""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()

View File

@ -0,0 +1,36 @@
"""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,
)

View File

@ -0,0 +1,17 @@
"""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),
)

View File

@ -0,0 +1,50 @@
"""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]

View File

@ -0,0 +1,21 @@
"""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
]

View File

@ -0,0 +1,55 @@
"""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'

View File

@ -0,0 +1,4 @@
dependency-injector
pyyaml
pytest
pytest-cov

View File

@ -1,16 +0,0 @@
A naive example of dependency injection in Python
=================================================
Example implementation of dependency injection on Python from Martin Fowler's
article about dependency injection and inversion of control:
http://www.martinfowler.com/articles/injection.html
Instructions for running:
.. code-block:: bash
python app_csv.py
python app_db.py
python app_db_csv.py

View File

@ -1,49 +0,0 @@
"""A naive example of dependency injection on Python.
Example implementation of dependency injection in Python from Martin Fowler's
article about dependency injection and inversion of control:
http://www.martinfowler.com/articles/injection.html
This mini application uses ``movies`` library, that is configured to work with
csv file movies database.
"""
import movies
import movies.finders
import example.db
import example.main
import settings
import fixtures
import dependency_injector.containers as containers
import dependency_injector.providers as providers
@containers.override(movies.MoviesModule)
class MyMoviesModule(containers.DeclarativeContainer):
"""IoC container for overriding movies module component providers."""
finder = providers.Factory(movies.finders.CsvMovieFinder,
csv_file_path=settings.MOVIES_CSV_PATH,
delimiter=',',
**movies.MoviesModule.finder.kwargs)
class CsvApplication(containers.DeclarativeContainer):
"""IoC container of csv application component providers."""
main = providers.Callable(example.main.main,
movie_lister=movies.MoviesModule.lister)
init_db = providers.Callable(example.db.init_csv,
movies_data=fixtures.MOVIES_SAMPLE_DATA,
csv_file_path=settings.MOVIES_CSV_PATH,
delimiter=',')
if __name__ == '__main__':
CsvApplication.init_db()
CsvApplication.main()

View File

@ -1,55 +0,0 @@
"""A naive example of dependency injection on Python.
Example implementation of dependency injection in Python from Martin Fowler's
article about dependency injection and inversion of control:
http://www.martinfowler.com/articles/injection.html
This mini application uses ``movies`` library, that is configured to work with
sqlite movies database.
"""
import sqlite3
import movies
import movies.finders
import example.db
import example.main
import settings
import fixtures
import dependency_injector.containers as containers
import dependency_injector.providers as providers
class ResourcesModule(containers.DeclarativeContainer):
"""IoC container of application resource providers."""
database = providers.Singleton(sqlite3.connect, settings.MOVIES_DB_PATH)
@containers.override(movies.MoviesModule)
class MyMoviesModule(containers.DeclarativeContainer):
"""IoC container for overriding movies module component providers."""
finder = providers.Factory(movies.finders.SqliteMovieFinder,
database=ResourcesModule.database,
**movies.MoviesModule.finder.kwargs)
class DbApplication(containers.DeclarativeContainer):
"""IoC container of database application component providers."""
main = providers.Callable(example.main.main,
movie_lister=movies.MoviesModule.lister)
init_db = providers.Callable(example.db.init_sqlite,
movies_data=fixtures.MOVIES_SAMPLE_DATA,
database=ResourcesModule.database)
if __name__ == '__main__':
DbApplication.init_db()
DbApplication.main()

View File

@ -1,80 +0,0 @@
"""A naive example of dependency injection on Python.
Example implementation of dependency injection in Python from Martin Fowler's
article about dependency injection and inversion of control:
http://www.martinfowler.com/articles/injection.html
This mini application uses ``movies`` library, that is configured to work with
sqlite movies database and csv file movies database.
"""
import sqlite3
import movies
import movies.finders
import example.db
import example.main
import settings
import fixtures
import dependency_injector.containers as containers
import dependency_injector.providers as providers
class ResourcesModule(containers.DeclarativeContainer):
"""IoC container of application resource providers."""
database = providers.Singleton(sqlite3.connect, settings.MOVIES_DB_PATH)
@containers.copy(movies.MoviesModule)
class DbMoviesModule(movies.MoviesModule):
"""IoC container for overriding movies module component providers."""
finder = providers.Factory(movies.finders.SqliteMovieFinder,
database=ResourcesModule.database,
**movies.MoviesModule.finder.kwargs)
@containers.copy(movies.MoviesModule)
class CsvMoviesModule(movies.MoviesModule):
"""IoC container for overriding movies module component providers."""
finder = providers.Factory(movies.finders.CsvMovieFinder,
csv_file_path=settings.MOVIES_CSV_PATH,
delimiter=',',
**movies.MoviesModule.finder.kwargs)
class DbApplication(containers.DeclarativeContainer):
"""IoC container of database application component providers."""
main = providers.Callable(example.main.main,
movie_lister=DbMoviesModule.lister)
init_db = providers.Callable(example.db.init_sqlite,
movies_data=fixtures.MOVIES_SAMPLE_DATA,
database=ResourcesModule.database)
class CsvApplication(containers.DeclarativeContainer):
"""IoC container of csv application component providers."""
main = providers.Callable(example.main.main,
movie_lister=CsvMoviesModule.lister)
init_db = providers.Callable(example.db.init_csv,
movies_data=fixtures.MOVIES_SAMPLE_DATA,
csv_file_path=settings.MOVIES_CSV_PATH,
delimiter=',')
if __name__ == '__main__':
DbApplication.init_db()
DbApplication.main()
CsvApplication.init_db()
CsvApplication.main()

View File

@ -1,5 +0,0 @@
# Ignore everything in this directory
*
# Except this file:
!.gitignore

View File

@ -1,3 +0,0 @@
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 The Hunger Games: Mockingjay - Part 2 2015 Francis Lawrence
2 The 33 2015 Patricia Riggen
3 Star Wars: Episode VII - The Force Awakens 2015 JJ Abrams

View File

@ -1 +0,0 @@
"""Example top-level package."""

View File

@ -1,35 +0,0 @@
"""Example database module."""
import csv
def init_sqlite(movies_data, database):
"""Initialize sqlite3 movies database.
:param movies_data: Data about movies
:type movies_data: tuple[tuple]
:param database: Connection to sqlite database with movies data
:type database: sqlite3.Connection
"""
with database:
database.execute('CREATE TABLE IF NOT EXISTS movies '
'(name text, year int, director text)')
database.execute('DELETE FROM movies')
database.executemany('INSERT INTO movies VALUES (?,?,?)', movies_data)
def init_csv(movies_data, csv_file_path, delimiter):
"""Initialize csv movies database.
:param movies_data: Data about movies
:type movies_data: tuple[tuple]
:param csv_file_path: Path to csv file with movies data
:type csv_file_path: str
:param delimiter: Csv file's delimiter
:type delimiter: str
"""
with open(csv_file_path, 'w') as csv_file:
csv.writer(csv_file, delimiter=delimiter).writerows(movies_data)

View File

@ -1,17 +0,0 @@
"""Example main module."""
def main(movie_lister):
"""Run application.
This program prints info about all movies that were directed by different
persons and then prints all movies that were released in 2015.
:param movie_lister: Movie lister instance
:type movie_lister: movies.listers.MovieLister
"""
print(movie_lister.movies_directed_by('Francis Lawrence'))
print(movie_lister.movies_directed_by('Patricia Riggen'))
print(movie_lister.movies_directed_by('JJ Abrams'))
print(movie_lister.movies_released_in(2015))

View File

@ -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'),
)

View File

@ -1,32 +0,0 @@
"""Movies package.
Top-level package of movies library. This package contains IoC container of
movies module component providers - ``MoviesModule``. It is recommended to use
movies library functionality by fetching required instances from
``MoviesModule`` providers.
``MoviesModule.finder`` is a factory that provides abstract component
``finders.MovieFinder``. This provider should be overridden by provider of
concrete finder implementation in terms of library configuration.
Each of ``MoviesModule`` providers could be overridden.
"""
import movies.finders
import movies.listers
import movies.models
import dependency_injector.containers as containers
import dependency_injector.providers as providers
class MoviesModule(containers.DeclarativeContainer):
"""IoC container of movies module component providers."""
movie = providers.Factory(movies.models.Movie)
finder = providers.AbstractFactory(movies.finders.MovieFinder,
movie_model=movie.provider)
lister = providers.Factory(movies.listers.MovieLister,
movie_finder=finder)

View File

@ -1,87 +0,0 @@
"""Movie finders module.
This module contains all finder implementations.
"""
import csv
class MovieFinder:
"""Movie finder component.
Movie finder component is responsible for fetching movies data from
various storage.
"""
def __init__(self, movie_model):
"""Initialize instance.
:param movie_model: Movie model's factory
:type movie_model: movies.models.Movie
"""
self._movie_model = movie_model
def find_all(self):
"""Return all found movies.
:rtype: list[movies.models.Movie]
:return: List of movie instances.
"""
raise NotImplementedError()
class CsvMovieFinder(MovieFinder):
"""Movie finder that fetches movies data from csv file."""
def __init__(self, movie_model, csv_file_path, delimiter):
"""Initialize instance.
:param movie_model: Movie model's factory
:type movie_model: movies.models.Movie
:param csv_file_path: Path to csv file with movies data
:type csv_file_path: str
:param delimiter: Csv file's delimiter
:type delimiter: str
"""
self._csv_file_path = csv_file_path
self._delimiter = delimiter
super().__init__(movie_model)
def find_all(self):
"""Return all found movies.
:rtype: list[movies.models.Movie]
:return: List of movie instances.
"""
with open(self._csv_file_path) as csv_file:
csv_reader = csv.reader(csv_file, delimiter=self._delimiter)
return [self._movie_model(*row) for row in csv_reader]
class SqliteMovieFinder(MovieFinder):
"""Movie finder that fetches movies data from sqlite database."""
def __init__(self, movie_model, database):
"""Initialize instance.
:param movie_model: Movie model's factory
:type movie_model: (object) -> movies.models.Movie
:param database: Connection to sqlite database with movies data
:type database: sqlite3.Connection
"""
self._database = database
super().__init__(movie_model)
def find_all(self):
"""Return all found movies.
:rtype: list[movies.models.Movie]
:return: List of movie instances.
"""
with self._database:
rows = self._database.execute('SELECT name, year, director '
'FROM movies')
return [self._movie_model(*row) for row in rows]

View File

@ -1,44 +0,0 @@
"""Movie listers module.
This module contains all lister implementations.
"""
class MovieLister:
"""Movie lister component.
Movie lister component provides several methods for filtering movies by
specific criteria.
"""
def __init__(self, movie_finder):
"""Initialize instance.
:param movie_finder: Movie finder instance
:type movie_finder: movies.finders.MovieFinder
"""
self._movie_finder = movie_finder
def movies_directed_by(self, director):
"""Return list of movies that were directed by certain person.
:param director: Director's name
:type director: str
:rtype: list[movies.models.Movie]
:return: List of movie instances.
"""
return [movie for movie in self._movie_finder.find_all()
if movie.director == director]
def movies_released_in(self, year):
"""Return list of movies that were released in certain year.
:param year: Release year
:type year: int
:rtype: list[movies.models.Movie]
:return: List of movie instances.
"""
return [movie for movie in self._movie_finder.find_all()
if movie.year == year]

View File

@ -1,36 +0,0 @@
"""Movie models module.
This module contains all model implementations.
"""
class Movie:
"""Base movie model."""
def __init__(self, name, year, director):
"""Initialize instance.
:param name: Movie's name
:type name: str
:param year: Year, when movie was released
:type year: int
:param director: Name of person, that directed the movie
:type director: str
"""
self.name = str(name)
self.year = int(year)
self.director = str(director)
def __repr__(self):
"""Return string representation of movie instance.
:rtype: str
:return: Movie's string representation.
"""
return '{0}(name={1}, year={2}, director={3})'.format(
self.__class__.__name__,
repr(self.name),
repr(self.year),
repr(self.director))

View File

@ -1,11 +0,0 @@
"""Settings module.
This module contains application's settings and constants.
"""
import os
DATA_DIR = os.path.abspath(os.path.dirname(__file__) + '/data')
MOVIES_CSV_PATH = DATA_DIR + '/movies.csv'
MOVIES_DB_PATH = DATA_DIR + '/movies.db'