README.rst -> README.md
This commit is contained in:
parent
e3f9c29462
commit
ee44d60a68
|
@ -1,36 +1,35 @@
|
|||
Injector - Python dependency injection framework, inspired by Guice
|
||||
######################################################################
|
||||
# Injector - Python dependency injection framework, inspired by Guice
|
||||
|
||||
.. image:: https://secure.travis-ci.org/alecthomas/injector.png?branch=master
|
||||
:target: https://travis-ci.org/alecthomas/injector
|
||||
![image](https://secure.travis-ci.org/alecthomas/injector.png?branch=master)
|
||||
|
||||
Introduction
|
||||
============
|
||||
## Introduction
|
||||
|
||||
Dependency injection as a formal pattern is less useful in Python than in other
|
||||
languages, primarily due to its support for keyword arguments, the ease with
|
||||
which objects can be mocked, and its dynamic nature.
|
||||
|
||||
Dependency injection as a formal pattern is less useful in Python than
|
||||
in other languages, primarily due to its support for keyword arguments,
|
||||
the ease with which objects can be mocked, and its dynamic nature.
|
||||
|
||||
That said, a framework for assisting in this process can remove a lot of
|
||||
boiler-plate from larger applications. That's where Injector can help. It
|
||||
automatically and transitively provides keyword arguments with their values. As
|
||||
an added benefit, Injector encourages nicely compartmentalised code through the
|
||||
use of ``Module`` s.
|
||||
boiler-plate from larger applications. That's where Injector can help.
|
||||
It automatically and transitively provides keyword arguments with their
|
||||
values. As an added benefit, Injector encourages nicely
|
||||
compartmentalised code through the use of `Module` s.
|
||||
|
||||
While being inspired by Guice, it does not slavishly replicate its API.
|
||||
Providing a Pythonic API trumps faithfulness.
|
||||
|
||||
A Full Example
|
||||
==============
|
||||
Here's a full example to give you a taste of how Injector works::
|
||||
## A Full Example
|
||||
|
||||
Here's a full example to give you a taste of how Injector works:
|
||||
|
||||
>>> from injector import Module, Key, provides, Injector, inject, singleton
|
||||
|
||||
We'll use an in-memory SQLite database for our example::
|
||||
We'll use an in-memory SQLite database for our example:
|
||||
|
||||
>>> import sqlite3
|
||||
|
||||
And make up an imaginary RequestHandler class that uses the SQLite connection::
|
||||
And make up an imaginary RequestHandler class that uses the SQLite
|
||||
connection:
|
||||
|
||||
>>> class RequestHandler(object):
|
||||
... @inject(db=sqlite3.Connection)
|
||||
|
@ -41,8 +40,8 @@ And make up an imaginary RequestHandler class that uses the SQLite connection::
|
|||
... cursor.execute('SELECT key, value FROM data ORDER by key')
|
||||
... return cursor.fetchall()
|
||||
|
||||
Next, for the sake of the example, we'll create a "configuration" annotated
|
||||
type::
|
||||
Next, for the sake of the example, we'll create a "configuration"
|
||||
annotated type:
|
||||
|
||||
>>> Configuration = Key('configuration')
|
||||
>>> class ConfigurationForTestingModule(Module):
|
||||
|
@ -51,8 +50,8 @@ type::
|
|||
... scope=singleton)
|
||||
|
||||
Next we create our database module that initialises the DB based on the
|
||||
configuration provided by the above module, populates it with some dummy data,
|
||||
and provides a Connection object::
|
||||
configuration provided by the above module, populates it with some dummy
|
||||
data, and provides a Connection object:
|
||||
|
||||
>>> class DatabaseModule(Module):
|
||||
... @singleton
|
||||
|
@ -65,110 +64,113 @@ and provides a Connection object::
|
|||
... cursor.execute('INSERT OR REPLACE INTO data VALUES ("hello", "world")')
|
||||
... return conn
|
||||
|
||||
(Note how we have decoupled configuration from our database initialisation
|
||||
code.)
|
||||
(Note how we have decoupled configuration from our database
|
||||
initialisation code.)
|
||||
|
||||
Finally, we initialise an Injector and use it to instantiate a RequestHandler
|
||||
instance. This first transitively constructs a sqlite3.Connection object, and the
|
||||
Configuration dictionary that it in turn requires, then instantiates our
|
||||
RequestHandler::
|
||||
Finally, we initialise an Injector and use it to instantiate a
|
||||
RequestHandler instance. This first transitively constructs a
|
||||
sqlite3.Connection object, and the Configuration dictionary that it in
|
||||
turn requires, then instantiates our RequestHandler:
|
||||
|
||||
>>> injector = Injector([ConfigurationForTestingModule(), DatabaseModule()])
|
||||
>>> handler = injector.get(RequestHandler)
|
||||
>>> tuple(map(str, handler.get()[0])) # py3/py2 compatibility hack
|
||||
('hello', 'world')
|
||||
|
||||
We can also veryify that our Configuration and SQLite connections are indeed
|
||||
singletons within the Injector::
|
||||
We can also veryify that our Configuration and SQLite connections are
|
||||
indeed singletons within the Injector:
|
||||
|
||||
>>> injector.get(Configuration) is injector.get(Configuration)
|
||||
True
|
||||
>>> injector.get(sqlite3.Connection) is injector.get(sqlite3.Connection)
|
||||
True
|
||||
|
||||
You're probably thinking something like: "this is a large amount of work just
|
||||
to give me a database connection", and you are correct; dependency injection is
|
||||
typically not that useful for smaller projects. It comes into its own on large
|
||||
projects where the up-front effort pays for itself in two ways:
|
||||
You're probably thinking something like: "this is a large amount of work
|
||||
just to give me a database connection", and you are correct; dependency
|
||||
injection is typically not that useful for smaller projects. It comes
|
||||
into its own on large projects where the up-front effort pays for itself
|
||||
in two ways:
|
||||
|
||||
1. Forces decoupling. In our example, this is illustrated by decoupling
|
||||
our configuration and database configuration.
|
||||
2. After a type is configured, it can be injected anywhere with no
|
||||
additional effort. Simply @inject and it appears. We don't really
|
||||
illustrate that here, but you can imagine adding an arbitrary number of
|
||||
RequestHandler subclasses, all of which will automatically have a DB
|
||||
connection provided.
|
||||
> 1. Forces decoupling. In our example, this is illustrated by
|
||||
> decoupling our configuration and database configuration.
|
||||
> 2. After a type is configured, it can be injected anywhere with no
|
||||
> additional effort. Simply @inject and it appears. We don't really
|
||||
> illustrate that here, but you can imagine adding an arbitrary
|
||||
> number of RequestHandler subclasses, all of which will
|
||||
> automatically have a DB connection provided.
|
||||
|
||||
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::
|
||||
## 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
|
||||
--------
|
||||
### Provider
|
||||
|
||||
A means of providing an instance of a type. Built-in providers include
|
||||
``ClassProvider`` (creates a new instance from a class),
|
||||
``InstanceProvider`` (returns an existing instance directly) and
|
||||
``CallableProvider`` (provides an instance by calling a function).
|
||||
`ClassProvider` (creates a new instance from a class),
|
||||
`InstanceProvider` (returns an existing instance directly) and
|
||||
`CallableProvider` (provides an instance by calling a function).
|
||||
|
||||
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.
|
||||
### Scope
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
The default scope is ``NoScope``.
|
||||
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.
|
||||
|
||||
Binding Key
|
||||
-----------
|
||||
A binding key uniquely identifies a provider of a type. It is effectively a
|
||||
tuple of ``(type, annotation)`` where ``type`` is the type to be provided and
|
||||
``annotation`` is additional, optional, uniquely identifying information for
|
||||
the type.
|
||||
The default scope is `NoScope`.
|
||||
|
||||
For example, the following are all unique binding keys for ``str``::
|
||||
### Binding Key
|
||||
|
||||
A binding key uniquely identifies a provider of a type. It is
|
||||
effectively a tuple of `(type, annotation)` where `type` is the type to
|
||||
be provided and `annotation` is additional, optional, uniquely
|
||||
identifying information for the type.
|
||||
|
||||
For example, the following are all unique binding keys for `str`:
|
||||
|
||||
(str, 'name')
|
||||
(str, 'description')
|
||||
|
||||
For a generic type such as ``str``, annotations are very useful for unique
|
||||
For a generic type such as `str`, annotations are very useful for unique
|
||||
identification.
|
||||
|
||||
As an *alternative* convenience to using annotations, ``Key`` may be used
|
||||
to create unique types as necessary::
|
||||
As an *alternative* convenience to using annotations, `Key` may be used
|
||||
to create unique types as necessary:
|
||||
|
||||
>>> from injector import Key
|
||||
>>> Name = Key('name')
|
||||
>>> Description = Key('description')
|
||||
|
||||
Which may then be used as binding keys, without annotations, as they already
|
||||
uniquely identify a particular provider::
|
||||
Which may then be used as binding keys, without annotations, as they
|
||||
already uniquely identify a particular provider:
|
||||
|
||||
(Name, None)
|
||||
(Description, None)
|
||||
|
||||
Though of course, annotations may still be used with these types, like any
|
||||
other type.
|
||||
Though of course, annotations may still be used with these types, like
|
||||
any other type.
|
||||
|
||||
Annotation
|
||||
----------
|
||||
An annotation is additional unique information about a type to avoid binding
|
||||
key collisions. It creates a new unique binding key for an existing type.
|
||||
### Annotation
|
||||
|
||||
Binding
|
||||
-------
|
||||
A binding is the mapping of a unique binding key to a corresponding provider.
|
||||
For example::
|
||||
An annotation is additional unique information about a type to avoid
|
||||
binding key collisions. It creates a new unique binding key for an
|
||||
existing type.
|
||||
|
||||
### Binding
|
||||
|
||||
A binding is the mapping of a unique binding key to a corresponding
|
||||
provider. For example:
|
||||
|
||||
>>> from injector import InstanceProvider
|
||||
>>> bindings = {
|
||||
|
@ -176,17 +178,17 @@ For example::
|
|||
... (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.
|
||||
### Binder
|
||||
|
||||
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::
|
||||
The `Binder` is simply a convenient wrapper around the dictionary that
|
||||
maps types to providers. It provides methods that make declaring
|
||||
bindings easier.
|
||||
|
||||
### 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):
|
||||
|
@ -195,7 +197,7 @@ created with::
|
|||
... binder.bind(Description, to='A man of astounding insight')
|
||||
|
||||
For more complex instance construction, methods decorated with
|
||||
``@provides`` will be called to resolve binding keys::
|
||||
`@provides` will be called to resolve binding keys:
|
||||
|
||||
>>> from injector import provides
|
||||
>>> class MyModule(Module):
|
||||
|
@ -206,15 +208,15 @@ For more complex instance construction, methods decorated with
|
|||
... def describe(self):
|
||||
... 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.
|
||||
### 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::
|
||||
constructor of a normal class:
|
||||
|
||||
>>> from injector import inject
|
||||
>>> class User(object):
|
||||
|
@ -236,28 +238,29 @@ constructor of a normal class::
|
|||
... def describe(self, name):
|
||||
... 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::
|
||||
### 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::
|
||||
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::
|
||||
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::
|
||||
Or transitively:
|
||||
|
||||
>>> user = injector.get(User)
|
||||
>>> isinstance(user, User)
|
||||
|
@ -267,19 +270,19 @@ Or transitively::
|
|||
>>> user.description
|
||||
'Sherlock is a man of astounding insight'
|
||||
|
||||
Scopes
|
||||
======
|
||||
## Scopes
|
||||
|
||||
Singletons
|
||||
----------
|
||||
Singletons are declared by binding them in the SingletonScope. This can be done
|
||||
in three ways:
|
||||
### Singletons
|
||||
|
||||
1. Decorating the class with ``@singleton``.
|
||||
2. Decorating a ``@provides(X)`` decorated Module method with ``@singleton``.
|
||||
3. Explicitly calling ``binder.bind(X, scope=singleton)``.
|
||||
Singletons are declared by binding them in the SingletonScope. This can
|
||||
be done in three ways:
|
||||
|
||||
A (redunant) example showing all three methods::
|
||||
> 1. Decorating the class with `@singleton`.
|
||||
> 2. Decorating a `@provides(X)` decorated Module method with
|
||||
> `@singleton`.
|
||||
> 3. Explicitly calling `binder.bind(X, scope=singleton)`.
|
||||
|
||||
A (redunant) example showing all three methods:
|
||||
|
||||
>>> @singleton
|
||||
... class Thing(object): pass
|
||||
|
@ -291,59 +294,56 @@ A (redunant) example showing all three methods::
|
|||
... def provide_thing(self):
|
||||
... return Thing()
|
||||
|
||||
### Implementing new Scopes
|
||||
|
||||
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``::
|
||||
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::
|
||||
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
|
||||
> \>\>\> @customscope ... class MyClass(object): ... pass
|
||||
|
||||
Scopes are bound in modules with the ``Binder.bind_scope`` method::
|
||||
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::
|
||||
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.
|
||||
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.
|
||||
|
||||
Tests
|
||||
=====
|
||||
## Tests
|
||||
|
||||
When you use unit test framework such as ``unittest2`` or ``nose`` you can also
|
||||
profit from ``injector``. However, manually creating injectors and test classes
|
||||
can be quite annoying. There is, however, ``with_injector`` method decorator which
|
||||
has parameters just as ``Injector`` construtor and installes configured injector into
|
||||
class instance on the time of method call::
|
||||
When you use unit test framework such as `unittest2` or `nose` you can
|
||||
also profit from `injector`. However, manually creating injectors and
|
||||
test classes can be quite annoying. There is, however, `with_injector`
|
||||
method decorator which has parameters just as `Injector` construtor and
|
||||
installs configured injector into class instance on the time of method
|
||||
call:
|
||||
|
||||
>>> from injector import Module, with_injector
|
||||
>>> class UsernameModule(Module):
|
||||
|
@ -359,16 +359,15 @@ class instance on the time of method call::
|
|||
... def test_username(self, username):
|
||||
... assert (username == 'Maria')
|
||||
|
||||
*Each* method call re-initializes ``Injector`` - if you want to you can also put
|
||||
``with_injector`` decorator on class constructor.
|
||||
*Each* method call re-initializes `Injector` - if you want to you can
|
||||
also put `@with_injector` decorator on class constructor.
|
||||
|
||||
After such call all ``inject``-decorated methods will work just as you'd expect
|
||||
them to work.
|
||||
After such call all `inject`-decorated methods will work just as you'd
|
||||
expect them to work.
|
||||
|
||||
## Footnote
|
||||
|
||||
Footnote
|
||||
========
|
||||
This framework is similar to snake-guice, but aims for simplification.
|
||||
|
||||
:copyright: (c) 2010 by Alec Thomas
|
||||
:license: BSD
|
||||
*Injector is © 2010 by Alec Thomas available under the Modified BSD License.*
|
||||
|
Loading…
Reference in New Issue