Scopes

Singletons

Singletons are declared by binding them in the SingletonScope. This can be done in three ways:

  1. Decorating the class with @singleton.
  2. Decorating a @provider decorated Module method with @singleton.
  3. Explicitly calling binder.bind(X, scope=singleton).

A (redundant) example showing all three methods:

@singleton
class Thing(object): pass
class ThingModule(Module):
    def configure(self, binder):
        binder.bind(Thing, scope=singleton)
    @singleton
    @provider
    def provide_thing(self) -> Thing:
        return Thing()

Implementing new Scopes

In the above description of scopes, we glossed over a lot of detail. In particular, how one would go about implementing our own scopes.

Basically, there are two steps. First, subclass Scope and implement Scope.get:

from injector import Scope
class CustomScope(Scope):
    def get(self, key, provider):
        return provider

Then create a global instance of ScopeDecorator to allow classes to be easily annotated with your scope:

from injector import ScopeDecorator
customscope = ScopeDecorator(CustomScope)

This can be used like so:

@customscope
class MyClass(object):
    pass

Scopes are bound in modules with the Binder.bind_scope() method:

class MyModule(Module):
    def configure(self, binder):
        binder.bind_scope(CustomScope)

Scopes can be retrieved from the injector, as with any other instance. They are singletons across the life of the injector:

>>> injector = Injector([MyModule()])
>>> injector.get(CustomScope) is injector.get(CustomScope)
True

For scopes with a transient lifetime, such as those tied to HTTP requests, the usual solution is to use a thread or greenlet-local cache inside the scope. The scope is “entered” in some low-level code by calling a method on the scope instance that creates this cache. Once the request is complete, the scope is “left” and the cache cleared.