Merge pull request #136 from ets-labs/deprecate_inject_decorator

Deprecate inject decorator
This commit is contained in:
Roman 2016-09-23 00:31:58 +03:00 committed by GitHub
commit 53f60d6fed
28 changed files with 310 additions and 193 deletions

View File

@ -62,6 +62,8 @@ system that consists from several business and platform services:
import sqlite3
import boto.s3.connection
import example.main
import example.services
import dependency_injector.containers as containers
@ -92,29 +94,38 @@ system that consists from several business and platform services:
db=Platform.database,
s3=Platform.s3)
Next example demonstrates usage of ``@inject`` decorator with IoC containers
defined above:
class Application(containers.DeclarativeContainer):
"""IoC container of application component providers."""
main = providers.Callable(example.main.main,
users_service=Services.users,
auth_service=Services.auth,
photos_service=Services.photos)
Next example demonstrates usage of IoC containers & providers defined above:
.. code-block:: python
"""Dependency Injector @inject decorator example."""
"""Run example application."""
import application
import dependency_injector.injections as injections
@injections.inject(users_service=application.Services.users)
@injections.inject(auth_service=application.Services.auth)
@injections.inject(photos_service=application.Services.photos)
def main(users_service, auth_service, photos_service):
"""Main function."""
user = users_service.get_user('user')
auth_service.authenticate(user, 'secret')
photos_service.upload_photo(user['id'], 'photo.jpg')
import containers
if __name__ == '__main__':
main()
containers.Application.main()
# Previous call is an equivalent of next operations:
#
# database = sqlite3.connect(':memory:')
# s3 = boto.s3.connection.S3Connection(aws_access_key_id='KEY',
# aws_secret_access_key='SECRET')
#
# example.main.main(users_service=example.services.Users(db=database),
# auth_service=example.services.Auth(db=database,
# token_ttl=3600),
# photos_service=example.services.Photos(db=database,
# s3=s3))
Alternative definition styles
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -136,21 +147,6 @@ IoC containers from previous example could look like these:
.add_kwargs(aws_access_key_id='KEY',
aws_secret_access_key='SECRET')
class Services(containers.DeclarativeContainer):
"""IoC container of business service providers."""
users = providers.Factory(example.services.Users) \
.add_kwargs(db=Platform.database)
auth = providers.Factory(example.services.Auth) \
.add_kwargs(db=Platform.database,
token_ttl=3600)
photos = providers.Factory(example.services.Photos) \
.add_kwargs(db=Platform.database,
s3=Platform.s3)
or like this these:
.. code-block:: python
@ -165,21 +161,6 @@ or like this these:
s3.add_kwargs(aws_access_key_id='KEY',
aws_secret_access_key='SECRET')
class Services(containers.DeclarativeContainer):
"""IoC container of business service providers."""
users = providers.Factory(example.services.Users)
users.add_kwargs(db=Platform.database)
auth = providers.Factory(example.services.Auth)
auth.add_kwargs(db=Platform.database,
token_ttl=3600)
photos = providers.Factory(example.services.Photos)
photos.add_kwargs(db=Platform.database,
s3=Platform.s3)
You can get more *Dependency Injector* examples in ``/examples`` directory on
GitHub:

View File

