Created
July 31, 2012 08:43
-
-
Save timcowlishaw/3215075 to your computer and use it in GitHub Desktop.
Simple Dependency Injection in Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Container: | |
| def __init__(self): | |
| self.factories = {} | |
| self.instances = {} | |
| def add(self, feature, implementation): | |
| if callable(implementation): | |
| self.factories[feature] = implementation | |
| else: | |
| self.instances[feature] = implementation | |
| def __getitem__(self,feature): | |
| try: | |
| instance = self.instances[feature] | |
| except KeyError: | |
| try: | |
| instance = self.instances[feature] = self.factories[feature]() | |
| except KeyError: | |
| raise KeyError, "No implementation registered for feature '%s'" % feature | |
| return instance | |
| def depends(features, container): | |
| def wrap(cls): | |
| orig_init = cls.__init__ | |
| def new_init(self): | |
| deps = [container[feature] for feature in features] | |
| orig_init(self, *deps) | |
| cls.__init__ = new_init | |
| return cls; | |
| return wrap | |
| def provides(features, container): | |
| def wrap(cls): | |
| for feature in features: | |
| container.add(feature, cls); | |
| return cls; | |
| return wrap |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from unittest import TestCase | |
| from mock import MagicMock, sentinel | |
| from container import Container, depends, provides | |
| class TestContainer(TestCase): | |
| def test_it_exposes_callables_passed_in_as_a_factory(self): | |
| container = Container() | |
| instance = sentinel | |
| callable = lambda : instance | |
| container.add('callable', callable) | |
| self.assertEqual(instance, container['callable']) | |
| def test_it_exposes_non_callables_passed_in_as_singletons(self): | |
| container = Container() | |
| instance = sentinel | |
| container.add("non-callable", instance) | |
| self.assertEqual(instance, container['non-callable']) | |
| def test_it_memoizes_created_instances_from_factories(self): | |
| container = Container() | |
| instance = sentinel | |
| factory = MagicMock(return_value=instance) | |
| container.add('factory', factory) | |
| first = container['factory'] | |
| second = container['factory'] | |
| factory.assert_called_once | |
| def test_it_raises_an_error_if_no_implementation_is_available(self): | |
| container = Container() | |
| with self.assertRaises(KeyError) as context: | |
| container['non-existent'] | |
| self.assertEqual("No implementation registered for feature 'non-existent'", context.exception.message) | |
| class TestProvides(TestCase): | |
| def test_it_adds_decorated_class_to_container_as_a_factory(self): | |
| container = Container() | |
| @provides(features=['factory'], container=container) | |
| class Factory: | |
| def __init__(self): | |
| pass | |
| self.assertIsInstance(container['factory'], Factory) | |
| class TestDepends(TestCase): | |
| def test_it_constructs_decorated_class_with_implementations_of_dependencies_from_container(self): | |
| container = Container() | |
| instance = sentinel | |
| container.add('dependency', sentinel) | |
| receiver = MagicMock() | |
| @depends(features=['dependency'], container=container) | |
| class Dependency(): | |
| def __init__(self, dep): | |
| receiver(dep) | |
| created = Dependency() | |
| receiver.assert_called_with(instance) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment