mirror of
				https://github.com/ets-labs/python-dependency-injector.git
				synced 2025-10-30 23:47:40 +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