Skip to content

value object

Value object gives you the way to execute code during dependency injection process.

If dependency injection process needs to resolve value object, a decorated function will be called. Result of it's execution will be used as resolved dependency under value object name. Returned value treated as is without further processing. Classes returned by @value decorated function would be be used as classes. It will not be treated as something we need instantiate.

Value object will be stored in the injection scope under the same name as decorated function.

Arguments of the decorated function will be treated as direct dependencies of the @value object. Each argument will be resolved from the same injection scope where value object was defined.

Example

>>> from dependencies import Injector, value

>>> class Calc:
...     def __init__(self, result):
...         self.result = result

>>> class Container(Injector):
...     calc = Calc
...     foo = 1
...     bar = 2
...     baz = 3
...
...     @value
...     def result(foo, bar, baz):
...         return foo + bar + baz

>>> Container.calc.result
6

As you can see, foo, bar, and baz were resolved from the Container injection scope. After that quiz function was called with resolved dependencies passed to its arguments. Result of the function was stored in the injection scope under the same quiz name. We received value from quiz name from the injection scope.

Depending on the value

>>> class Foo:
...     def __init__(self, quiz):
...         self.quiz = quiz

>>> class Bar:
...     def __init__(self, quiz):
...         self.quiz = quiz

>>> class Baz:
...     def __init__(self, foo, bar):
...         self.foo = foo
...         self.bar = bar

>>> class Container(Injector):
...     foo = Foo
...     bar = Bar
...     baz = Baz
...
...     @value
...     def quiz():
...         print('value executed')
...         return 3

>>> baz = Container.baz
value executed

>>> baz.foo.quiz
3

>>> baz.bar.quiz
3

You may depend on the @value object value the same way you depend on regular instances of the class.

In the example above Foo and Bar depends on quiz name. Value object would be resolved to 3 (result returned by decorated function) and this value would be injected in corresponding argument of Foo and Bar constructors.

As you can see @value decorated function was called only once even two objects depends on it.

Singletons

The singleton pattern is considered a bad practice in general. But in some cases you would actually need to use them. For example, if you instantiate connection pool for you database, message queue, or cache.

In that case you could use functools.lru_cache together with @value decorator.

Let assume you would like to instantiate your view class each time you need to handle a separate HTTP request. If your view class would depend on connection pool, it would not be recreated each time you want to process the request.

Nice thing about this approach - you don't need to make connection pool a singleton itself. This logic would kept inside Injector subclass, which would make pool class a little bit more testable.

>>> from functools import lru_cache

>>> class Connections:
...     """Connection pool."""
...     def __init__(self, host, port):
...         ...
...
...     def connect(self):
...         ...

>>> class Request:
...     def __init__(self, connections):
...         self.connections = connections

>>> class Container(Injector):
...     request = Request
...     host = "localhost"
...     port = 1234
...
...     @value
...     @lru_cache
...     def connections(host, port):
...         pool = Connections(host, port)
...         pool.connect()
...         return port

>>> request1 = Container.request

>>> request2 = Container.request

>>> request1 is request2
False

>>> request1.connections is request2.connections
True

— ⭐ —