dic

Registration

In order to create a dependency container, components must be registered within a dic.container.ContainerBuilder. The container builder controls how components will be resolved. Consider a builder as a ‘spec’ for how the container should be built.

Some key points:

  1. The order of registration does not matter
  2. A container builder may build multiple containers, each of which will be independent

Registering Classes

Core to dic is class registration. dic uses Python 3 annotations to provide ‘hints’ for the types of dependencies that components require. The annotations provide a ‘compile safe’ way of referencing types without creating any hard dependencies on the container, or other dic types.

class OtherDependency(object):
    pass

class MyClass(object):
    def __init__(self, dependency: OtherDependency):
        pass

builder = dic.container.ContainerBuilder()
builder.register_class(MyClass)
builder.register_class(OtherDependency)

container = builder.build()
# use the container

A dic.container.DependencyResolutionError will be raised during a .resolve(...) call if an annotation is provided, but no component registered.

Registering Instances

An already-created dependency can be registered directly. This is useful if you’re integrating with other projects, or migrating to dic.

class MyExternalThing(object):
    pass

instance = MyExternalThing()

builder = dic.container.ContainerBuilder()
builder.register_instance(MyExternalThing, instance)

container = builder.build()
# use the container

Note that:

  1. Scopes do not apply to components registered in this way
  2. Aliases can still be specified with the register_as argument

Custom Registration

Lastly, a callback can be provided to create components in any way you want. This provides a good way of integrating things that don’t play nicely with dic, or components that you don’t have control of.

  1. Scopes are respected
  2. Aliases can still be specified with the register_as argument

3. The callback is called with a component context that can be used to resolve other dependencies. Do not store this context, as it can only be used for the scope of the context callback.

class OtherThing(object):
    pass

class MySpecialThing(object):
    def __init__(self, other_thing):
        pass

def create_my_thing(component_context):
    return MyExternalThing(component_context.resolve(OtherThing))

builder = dic.container.ContainerBuilder()
builder.register_class(OtherThing)

builder.register_callback(MySpecialThing, create_my_thing)
# or as a lambda
# builder.register_callback(MySpecialThing, lambda context: MySpecialThing(context.resolve(OtherThing))

container = builder.build()
# use the container

Aliases (register_as)

It’s possible to register callbacks and classes under multiple types. This is useful if you want a specialised implementation available as its base class.

If register_as isn’t specified, then the type of the given component will be used instead. register_as can be:

  1. A list; or
  2. A tuple; or
  3. A single item
class BaseDependency(object):
    pass

class SpecialDependency(BaseDependency):
    pass

class MyClass(object):
    def __init__(self, dependency: BaseDependency):
        pass

builder = dic.container.ContainerBuilder()
builder.register_class(MyClass)
builder.register_class(SpecialDependency, register_as=BaseDependency)

# or available as both:
# builder.register_class(SpecialDependency, register_as=(BaseDependency, SpecialDependency))

container = builder.build()
# use the container

Technically any python object can be used as an alias, but to keep things simple and “self documenting” only types are recommended.

Modules

Modules are simple classes that help provide clarity when building the container. To use them, derive from dic.container.Module and register the instance of the module when building the container. For example:

class Filesystem(object):
    pass

class WindowsFilesystem(Filesystem):
    pass

class DefaultFilesystem(Filesystem):
    pass

class FilesystemModule(dic.container.Module):
    def load(self, builder):
        if os.name == 'nt':
            builder.register_class(WindowsFilesystem, register_as=[Filesystem])
        else:
            builder.register_class(DefaultFilesystem, register_as=[Filesystem])

# building the container now has none of this logic
builder = dic.container.ContainerBuilder()
builder.register_module(FilesystemModule())

container = builder.build()

fs = container.resolve(Filesystem)

Scopes

Scopes model how long resolved components should live for.

Instance Per Dependency (Default)

The default scope is to create a new instance each time the component is resolved.

class ManyOfThese(object):
    pass

builder = dic.container.ContainerBuilder()
# this is the default, but shows how the scope can be set
builder.register_class(ManyOfThese, component_scope=dic.scope.InstancePerDependency)

container = builder.build()
# use the container

Single Instance

Models a ‘singleton’, no matter how many times the component is resolved, only one instance will be created.

class OneOfThese(object):
    pass

builder = dic.container.ContainerBuilder()
builder.register_class(OneOfThese, component_scope=dic.scope.SingleInstance)

container = builder.build()
# use the container
only_one = container.resolve(OneOfThese)
other_only_one = container.resolve(OneOfThese)

# only_one is the same instance as other_only_one

