Add DependenciesContainer provider

This commit is contained in:
Roman Mogylatov 2018-01-19 22:25:09 +02:00
parent af28cb60c2
commit 926f716ce6
14 changed files with 6035 additions and 3749 deletions

View File

@ -0,0 +1,8 @@
Dependency Injector Bundles example
===================================
Instructions for running
.. code-block:: bash
python run.py

View File

@ -0,0 +1 @@
"""Bundles package."""

View File

@ -0,0 +1,19 @@
"""Photos bundle."""
from dependency_injector import containers
from dependency_injector import providers
from . import entities
from . import repositories
class Photos(containers.DeclarativeContainer):
"""Photos bundle container."""
core = providers.DependenciesContainer()
photo = providers.Factory(entities.Photo)
photo_repository = providers.Singleton(repositories.PhotoRepository,
object_factory=photo.provider,
fs=core.file_storage,
db=core.database)

View File

@ -0,0 +1,5 @@
"""Photos bundle entities module."""
class Photo(object):
"""Photo entity."""

View File

@ -0,0 +1,11 @@
"""Photos bundle entity repositories module."""
class PhotoRepository(object):
"""Photo entity repository."""
def __init__(self, object_factory, fs, db):
"""Initializer."""
self.object_factory = object_factory
self.fs = fs
self.db = db

View File

@ -0,0 +1,18 @@
"""Users bundle."""
from dependency_injector import containers
from dependency_injector import providers
from . import entities
from . import repositories
class Users(containers.DeclarativeContainer):
"""Users bundle container."""
core = providers.DependenciesContainer()
user = providers.Factory(entities.User)
user_repository = providers.Singleton(repositories.UserRepository,
object_factory=user.provider,
db=core.database)

View File

@ -0,0 +1,9 @@
"""Users bundle entities module."""
class User(object):
"""User entity."""
def __init__(self, id):
"""Initializer."""
self.id = id

View File

@ -0,0 +1,14 @@
"""Users bundle entity repositories module."""
class UserRepository(object):
"""User entity repository."""
def __init__(self, object_factory, db):
"""Initializer."""
self.object_factory = object_factory
self.db = db
def get(self, id):
"""Return user entity with given identifier."""
return self.object_factory(id=id)

View File

@ -0,0 +1,41 @@
"""Run 'Bundles' example application."""
import sqlite3
import boto3
from dependency_injector import containers
from dependency_injector import providers
from bundles.users import Users
from bundles.photos import Photos
class Core(containers.DeclarativeContainer):
"""Core container."""
config = providers.Configuration('config')
database = providers.Singleton(sqlite3.connect, config.database.dsn)
file_storage = providers.Singleton(
boto3.client, 's3',
aws_access_key_id=config.aws.access_key_id,
aws_secret_access_key=config.aws.secret_access_key)
if __name__ == '__main__':
# Initializing containers
core = Core()
core.config.update({'database': {'dsn': ':memory:'},
'aws': {'access_key_id': 'KEY',
'secret_access_key': 'SECRET'}})
users = Users(core=core)
photos = Photos(core=core)
# Fetching few users
user_repository = users.user_repository()
user1 = user_repository.get(id=1)
user2 = user_repository.get(id=2)
# Making some checks
assert user1.id == 1
assert user2.id == 2
assert user_repository.db is core.database()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@ cdef class Provider(object):
cdef object __overriding_lock
cpdef object _provide(self, tuple args, dict kwargs)
cpdef void _copy_overridings(self, Provider copied)
cdef class Object(Provider):
@ -33,6 +34,12 @@ cdef class ExternalDependency(Dependency):
pass
cdef class DependenciesContainer(Object):
cdef dict __providers
cpdef object _override_providers(self, object container)
cdef class OverridingContext(object):
cdef Provider __overridden
cdef Provider __overriding

View File