@ -1,5 +1,7 @@
"""Dependency injector injections module."""
import warnings
import six
from dependency_injector.providers.base import (
@ -13,6 +15,13 @@ from dependency_injector import errors
def inject(*args, **kwargs):
"""Dependency injection decorator.
.. warning::
:py:func:`inject` decorator has been deprecated since version 2.2.0.
Usage of :py:func:`inject` decorator can lead to bad design and could
be considered as anti-pattern.
:py:func:`inject` decorator can be used for making inline dependency
injections. It patches decorated callable in such way that dependency
injection will be done during every call of decorated callable.
@ -41,6 +50,10 @@ def inject(*args, **kwargs):
def __init__(self, arg1, arg2):
pass
.. deprecated:: 2.2.0
Usage of :py:func:`inject` decorator can lead to bad design and could
be considered as anti-pattern.
:param args: Tuple of context positional arguments.
:type args: tuple[object]
@ -50,6 +63,11 @@ def inject(*args, **kwargs):
:return: Class / callable decorator
:rtype: (callable) -> (type | callable)
"""
warnings.warn(message='Call to a deprecated decorator - @{0}.{1}'
.format(inject.__module__, inject.__name__),
category=DeprecationWarning,
stacklevel=2)
arg_injections = _parse_positional_injections(args)
kwarg_injections = _parse_keyword_injections(kwargs)

View File

@ -9,6 +9,10 @@ Current section of documentation describes advanced usage of
.. currentmodule:: dependency_injector.injections
.. warning::
:py:func:`inject` decorator has been deprecated since version 2.2.0.
:py:func:`inject` decorator is a part of
:py:mod:`dependency_injector.injections` module.

View File

@ -7,6 +7,5 @@ API Documentation
top_level
providers
containers
injections
utils
errors

View File

@ -9,8 +9,8 @@ Examples
"Dependency Injector" framework.
Current section of documentation is designed to provide several example mini
applications that are built on the top of inversion of control principle and
powered by *Dependency Injector* framework.
applications that are built according to the inversion of control principle
and powered by *Dependency Injector* framework.
.. toctree::
:maxdepth: 2

View File

@ -20,14 +20,14 @@ Like Martin says:
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 configurable to work with different
movie databases (csv, sqlite) and provide 2 main features:
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 :
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.
@ -38,8 +38,6 @@ Instructions for running:
.. code-block:: bash
python create_db.py
python app_csv.py
python app_db.py
python app_db_csv.py
@ -74,6 +72,30 @@ Listing of ``movies/__init__.py``:
:language: python
:linenos:
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
:linenos:
Listing of ``examples/db.py``:
.. literalinclude:: ../../examples/miniapps/movie_lister/example/db.py
:language: python
:linenos:
Csv application
~~~~~~~~~~~~~~~

View File

@ -59,7 +59,6 @@ Contents
main/installation
providers/index
containers/index
advanced_usage/index
examples/index
api/index
main/feedback

View File

@ -9,7 +9,7 @@ follows `Semantic versioning`_
Development version
-------------------
- No features.
- Deprecate ``inject`` decorator.
2.1.1
-----

View File

@ -11,8 +11,6 @@ Instructions for running:
.. code-block:: bash
python create_db.py
python app_csv.py
python app_db.py
python app_db_csv.py

View File

@ -9,42 +9,40 @@ This mini application uses ``movies`` library, that is configured to work with
csv file movies database.
"""
import dependency_injector.containers as containers
import dependency_injector.providers as providers
import dependency_injector.injections as injections
import movies
import movies.finders
import example.db
import example.main
import settings
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."""
movie_finder = providers.Factory(movies.finders.CsvMovieFinder,
csv_file=settings.MOVIES_CSV_PATH,
delimeter=',',
csv_file_path=settings.MOVIES_CSV_PATH,
delimiter=',',
**movies.MoviesModule.movie_finder.kwargs)
@injections.inject(movies.MoviesModule.movie_lister)
def main(movie_lister):
"""Main function.
class CsvApplication(containers.DeclarativeContainer):
"""IoC container of csv application component providers."""
This program prints info about all movies that were directed by different
persons and then prints all movies that were released in 2015.
main = providers.Callable(example.main.main,
movie_lister=movies.MoviesModule.movie_lister)
: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)
init_db = providers.Callable(example.db.init_csv,
movies_data=settings.MOVIES_SAMPLE_DATA,
csv_file_path=settings.MOVIES_CSV_PATH,
delimiter=',')
if __name__ == '__main__':
main()
CsvApplication.init_db()
CsvApplication.main()

View File

@ -11,18 +11,20 @@ sqlite movies database.
import sqlite3
import dependency_injector.containers as containers
import dependency_injector.providers as providers
import dependency_injector.injections as injections
import movies
import movies.finders
import example.db
import example.main
import settings
import dependency_injector.containers as containers
import dependency_injector.providers as providers
class ApplicationModule(containers.DeclarativeContainer):
"""IoC container of application component providers."""
class ResourcesModule(containers.DeclarativeContainer):
"""IoC container of application resource providers."""
database = providers.Singleton(sqlite3.connect, settings.MOVIES_DB_PATH)
@ -32,26 +34,21 @@ class MyMoviesModule(containers.DeclarativeContainer):
"""IoC container for overriding movies module component providers."""
movie_finder = providers.Factory(movies.finders.SqliteMovieFinder,
database=ApplicationModule.database,
database=ResourcesModule.database,
**movies.MoviesModule.movie_finder.kwargs)
@injections.inject(movies.MoviesModule.movie_lister)
def main(movie_lister):
"""Main function.
class DbApplication(containers.DeclarativeContainer):
"""IoC container of database application component providers."""
This program prints info about all movies that were directed by different
persons and then prints all movies that were released in 2015.
main = providers.Callable(example.main.main,
movie_lister=movies.MoviesModule.movie_lister)
: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)
init_db = providers.Callable(example.db.init_sqlite,
movies_data=settings.MOVIES_SAMPLE_DATA,
database=ResourcesModule.database)
if __name__ == '__main__':
main()
DbApplication.init_db()
DbApplication.main()