Custom Scopes

Scopes are highly extensible, it’s possible to create new scopes by deriving from dic.scope.Scope.

For example, a scope that creates a dependency per calling thread may look like:

class ThreadingScope(dic.scope.Scope):
    def __init__(self)
        # thread -> instance
        self._instances = {}
        self._scope_lock = threading.RLock()

    def instance(self, create_function):
        with self._scope_lock:
            thread_id = threading.current_thread().ident
            if thread_id not in self.instances:
                self._instances[thread_id] = create_function()
            return self._instances[thread_id]


# use the scope
builder = dic.container.ContainerBuilder()
builder.register_class(MyClass, component_scope=ThreadingScope)
# ...

Note that the above is a sample. The instances will live beyond the threads.

Relationships

Relationships are ‘special’ dependencies that model something more complex than A depends on B. As you’d expect, scopes are respected.

Factory

A dic.rel.Factory relationship can be used when you want to be able to create dependencies from a class, without depending on the dic container directly.

For example:

class Part(object):
    pass

class BuildsStuff(object):
    def __init__(self, part_factory: dic.rel.Factory(Part))
        self.part_factory = part_factory

    def make_me_one(self):
        return self.part_factory()

Custom Arguments

It’s possible to specify custom arguments when invoking a factory, the arguments will be used:

  1. When the dependency isn’t registered in the container; or
  2. To override a resolve operation for something that is registered in the container

An example of the first:

class Part(object):
    def __init__(self, name):
        self.name = name

class BuildsStuff(object):
    def __init__(self, part_factory: dic.rel.Factory(Part))
        self.part_factory = part_factory

    def make_me_one_with_a_name(self, name):
        return self.part_factory(name=name)

# can then resolve just BuildsStuff

Lazy

A dic.rel.Lazy relationship can be used where you want a component, but not yet. For example for breaking circular resolve dependencies. Lazy is implemented in a thread-safe way, so multiple threads will get the same instance.

class EventuallyNeeded(object):
    def do_it(self):
        pass

class EventuallyWantsYou(object):
    def __init__(self, eventually_needed: dic.rel.Lazy(EventuallyNeeded)):
        self.eventually_needed = eventually_needed

    def ok_ready(self, name):
        # EventuallyNeeded will be created here (rather than directly injected in to the constructor)
        self.eventually_needed.value.do_it()

Resolving

Once a dic.container.Container has been built via the container builder, it’s ready for use. Generally you’ll resolve your application ‘bootstrapper’ after building the container.

Components can be directly resolved from the container:

class MyClass(object):
    pass

builder = dic.container.ContainerBuilder()
builder.register_class(MyClass)

container = builder.build()

# factory to create MyClass
factory = container.resolve(dic.rel.Factory(MyClass))

# or instances
instance = container.resolve(MyClass)

Circular Dependencies

dic does not yet have ‘circular dependency’ detection yet, this means if a relationship like this is resolved it will likely crash.

Thread Safety

dic.container.Container.resolve() is thread-safe. Also see registration for implications with the callback resolve function .register_callback().

Samples

dic includes several samples, see the github repository.

CherryPy Songs

Extending the CherryPy songs tutorial:

# Example of dic based on the CherryPy songs tutorial
# See https://cherrypy.readthedocs.org/en/3.3.0/tutorial/REST.html#getting-started

import cherrypy
import dic


class Song(object):
    def __init__(self, title, artist):
        self.title = title
        self.artist = artist

    def __repr__(self):
        return 'title: %s, artist: %s' % (self.title, self.artist)


class SongDatabase(object):
    def __init__(self, song_factory: dic.rel.Factory(Song)):
        self.song_factory = song_factory
        self.songs = {}

    def add_song(self, ident, title, artist):
        self.songs[ident] = self.song_factory(title=title, artist=artist)

    def __getitem__(self, ident):
        return self.songs[ident]

    def __str__(self):
        return self.songs.__str__()


class Songs:
    exposed = True

    def __init__(self, song_database: SongDatabase):
        self.songs = song_database
        self.songs.add_song('1', 'Lumberjack Song', 'Canadian Guard Choir')
        self.songs.add_song('2', 'Always Look On the Bright Side of Life', 'Eric Idle')
        self.songs.add_song('3', 'Spam Spam Spam', 'Monty Python')

    def GET(self, id=None):

        if id is None:
            return('Here are all the songs we have: %s' % self.songs)
        elif id in self.songs:
            song = self.songs[id]

            return(
                'Song with the ID %s is called %s, and the artist is %s' % (
                    id, song.title, song.artist))
        else:
            return('No song with the ID %s :-(' % id)


