Add FAQ and good/bad practices to the docs
This commit is contained in:
parent
c4a1354dc0
commit
6730306841
|
@ -0,0 +1,29 @@
|
||||||
|
.. _faq:
|
||||||
|
|
||||||
|
Frequently Asked Questions
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* If I use :func:`~injector.inject` or scope decorators on my classess will
|
||||||
|
I be able to create instances of them without using Injector?
|
||||||
|
|
||||||
|
Yes. Scope decorators don't change the way you can construct your class
|
||||||
|
instances without Injector interaction.
|
||||||
|
|
||||||
|
:func:`~injector.inject` changes the constructor semantics slightly
|
||||||
|
if you use it to decorate your class - in this case you need to use
|
||||||
|
keyword arguments to pass values to the constructor.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@inject(s=str)
|
||||||
|
class X(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# will fail
|
||||||
|
X('a')
|
||||||
|
|
||||||
|
# will work
|
||||||
|
X(s='a')
|
|
@ -30,3 +30,5 @@ Contents:
|
||||||
scopes
|
scopes
|
||||||
logging
|
logging
|
||||||
api
|
api
|
||||||
|
faq
|
||||||
|
practices
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
.. _practices:
|
||||||
|
|
||||||
|
Good and bad practices
|
||||||
|
======================
|
||||||
|
|
||||||
|
Side effects
|
||||||
|
````````````
|
||||||
|
|
||||||
|
You should avoid creating side effects in your modules for two reasons:
|
||||||
|
|
||||||
|
* Side effects will make it more difficult to test a module if you want to do it
|
||||||
|
* Modules expose a way to acquire some resource but they don't provide any way
|
||||||
|
to release it. If, for example, your module connects to a remote server while
|
||||||
|
creating a service you have no way of closing that connection unless the
|
||||||
|
service exposes it.
|
||||||
|
|
||||||
|
|
||||||
|
Injecting into constructors vs injecting into other methods
|
||||||
|
```````````````````````````````````````````````````````````
|
||||||
|
|
||||||
|
In general you should prefer injecting into constructors to injecting into
|
||||||
|
other methods because:
|
||||||
|
|
||||||
|
* it can expose potential issues earlier (at object construction time rather
|
||||||
|
than at the method call)
|
||||||
|
* it exposes class' dependencies more openly. Constructor injection:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Service1(object):
|
||||||
|
@inject(http_client=HTTP)
|
||||||
|
def __init__(self, http_client):
|
||||||
|
self.http_client = http_client
|
||||||
|
# some other code
|
||||||
|
|
||||||
|
# tens or hundreds lines of code
|
||||||
|
|
||||||
|
def method(self):
|
||||||
|
# do something
|
||||||
|
pass
|
||||||
|
|
||||||
|
Regular method injection:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Service2(object):
|
||||||
|
def __init__(self):
|
||||||
|
# some other code
|
||||||
|
|
||||||
|
# tens or hundreds lines of code
|
||||||
|
|
||||||
|
@inject(http_client=HTTP)
|
||||||
|
def method(self, http_client):
|
||||||
|
# do something
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
In first case you know all the dependencies by looking at the class'
|
||||||
|
constructor, in the second you don't know about ``HTTP`` dependency until
|
||||||
|
you see the method definition.
|
||||||
|
|
||||||
|
Slightly different approach is suggested when it comes to Injector modules -
|
||||||
|
in this case injecting into their constructors (or ``configure`` methods)
|
||||||
|
would make the injection process dependent on the order of passing modules
|
||||||
|
to Injector and therefore quite fragile. See this code sample:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
A = Key('A')
|
||||||
|
B = Key('B')
|
||||||
|
|
||||||
|
class ModuleA(Module):
|
||||||
|
@inject(a=A)
|
||||||
|
def configure(self, binder, a):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ModuleB(Module):
|
||||||
|
@inject(b=B)
|
||||||
|
def __init__(self, b):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ModuleC(Module):
|
||||||
|
def configure(self, binder):
|
||||||
|
binder.bind(A, to='a')
|
||||||
|
binder.bind(B, to='b')
|
||||||
|
|
||||||
|
|
||||||
|
# error, at the time of ModuleA processing A is unbound
|
||||||
|
Injector([ModuleA, ModuleC])
|
||||||
|
|
||||||
|
# error, at the time of ModuleB processing B is unbound
|
||||||
|
Injector([ModuleB, ModuleC])
|
||||||
|
|
||||||
|
# no error this time
|
||||||
|
Injector([ModuleC, ModuleA, ModuleB])
|
||||||
|
|
||||||
|
|
||||||
|
Doing too much in modules and/or providers
|
||||||
|
``````````````````````````````````````````
|
||||||
|
|
||||||
|
An implementation detail of Injector: Injector and accompanying classes are
|
||||||
|
protected by a lock to make them thread safe. This has a downside though:
|
||||||
|
in general only one thread can use dependency injection at any given moment.
|
||||||
|
|
||||||
|
In best case scenario you "only" slow other threads' dependency injection
|
||||||
|
down. In worst case scenario (performing blocking calls without timeouts) you
|
||||||
|
can **deadlock** whole application.
|
||||||
|
|
||||||
|
**It is advised to avoid performing any IO, particularly without a timeout
|
||||||
|
set, inside modules code.**
|
||||||
|
|
||||||
|
As an illustration:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from threading import Thread
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from injector import inject, Injector, Key, Module, provides
|
||||||
|
|
||||||
|
SubA = Key('SubA')
|
||||||
|
A = Key('A')
|
||||||
|
B = Key('B')
|
||||||
|
|
||||||
|
|
||||||
|
class BadModule(Module):
|
||||||
|
@provides(A)
|
||||||
|
@inject(suba=SubA)
|
||||||
|
def provide_a(self, suba):
|
||||||
|
return suba
|
||||||
|
|
||||||
|
@provides(SubA)
|
||||||
|
def provide_suba(self):
|
||||||
|
print('Providing SubA...')
|
||||||
|
while True:
|
||||||
|
print('Sleeping...')
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
# This never executes
|
||||||
|
return 'suba'
|
||||||
|
|
||||||
|
@provides(B)
|
||||||
|
def provide_b(self):
|
||||||
|
return 'b'
|
||||||
|
|
||||||
|
|
||||||
|
injector = Injector([BadModule])
|
||||||
|
|
||||||
|
thread = Thread(target=lambda: injector.get(A))
|
||||||
|
|
||||||
|
# to make sure the thread doesn't keep the application alive
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
# This will never finish
|
||||||
|
injector.get(B)
|
||||||
|
print('Got B')
|
||||||
|
|
||||||
|
|
||||||
|
Here's the output of the application::
|
||||||
|
|
||||||
|
Providing SubA...
|
||||||
|
Sleeping...
|
||||||
|
Sleeping...
|
||||||
|
Sleeping...
|
||||||
|
(...)
|
Loading…
Reference in New Issue