View File

@ -11,18 +11,20 @@ sqlite movies database and csv file movies database.
import sqlite3
import dependency_injector.containers as containers
import dependency_injector.providers as providers
import dependency_injector.injections as injections
import movies
import movies.finders
import example.db
import example.main
import settings
import dependency_injector.containers as containers
import dependency_injector.providers as providers
class ApplicationModule(containers.DeclarativeContainer):
"""IoC container of application component providers."""
class ResourcesModule(containers.DeclarativeContainer):
"""IoC container of application resource providers."""
database = providers.Singleton(sqlite3.connect, settings.MOVIES_DB_PATH)
@ -32,7 +34,7 @@ class DbMoviesModule(movies.MoviesModule):
"""IoC container for overriding movies module component providers."""
movie_finder = providers.Factory(movies.finders.SqliteMovieFinder,
database=ApplicationModule.database,
database=ResourcesModule.database,
**movies.MoviesModule.movie_finder.kwargs)
@ -41,31 +43,36 @@ class CsvMoviesModule(movies.MoviesModule):
"""IoC container for overriding movies module component providers."""
movie_finder = providers.Factory(movies.finders.CsvMovieFinder,
csv_file=settings.MOVIES_CSV_PATH,
delimeter=',',
csv_file_path=settings.MOVIES_CSV_PATH,
delimiter=',',
**movies.MoviesModule.movie_finder.kwargs)
@injections.inject(db_movie_lister=DbMoviesModule.movie_lister)
@injections.inject(csv_movie_lister=CsvMoviesModule.movie_lister)
def main(db_movie_lister, csv_movie_lister):
"""Main function.
class DbApplication(containers.DeclarativeContainer):
"""IoC container of database application component providers."""
This program prints info about all movies that were directed by different
persons and then prints all movies that were released in 2015.
main = providers.Callable(example.main.main,
movie_lister=DbMoviesModule.movie_lister)
:param db_movie_lister: Movie lister, configured to work with database
:type db_movie_lister: movies.listers.MovieLister
init_db = providers.Callable(example.db.init_sqlite,
movies_data=settings.MOVIES_SAMPLE_DATA,
database=ResourcesModule.database)
:param csv_movie_lister: Movie lister, configured to work with csv file
:type csv_movie_lister: movies.listers.MovieLister
"""
for movie_lister in (db_movie_lister, csv_movie_lister):
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)
class CsvApplication(containers.DeclarativeContainer):
"""IoC container of csv application component providers."""
main = providers.Callable(example.main.main,
movie_lister=CsvMoviesModule.movie_lister)
init_db = providers.Callable(example.db.init_csv,
movies_data=settings.MOVIES_SAMPLE_DATA,
csv_file_path=settings.MOVIES_CSV_PATH,
delimiter=',')
if __name__ == '__main__':
main()
DbApplication.init_db()
DbApplication.main()
CsvApplication.init_db()
CsvApplication.main()