if __name__ == '__main__':
    builder = dic.container.ContainerBuilder()
    builder.register_class(Songs)
    builder.register_class(SongDatabase)
    builder.register_class(Song)

    container = builder.build()

    cherrypy.tree.mount(
        container.resolve(Songs), '/api/songs',
        {'/':
            {'request.dispatch': cherrypy.dispatch.MethodDispatcher()}
         }
    )

    cherrypy.engine.start()
    cherrypy.engine.block()

Mocking

dic wouldn’t be very useful if it didn’t help you test your code. A full example of using dic to inject mocks during tests:

This sample shows: 1. Mocking the database from a service 2. Using the new Python 3.3 mocking library (``unittest.mock`)

# Shows how unittest.mock can be used with dic to mock components during testing

import dic
import unittest, unittest.mock


class Database(object):
    def __init__(self):
        self.data = {
            '1': 'some data',
            '2': 'other data',
        }

    def get_data(self):
        return self.data


class Service(object):
    def __init__(self, database: Database):
        self.database = database

    def load(self):
        return self.database.get_data()


class ServiceTestCase(unittest.TestCase):
    """
    Test case for `Service` that mocks out the database.
    """
    def setUp(self):
        builder = dic.container.ContainerBuilder()
        builder.register_class(Service)

        # register a mock instead of the real database
        self.database_mock = unittest.mock.Mock(spec=Database)
        builder.register_instance(Database, self.database_mock)
        container = builder.build()

        self.service = container.resolve(Service)

    def test_get_data(self):
        # Arrange
        attrs = {'get_data.return_value': {'3': 'mocked data'}}
        self.database_mock.configure_mock(**attrs)

        # Act
        self.service.load()

        # Assert
        self.database_mock.get_data.called_once_with()

if __name__ == '__main__':
    unittest.main()

API

dic.container module

class dic.container.Container(registry_map)

Bases: builtins.object

IoC container.

resolve(component_type, **kwargs)

Resolves an instance of the component type. :param component_type: The type of the component (e.g. a class). :param kwargs: Overriding arguments to use (by name) instead of resolving them. :return: An instance of the component.

class dic.container.ContainerBuilder

Bases: builtins.object

Builds a container from the registered configuration.

build()

Builds a new container using the registered components. :return: A container

register_callback(class_type, callback, component_scope=<class 'dic.scope.InstancePerDependency'>, register_as=None)

Registers the given class for creation via the given callback. :param class_type: The class type. :param callback: The function to call to create/get an instance, of the form fn(component_context) :param component_scope: The scope of the component, defaults to instance per dependency. :param register_as: The types to register the class as, defaults to the given class_type.

register_class(class_type, component_scope=<class 'dic.scope.InstancePerDependency'>, register_as=None)

Registers the given class for creation via its constructor. :param class_type: The class type. :param component_scope: The scope of the component, defaults to instance per dependency. :param register_as: The types to register the class as, defaults to the given class_type.

register_instance(class_type, instance, register_as=None)

Registers the given instance (already created). :param class_type: The class type. :param instance: The instance to register. :param register_as: The types to register the class as, defaults to the given class_type.

register_module(module)

Registers the module instance. :param module: The module to register.

exception dic.container.DependencyResolutionError

Bases: builtins.Exception

class dic.container.Module

Bases: builtins.object

Module to help structure building of the container.

load(builder)

Register dependencies from this module. :param builder: The container builder to register components to.

dic.rel module

class dic.rel.Factory(component_type)

Bases: dic.rel.Relationship

Models a factory relationship capable of creating the given type. The scope of the registered component is respected. Meaning a SingleInstance registration will return the same instance for multiple factory calls.

Overriding arguments can be provided.

resolve(container)
class dic.rel.Lazy(component_type)

Bases: dic.rel.Relationship

Models a lazy relationship. Dependency lookup is delayed until the value is resolved for the first time. Lazy is thread-safe, so only one instance will be resolved if two threads ask at the same time.

resolve(container)
class dic.rel.Relationship

Bases: builtins.object

resolve(container)

Called when the relationship is resolved. E.g. about to be injected.

dic.scope module

class dic.scope.InstancePerDependency

Bases: dic.scope.Scope

Creates an instance per dependency

instance(create_function)
class dic.scope.Scope

Bases: builtins.object

Controls the lifetime scope of a component registration.

instance(create_function)

Gets the instance of the component given its registration. :param create_function: The function to create a new component, if required. :return: The instance

class dic.scope.SingleInstance

Bases: dic.scope.Scope

instance(create_function)

Module contents