File: terminology.rst

package info (click to toggle)
python-injector 0.21.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 304 kB
  • sloc: python: 1,908; makefile: 146
file content (229 lines) | stat: -rw-r--r-- 8,425 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
Terminology
===========

At its heart, Injector is simply a dictionary for mapping types to things that create instances of those types. This could be as simple as::

    {str: 'an instance of a string'}

For those new to dependency-injection and/or Guice, though, some of the terminology used may not be obvious.

Provider
````````

A means of providing an instance of a type. Built-in providers include:

* :class:`~injector.ClassProvider` - creates a new instance from a class
* :class:`~injector.InstanceProvider` - returns an existing instance directly
* :class:`~injector.CallableProvider` - provides an instance by calling a function

In order to create custom provider you need to subclass :class:`~injector.Provider` and override its :meth:`~injector.Provider.get` method.

Scope
`````

By default, providers are executed each time an instance is required. Scopes allow this behaviour to be customised. For example, `SingletonScope` (typically used through the class decorator `singleton`), can be used to always provide the same instance of a class.

Other examples of where scopes might be a threading scope, where instances are provided per-thread, or a request scope, where instances are provided per-HTTP-request.

The default scope is :class:`NoScope`.

.. seealso:: :ref:`scopes`

Binding
```````

A binding is the mapping of a unique binding key to a corresponding provider. For example::

    >>> from injector import InstanceProvider
    >>> bindings = {
    ...   (Name, None): InstanceProvider('Sherlock'),
    ...   (Description, None): InstanceProvider('A man of astounding insight'),
    ... }


Binder
``````

The `Binder` is simply a convenient wrapper around the dictionary that maps types to providers. It provides methods that make declaring bindings easier.


.. _module:

Module
``````

A `Module` configures bindings. It provides methods that simplify the process of binding a key to a provider. For example the above bindings would be created with::

    >>> from injector import Module
    >>> class MyModule(Module):
    ...     def configure(self, binder):
    ...         binder.bind(Name, to='Sherlock')
    ...         binder.bind(Description, to='A man of astounding insight')

For more complex instance construction, methods decorated with `@provider` will be called to resolve binding keys::

    >>> from injector import provider
    >>> class MyModule(Module):
    ...     def configure(self, binder):
    ...         binder.bind(Name, to='Sherlock')
    ...
    ...     @provider
    ...     def describe(self) -> Description:
    ...         return 'A man of astounding insight (at %s)' % time.time()

Injection
`````````

Injection is the process of providing an instance of a type, to a method that uses that instance. It is achieved with the `inject` decorator. Keyword arguments to inject define which arguments in its decorated method should be injected, and with what.

Here is an example of injection on a module provider method, and on the constructor of a normal class::

    from injector import inject

    class User:
        @inject
        def __init__(self, name: Name, description: Description):
            self.name = name
            self.description = description


    class UserModule(Module):
        def configure(self, binder):
           binder.bind(User)


    class UserAttributeModule(Module):
        def configure(self, binder):
            binder.bind(Name, to='Sherlock')

        @provider
        def describe(self, name: Name) -> Description:
            return '%s is a man of astounding insight' % name


Injector
````````

The `Injector` brings everything together. It takes a list of `Module` s, and configures them with a binder, effectively creating a dependency graph::

    from injector import Injector
    injector = Injector([UserModule(), UserAttributeModule()])

You can also pass classes instead of instances to `Injector`, it will instantiate them for you::

    injector = Injector([UserModule, UserAttributeModule])

The injector can then be used to acquire instances of a type, either directly::

    >>> injector.get(Name)
    'Sherlock'
    >>> injector.get(Description)
    'Sherlock is a man of astounding insight'

Or transitively::

    >>> user = injector.get(User)
    >>> isinstance(user, User)
    True
    >>> user.name
    'Sherlock'
    >>> user.description
    'Sherlock is a man of astounding insight'

Assisted injection
``````````````````

Sometimes there are classes that have injectable and non-injectable parameters in their constructors. Let's have for example::

    class Database: pass


    class User:
        def __init__(self, name):
            self.name = name


    class UserUpdater:
        def __init__(self, db: Database, user):
            pass

You may want to have database connection `db` injected into `UserUpdater` constructor, but in the same time provide `user` object by yourself, and assuming that `user` object is a value object and there's many users in your application it doesn't make much sense to inject objects of class `User`.

In this situation there's technique called Assisted injection::

    from injector import ClassAssistedBuilder
    injector = Injector()
    builder = injector.get(ClassAssistedBuilder[UserUpdater])
    user = User('John')
    user_updater = builder.build(user=user)

This way we don't get `UserUpdater` directly but rather a builder object. Such builder has `build(**kwargs)` method which takes non-injectable parameters, combines them with injectable dependencies of `UserUpdater` and calls `UserUpdater` initializer using all of them.

`AssistedBuilder[T]` and `ClassAssistedBuilder[T]` are injectable just as anything
else, if you need instance of it you just ask for it like that::

    class NeedsUserUpdater:
        @inject
        def __init__(self, builder: ClassAssistedBuilder[UserUpdater]):
            self.updater_builder = builder

        def method(self):
            updater = self.updater_builder.build(user=None)

`ClassAssistedBuilder` means it'll construct a concrete class and no bindings will be used.

If you want to follow bindings and construct class pointed to by a key you use `AssistedBuilder` and can do it like this::

    >>> DB = Key('DB')
    >>> class DBImplementation:
    ...     def __init__(self, uri):
    ...         pass
    ...
    >>> def configure(binder):
    ...     binder.bind(DB, to=DBImplementation)
    ...
    >>> injector = Injector(configure)
    >>> builder = injector.get(AssistedBuilder[DB])
    >>> isinstance(builder.build(uri='x'), DBImplementation)
    True

More information on this topic:

- `"How to use Google Guice to create objects that require parameters?" on Stack Overflow <http://stackoverflow.com/questions/996300/how-to-use-google-guice-to-create-objects-that-require-parameters>`_
- `Google Guice assisted injection <http://code.google.com/p/google-guice/wiki/AssistedInject>`_


Child injectors
```````````````

Concept similar to Guice's child injectors is supported by `Injector`. This way you can have one injector that inherits bindings from other injector (parent) but these bindings can be overriden in it and it doesn't affect parent injector bindings::

    >>> def configure_parent(binder):
    ...     binder.bind(str, to='asd')
    ...     binder.bind(int, to=42)
    ...
    >>> def configure_child(binder):
    ...     binder.bind(str, to='qwe')
    ...
    >>> parent = Injector(configure_parent)
    >>> child = parent.create_child_injector(configure_child)
    >>> parent.get(str), parent.get(int)
    ('asd', 42)
    >>> child.get(str), child.get(int)
    ('qwe', 42)

**Note**: Default scopes are bound only to root injector. Binding them manually to child injectors will result in unexpected behaviour. **Note 2**: Once a binding key is present in parent injector scope (like `singleton` scope), provider saved there takes predecence when binding is overridden in child injector in the same scope. This behaviour is subject to change::


    >>> def configure_parent(binder):
    ...     binder.bind(str, to='asd', scope=singleton)
    ...
    >>> def configure_child(binder):
    ...     binder.bind(str, to='qwe', scope=singleton)
    ...
    >>> parent = Injector(configure_parent)
    >>> child = parent.create_child_injector(configure_child)
    >>> child.get(str) # this behaves as expected
    'qwe'
    >>> parent.get(str) # wat
    'qwe'