View File

@ -1,33 +0,0 @@
"""Script for initializing movie databases."""
import os
import csv
import sqlite3
import shutil
from settings import DATA_DIR
from settings import MOVIES_CSV_PATH
from settings import MOVIES_DB_PATH
MOVIES = (('The Hunger Games: Mockingjay - Part 2', 2015, 'Francis Lawrence'),
('The 33', 2015, 'Patricia Riggen'),
('Star Wars: Episode VII - The Force Awakens', 2015, 'JJ Abrams'))
if __name__ == '__main__':
# (Re)create data directory:
if os.path.exists(DATA_DIR):
shutil.rmtree(DATA_DIR)
os.makedirs(DATA_DIR)
# Initialize sqlite database:
connection = sqlite3.connect(MOVIES_DB_PATH)
with connection:
connection.execute('CREATE TABLE movies '
'(name text, year int, director text)')
connection.executemany('INSERT INTO movies VALUES (?,?,?)', MOVIES)
# Initialize csv database:
with open(MOVIES_CSV_PATH, 'w') as csv_file:
csv.writer(csv_file).writerows(MOVIES)

View File

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

View File

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

View File

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

View File

@ -0,0 +1,17 @@
"""Example main module."""
def main(movie_lister):
"""Main function.
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

@ -12,13 +12,13 @@ concrete finder implementation in terms of library configuration.
Each of ``MoviesModule`` providers could be overridden.
"""
import dependency_injector.containers as containers
import dependency_injector.providers as providers
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."""

View File

@ -33,20 +33,20 @@ class MovieFinder(object):
class CsvMovieFinder(MovieFinder):
"""Movie finder that fetches movies data from csv file."""
def __init__(self, movie_model, csv_file, delimeter):
def __init__(self, movie_model, csv_file_path, delimiter):
"""Initializer.
:param movie_model: Movie model's factory
:type movie_model: movies.models.Movie
:param csv_file: Path to csv file with movies data
:type csv_file: str
:param csv_file_path: Path to csv file with movies data
:type csv_file_path: str
:param delimeter: Csv file's delimeter
:type delimeter: str
:param delimiter: Csv file's delimiter
:type delimiter: str
"""
self._csv_file = csv_file
self._delimeter = delimeter
self._csv_file_path = csv_file_path
self._delimiter = delimiter
super(CsvMovieFinder, self).__init__(movie_model)
def find_all(self):
@ -55,9 +55,9 @@ class CsvMovieFinder(MovieFinder):
:rtype: list[movies.models.Movie]
:return: List of movie instances.
"""
with open(self._csv_file) as csv_file:
reader = csv.reader(csv_file, delimiter=self._delimeter)
return [self._movie_model(*row) for row in reader]
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):

View File

@ -7,8 +7,11 @@ import os
DATA_DIR = os.path.abspath(os.path.dirname(__file__) + '/data')
MOVIES_CSV_PATH = DATA_DIR + '/movies.csv'
MOVIES_CSV_DELIMETER = ','
MOVIES_DB_PATH = DATA_DIR + '/movies.db'
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

@ -5,4 +5,4 @@ Instructions for running
.. code-block:: bash
python main.py
python run.py

View File

