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
|
||||
logging
|
||||
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