Movie lister naive example

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:

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:

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:

../_images/classes.png

Movies library structure:

/movies
    /__init__.py
    /finders.py
    /listers.py
    /models.py

Listing of movies/__init__.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
"""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."""

    models_factory = providers.Factory(movies.models.Movie)

    finder = providers.AbstractFactory(movies.finders.MovieFinder,
                                       movie_model=models_factory.delegate())

    lister = providers.Factory(movies.listers.MovieLister,
                               movie_finder=finder)

Example application

Example application structure:

/example
    /__init__.py
    /db.py
    /main.py

Listing of examples/main.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
"""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))

Listing of examples/db.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
"""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)

Csv application

Listing of app_csv.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
"""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()

Database application

Listing of app_db.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
"""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()

Csv and database application

Listing of app_db_csv.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
"""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()