@ -2,6 +2,8 @@
import sqlite3
import boto.s3.connection
import example.main
import example.services
import dependency_injector.containers as containers
@ -31,3 +33,12 @@ class Services(containers.DeclarativeContainer):
photos = providers.Factory(example.services.Photos,
db=Platform.database,
s3=Platform.s3)
class Application(containers.DeclarativeContainer):
"""IoC container of application component providers."""
main = providers.Callable(example.main.main,
users_service=Services.users,
auth_service=Services.auth,
photos_service=Services.photos)

View File

@ -5,6 +5,8 @@ Alternative injections definition style #1.
import sqlite3
import boto.s3.connection
import example.main
import example.services
import dependency_injector.containers as containers
@ -35,3 +37,12 @@ class Services(containers.DeclarativeContainer):
photos = providers.Factory(example.services.Photos) \
.add_kwargs(db=Platform.database,
s3=Platform.s3)
class Application(containers.DeclarativeContainer):
"""IoC container of application component providers."""
main = providers.Callable(example.main.main) \
.add_kwargs(users_service=Services.users,
auth_service=Services.auth,
photos_service=Services.photos)

View File

@ -5,6 +5,8 @@ Alternative injections definition style #2.
import sqlite3
import boto.s3.connection
import example.main
import example.services
import dependency_injector.containers as containers
@ -35,3 +37,12 @@ class Services(containers.DeclarativeContainer):
photos = providers.Factory(example.services.Photos)
photos.add_kwargs(db=Platform.database,
s3=Platform.s3)
class Application(containers.DeclarativeContainer):
"""IoC container of application component providers."""
main = providers.Callable(example.main.main)
main.add_kwargs(users_service=Services.users,
auth_service=Services.auth,
photos_service=Services.photos)

View File

@ -0,0 +1,8 @@
"""Example main module."""
def main(users_service, auth_service, photos_service):
"""Example main function."""
user = users_service.get_user('user')
auth_service.authenticate(user, 'secret')
photos_service.upload_photo(user['id'], 'photo.jpg')

View File

@ -1,18 +0,0 @@
"""Dependency Injector @inject decorator example."""
import application
import dependency_injector.injections as injections
@injections.inject(users_service=application.Services.users)
@injections.inject(auth_service=application.Services.auth)
@injections.inject(photos_service=application.Services.photos)
def main(users_service, auth_service, photos_service):
"""Main function."""
user = users_service.get_user('user')
auth_service.authenticate(user, 'secret')
photos_service.upload_photo(user['id'], 'photo.jpg')
if __name__ == '__main__':
main()

View File

@ -0,0 +1,19 @@
"""Run example application."""
import containers
if __name__ == '__main__':
containers.Application.main()
# Previous call is an equivalent of next operations:
#
# database = sqlite3.connect(':memory:')
# s3 = boto.s3.connection.S3Connection(aws_access_key_id='KEY',
# aws_secret_access_key='SECRET')
#
# example.main.main(users_service=example.services.Users(db=database),
# auth_service=example.services.Auth(db=database,
# token_ttl=3600),
# photos_service=example.services.Photos(db=database,
# s3=s3))

View File

@ -1,5 +1,7 @@
"""Dependency injector injections unittests."""
import warnings
import unittest2 as unittest
from dependency_injector import injections
@ -176,3 +178,25 @@ class InjectTests(unittest.TestCase):
@injections.inject(arg1=123)
class Test(object):
"""Test class."""
class InjectDeprecationTests(unittest.TestCase):
"""Deprecation of `@inject()` tests."""
def test_deprecation_warning_on_usage(self):
"""Test that DeprecationWarning is produced when `@inject` is used."""
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter('always')
@injections.inject(1)
def _example(arg):
pass
warnings.simplefilter('default')
self.assertEquals(len(caught_warnings), 1)
self.assertEquals(caught_warnings[-1].category, DeprecationWarning)
self.assertIn('Call to a deprecated decorator',
str(caught_warnings[-1].message))
self.assertIn('@dependency_injector.injections.inject',
str(caught_warnings[-1].message))