diff --git a/README.rst b/README.md similarity index 54% rename from README.rst rename to README.md index 7dbbc82..0e8b32c 100644 --- a/README.rst +++ b/README.md @@ -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.*