@ -107,8 +107,7 @@ cdef class Provider(object):
copied = self.__class__()
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -215,6 +214,11 @@ cdef class Provider(object):
"""
raise NotImplementedError()
cpdef void _copy_overridings(self, Provider copied):
"""Copy provider overridings to a newly copied provider."""
copied.__overridden = deepcopy(self.__overridden)
copied.__last_overriding = deepcopy(self.__last_overriding)
cdef class Object(Provider):
"""Object provider returns provided instance "as is".
@ -243,8 +247,7 @@ cdef class Object(Provider):
copied = self.__class__(deepcopy(self.__provides, memo))
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -330,8 +333,7 @@ cdef class Dependency(Provider):
copied = self.__class__(self.__instance_of)
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -418,6 +420,121 @@ cdef class ExternalDependency(Dependency):
"""
cdef class DependenciesContainer(Object):
""":py:class:`DependenciesContainer` provider provides set of dependencies.
Dependencies container provider is used to implement late static binding
for a set of providers of a particular container.
Example code:
.. code-block:: python
class Adapters(containers.DeclarativeContainer):
email_sender = providers.Singleton(SmtpEmailSender)
class TestAdapters(containers.DeclarativeContainer):
email_sender = providers.Singleton(EchoEmailSender)
class UseCases(containers.DeclarativeContainer):
adapters = providers.DependenciesContainer()
signup = providers.Factory(SignupUseCase,
email_sender=adapters.email_sender)
use_cases = UseCases(adapters=Adapters)
# or
use_cases = UseCases(adapters=TestAdapters)
# Another file
from .containers import use_cases
use_case = use_cases.signup()
use_case.execute()
"""
def __init__(self, provides=None, **dependencies):
"""Initializer."""
self.__providers = dependencies
if provides:
self._override_providers(container=provides)
super(DependenciesContainer, self).__init__(provides)
def __deepcopy__(self, memo):
"""Create and return full copy of provider."""
cdef DependenciesContainer copied
copied = memo.get(id(self))
if copied is not None:
return copied
copied = self.__class__()
copied.__provides = deepcopy(self.__provides, memo)
copied.__providers = deepcopy(self.__providers, memo)
self._copy_overridings(copied)
return copied
def __getattr__(self, name):
"""Return dependency provider."""
if name.startswith('__') and name.endswith('__'):
raise AttributeError(
'\'{cls}\' object has no attribute '
'\'{attribute_name}\''.format(cls=self.__class__.__name__,
attribute_name=name))
provider = self.__providers.get(name)
if not provider:
provider = Dependency()
self.__providers[name] = provider
container = self.__call__()
if container:
dependency_provider = container.providers.get(name)
if dependency_provider:
provider.override(dependency_provider)
return provider
@property
def providers(self):
"""Read-only dictionary of dependency providers."""
return self.__providers
def override(self, provider):
"""Override provider with another provider.
:param provider: Overriding provider.
:type provider: :py:class:`Provider`
:raise: :py:exc:`dependency_injector.errors.Error`
:return: Overriding context.
:rtype: :py:class:`OverridingContext`
"""
self._override_providers(container=provider)
return super(DependenciesContainer, self).override(provider)
cpdef object _override_providers(self, object container):
"""Override providers with providers from provided container."""
for name, dependency_provider in container.providers.items():
provider = self.__providers.get(name)
if not provider:
continue
if provider.last_overriding is dependency_provider:
continue
provider.override(dependency_provider)
cdef class OverridingContext(object):
"""Provider overriding context.
@ -517,8 +634,7 @@ cdef class Callable(Provider):
*deepcopy(self.args, memo),
**deepcopy(self.kwargs, memo))
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -755,8 +871,7 @@ cdef class Configuration(Provider):
copied.__value = deepcopy(self.__value, memo)
copied.__children = deepcopy(self.__children, memo)
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -923,8 +1038,7 @@ cdef class Factory(Provider):
**deepcopy(self.kwargs, memo))
copied.set_attributes(**deepcopy(self.attributes, memo))
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -1272,8 +1386,7 @@ cdef class BaseSingleton(Provider):
**deepcopy(self.kwargs, memo))
copied.set_attributes(**deepcopy(self.attributes, memo))
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied

View File

@ -3,6 +3,7 @@
import unittest2 as unittest
from dependency_injector import (
containers,
providers,
errors,
)
@ -298,3 +299,52 @@ class ExternalDependencyTests(unittest.TestCase):
def test_is_instance(self):
self.assertIsInstance(self.provider, providers.Dependency)
class DependenciesContainerTests(unittest.TestCase):
class Container(containers.DeclarativeContainer):
dependency = providers.Provider()
def setUp(self):
self.provider = providers.DependenciesContainer()
self.container = self.Container()
def test_getattr(self):
has_dependency = hasattr(self.provider, 'dependency')
dependency = self.provider.dependency
self.assertIsInstance(dependency, providers.Dependency)
self.assertIs(dependency, self.provider.dependency)
self.assertTrue(has_dependency)
self.assertIsNone(dependency.last_overriding)
def test_getattr_with_container(self):
self.provider.override(self.container)
dependency = self.provider.dependency
self.assertTrue(dependency.overridden)
self.assertIs(dependency.last_overriding, self.container.dependency)
def test_providers(self):
dependency1 = self.provider.dependency1
dependency2 = self.provider.dependency2
self.assertEqual(self.provider.providers, {'dependency1': dependency1,
'dependency2': dependency2})
def test_override(self):
dependency = self.provider.dependency
self.provider.override(self.container)
self.assertTrue(dependency.overridden)
self.assertIs(dependency.last_overriding, self.container.dependency)
def test_init_with_container_and_providers(self):
provider = providers.DependenciesContainer(
self.container, dependency=providers.Dependency())
dependency = provider.dependency
self.assertTrue(dependency.overridden)
self.assertIs(dependency.last_overriding, self.container.dependency)