mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 17:47:02 +03:00
Merge pull request #174 from ets-labs/feature/factory-aggregate
Create FactoryAggregate provider
This commit is contained in:
commit
9305b9db7d
|
@ -9,8 +9,9 @@ follows `Semantic versioning`_
|
||||||
|
|
||||||
Development version
|
Development version
|
||||||
-------------------
|
-------------------
|
||||||
|
- Add ``FactoryAggregate`` provider.
|
||||||
- Add support of six 1.11.0.
|
- Add support of six 1.11.0.
|
||||||
- Regenerate C sources using Cython 0.27.
|
- Regenerate C sources using Cython 0.27.1.
|
||||||
|
|
||||||
3.6.1
|
3.6.1
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -147,4 +147,38 @@ Listing of ``example.py``:
|
||||||
:language: python
|
:language: python
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
|
Factory aggregate providers
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
:py:class:`FactoryAggregate` provider is a special type of provider that
|
||||||
|
aggregates other :py:class:`Factory` providers.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
:py:class:`FactoryAggregate` is not overridable. Calling of
|
||||||
|
:py:meth:`FactoryAggregate.override` will result in raising of an
|
||||||
|
expection.
|
||||||
|
|
||||||
|
Next prototype might be the best demonstration of
|
||||||
|
:py:class:`FactoryAggregate` features:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/factory_aggregate/prototype.py
|
||||||
|
:language: python
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
Example below shows one of the :py:class:`FactoryAggregate` use cases, when
|
||||||
|
concrete implementation (game) must be selected based on dynamic input (CLI).
|
||||||
|
|
||||||
|
Listing of ``games.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/factory_aggregate/games.py
|
||||||
|
:language: python
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
Listing of ``example.py``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/factory_aggregate/example.py
|
||||||
|
:language: python
|
||||||
|
:linenos:
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
29
examples/providers/factory_aggregate/example.py
Normal file
29
examples/providers/factory_aggregate/example.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
"""`FactoryAggregate` providers example."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import dependency_injector.providers as providers
|
||||||
|
|
||||||
|
from games import Chess, Checkers, Ludo
|
||||||
|
|
||||||
|
|
||||||
|
game_factory = providers.FactoryAggregate(chess=providers.Factory(Chess),
|
||||||
|
checkers=providers.Factory(Checkers),
|
||||||
|
ludo=providers.Factory(Ludo))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
game_type = sys.argv[1].lower()
|
||||||
|
player1 = sys.argv[2].capitalize()
|
||||||
|
player2 = sys.argv[3].capitalize()
|
||||||
|
|
||||||
|
selected_game = game_factory(game_type, player1, player2)
|
||||||
|
selected_game.play()
|
||||||
|
|
||||||
|
# $ python example.py chess John Jane
|
||||||
|
# John and Jane are playing chess
|
||||||
|
#
|
||||||
|
# $ python example.py checkers John Jane
|
||||||
|
# John and Jane are playing checkers
|
||||||
|
#
|
||||||
|
# $ python example.py ludo John Jane
|
||||||
|
# John and Jane are playing ludo
|
27
examples/providers/factory_aggregate/games.py
Normal file
27
examples/providers/factory_aggregate/games.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
"""Example games module."""
|
||||||
|
|
||||||
|
|
||||||
|
class Game(object):
|
||||||
|
"""Base game class."""
|
||||||
|
|
||||||
|
def __init__(self, player1, player2):
|
||||||
|
"""Initializer."""
|
||||||
|
self.player1 = player1
|
||||||
|
self.player2 = player2
|
||||||
|
|
||||||
|
def play(self):
|
||||||
|
"""Play game."""
|
||||||
|
print('{0} and {1} are playing {2}'.format(
|
||||||
|
self.player1, self.player2, self.__class__.__name__.lower()))
|
||||||
|
|
||||||
|
|
||||||
|
class Chess(Game):
|
||||||
|
"""Chess game."""
|
||||||
|
|
||||||
|
|
||||||
|
class Checkers(Game):
|
||||||
|
"""Checkers game."""
|
||||||
|
|
||||||
|
|
||||||
|
class Ludo(Game):
|
||||||
|
"""Ludo game."""
|
17
examples/providers/factory_aggregate/prototype.py
Normal file
17
examples/providers/factory_aggregate/prototype.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
"""FactoryAggregate provider prototype."""
|
||||||
|
|
||||||
|
|
||||||
|
class FactoryAggregate(object):
|
||||||
|
"""FactoryAggregate provider prototype."""
|
||||||
|
|
||||||
|
def __init__(self, **factories):
|
||||||
|
"""Initializer."""
|
||||||
|
self.factories = factories
|
||||||
|
|
||||||
|
def __call__(self, factory_name, *args, **kwargs):
|
||||||
|
"""Create object."""
|
||||||
|
return self.factories[factory_name](*args, **kwargs)
|
||||||
|
|
||||||
|
def __getattr__(self, factory_name):
|
||||||
|
"""Return factory with specified name."""
|
||||||
|
return self.factories[factory_name]
|
|
@ -1,4 +1,4 @@
|
||||||
cython==0.27
|
cython==0.27.1
|
||||||
tox
|
tox
|
||||||
unittest2
|
unittest2
|
||||||
coverage
|
coverage
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,3 +6,7 @@ class Error(Exception):
|
||||||
|
|
||||||
All dependency injector errors extend this error class.
|
All dependency injector errors extend this error class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchProviderError(Error, AttributeError):
|
||||||
|
"""Error that is raised when provider lookup is failed."""
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -93,6 +93,12 @@ cdef class FactoryDelegate(Delegate):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
cdef class FactoryAggregate(Provider):
|
||||||
|
cdef dict __factories
|
||||||
|
|
||||||
|
cdef Factory __get_factory(self, str factory_name)
|
||||||
|
|
||||||
|
|
||||||
# Singleton providers
|
# Singleton providers
|
||||||
cdef class BaseSingleton(Provider):
|
cdef class BaseSingleton(Provider):
|
||||||
cdef Factory __instantiator
|
cdef Factory __instantiator
|
||||||
|
|
|
@ -9,7 +9,10 @@ import sys
|
||||||
import types
|
import types
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .errors import Error
|
from .errors import (
|
||||||
|
Error,
|
||||||
|
NoSuchProviderError,
|
||||||
|
)
|
||||||
|
|
||||||
cimport cython
|
cimport cython
|
||||||
|
|
||||||
|
@ -1111,6 +1114,81 @@ cdef class FactoryDelegate(Delegate):
|
||||||
super(Delegate, self).__init__(factory)
|
super(Delegate, self).__init__(factory)
|
||||||
|
|
||||||
|
|
||||||
|
cdef class FactoryAggregate(Provider):
|
||||||
|
"""Factory providers aggregate.
|
||||||
|
|
||||||
|
:py:class:`FactoryAggregate` is an aggregate of :py:class:`Factory`
|
||||||
|
providers.
|
||||||
|
|
||||||
|
:py:class:`FactoryAggregate` is a delegated provider, meaning that it is
|
||||||
|
injected "as is".
|
||||||
|
|
||||||
|
All aggregated factories could be retrieved as a read-only
|
||||||
|
dictionary :py:attr:`FactoryAggregate.factories` or just as an attribute of
|
||||||
|
:py:class:`FactoryAggregate`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__IS_DELEGATED__ = True
|
||||||
|
|
||||||
|
def __init__(self, **factories):
|
||||||
|
"""Initializer.
|
||||||
|
|
||||||
|
:param factories: Dictionary of aggregate factories.
|
||||||
|
:type factories: dict[str, :py:class:`Factory`]
|
||||||
|
"""
|
||||||
|
for factory in factories.values():
|
||||||
|
if isinstance(factory, Factory) is False:
|
||||||
|
raise Error(
|
||||||
|
'{0} can aggregate only instances of {1}, given - {2}'
|
||||||
|
.format(self.__class__, Factory, factory))
|
||||||
|
self.__factories = factories
|
||||||
|
super(FactoryAggregate, self).__init__()
|
||||||
|
|
||||||
|
def __call__(self, factory_name, *args, **kwargs):
|
||||||
|
"""Create new object using factory with provided name.
|
||||||
|
|
||||||
|
Callable interface implementation.
|
||||||
|
"""
|
||||||
|
return self.__get_factory(factory_name)(*args, **kwargs)
|
||||||
|
|
||||||
|
def __getattr__(self, factory_name):
|
||||||
|
"""Return aggregated factory."""
|
||||||
|
return self.__get_factory(factory_name)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Return string representation of provider.
|
||||||
|
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return represent_provider(provider=self, provides=self.factories)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def factories(self):
|
||||||
|
"""Return dictionary of factories, read-only."""
|
||||||
|
return self.__factories
|
||||||
|
|
||||||
|
def override(self, _):
|
||||||
|
"""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`
|
||||||
|
"""
|
||||||
|
raise Error(
|
||||||
|
'{0} providers could not be overridden'.format(self.__class__))
|
||||||
|
|
||||||
|
cdef Factory __get_factory(self, str factory_name):
|
||||||
|
if factory_name not in self.__factories:
|
||||||
|
raise NoSuchProviderError(
|
||||||
|
'{0} does not contain factory with name {1}'.format(
|
||||||
|
self, factory_name))
|
||||||
|
return <Factory> self.__factories[factory_name]
|
||||||
|
|
||||||
|
|
||||||
cdef class BaseSingleton(Provider):
|
cdef class BaseSingleton(Provider):
|
||||||
"""Base class of singleton providers."""
|
"""Base class of singleton providers."""
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
[pydocstyle]
|
[pydocstyle]
|
||||||
ignore = D101,D102
|
ignore = D101,D102,D106,D107
|
||||||
|
|
|
@ -414,3 +414,77 @@ class FactoryDelegateTests(unittest.TestCase):
|
||||||
self.assertRaises(errors.Error,
|
self.assertRaises(errors.Error,
|
||||||
providers.FactoryDelegate,
|
providers.FactoryDelegate,
|
||||||
providers.Object(object()))
|
providers.Object(object()))
|
||||||
|
|
||||||
|
|
||||||
|
class FactoryAggregateTests(unittest.TestCase):
|
||||||
|
|
||||||
|
class ExampleA(Example):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ExampleB(Example):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.example_a_factory = providers.Factory(self.ExampleA)
|
||||||
|
self.example_b_factory = providers.Factory(self.ExampleB)
|
||||||
|
self.factory_aggregate = providers.FactoryAggregate(
|
||||||
|
example_a=self.example_a_factory,
|
||||||
|
example_b=self.example_b_factory)
|
||||||
|
|
||||||
|
def test_is_provider(self):
|
||||||
|
self.assertTrue(providers.is_provider(self.factory_aggregate))
|
||||||
|
|
||||||
|
def test_is_delegated_provider(self):
|
||||||
|
self.assertTrue(providers.is_delegated(self.factory_aggregate))
|
||||||
|
|
||||||
|
def test_init_with_not_a_factory(self):
|
||||||
|
with self.assertRaises(errors.Error):
|
||||||
|
providers.FactoryAggregate(
|
||||||
|
example_a=providers.Factory(self.ExampleA),
|
||||||
|
example_b=object())
|
||||||
|
|
||||||
|
def test_call(self):
|
||||||
|
object_a = self.factory_aggregate('example_a',
|
||||||
|
1, 2, init_arg3=3, init_arg4=4)
|
||||||
|
object_b = self.factory_aggregate('example_b',
|
||||||
|
11, 22, init_arg3=33, init_arg4=44)
|
||||||
|
|
||||||
|
self.assertIsInstance(object_a, self.ExampleA)
|
||||||
|
self.assertEqual(object_a.init_arg1, 1)
|
||||||
|
self.assertEqual(object_a.init_arg2, 2)
|
||||||
|
self.assertEqual(object_a.init_arg3, 3)
|
||||||
|
self.assertEqual(object_a.init_arg4, 4)
|
||||||
|
|
||||||
|
self.assertIsInstance(object_b, self.ExampleB)
|
||||||
|
self.assertEqual(object_b.init_arg1, 11)
|
||||||
|
self.assertEqual(object_b.init_arg2, 22)
|
||||||
|
self.assertEqual(object_b.init_arg3, 33)
|
||||||
|
self.assertEqual(object_b.init_arg4, 44)
|
||||||
|
|
||||||
|
def test_call_no_such_provider(self):
|
||||||
|
with self.assertRaises(errors.NoSuchProviderError):
|
||||||
|
self.factory_aggregate('unknown')
|
||||||
|
|
||||||
|
def test_overridden(self):
|
||||||
|
with self.assertRaises(errors.Error):
|
||||||
|
self.factory_aggregate.override(providers.Object(object()))
|
||||||
|
|
||||||
|
def test_getattr(self):
|
||||||
|
self.assertIs(self.factory_aggregate.example_a, self.example_a_factory)
|
||||||
|
self.assertIs(self.factory_aggregate.example_b, self.example_b_factory)
|
||||||
|
|
||||||
|
def test_getattr_no_such_provider(self):
|
||||||
|
with self.assertRaises(errors.NoSuchProviderError):
|
||||||
|
self.factory_aggregate.unknown
|
||||||
|
|
||||||
|
def test_factories(self):
|
||||||
|
self.assertDictEqual(self.factory_aggregate.factories,
|
||||||
|
dict(example_a=self.example_a_factory,
|
||||||
|
example_b=self.example_b_factory))
|
||||||
|
|
||||||
|
def test_repr(self):
|
||||||
|
self.assertEqual(repr(self.factory_aggregate),
|
||||||
|
'<dependency_injector.providers.'
|
||||||
|
'FactoryAggregate({0}) at {1}>'.format(
|
||||||
|
repr(self.factory_aggregate.factories),
|
||||||
|
hex(id(self.factory_aggregate))))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user