23
Pythonic Dependency Injection (py-di) Documentation Release 0.1.1 Lukas Buenger September 19, 2013

Pythonic Dependency Injection (py-di) Documentation · Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1 Python-DI is a smallDependency Injectionlibrary for Python

  • Upload
    doanbao

  • View
    301

  • Download
    0

Embed Size (px)

Citation preview

Pythonic Dependency Injection (py-di)Documentation

Release 0.1.1

Lukas Buenger

September 19, 2013

CONTENTS

1 Contents 31.1 Quick start . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.2 Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.3 Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.4 Package documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

2 Indices and tables 17

Python Module Index 19

i

ii

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

Python-DI is a small Dependency Injection library for Python with a limited set of features, exposing an overall explicitAPI and developed with simplicity and source code readability in mind.

The sources can be found on GitHub, the docs are on Read The Docs. Feel free to open a ticket for any kind of request,report, feedback or question.

Note: If you are not familiar with the Dependency Injection (DI) pattern, I strongly advise you to read:

• This article by Martin Fowler.

• On Wikipedia: About Dependency Injection (DI) and Inversion of Control (IoC).

Warning: This project is still in alpha status. There are going to be breaking changes and test coverage is noteven anywhere near a reasonable ratio.

CONTENTS 1

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

2 CONTENTS

CHAPTER

ONE

CONTENTS

1.1 Quick start

1. Install Python-DI in your Python 3.3+ environment:

pip install python-di

2. Write your application:

class Inner(object):def __init__(self, version:’latest’):

self.version = version

class Outer(object):def __init__(self, inner):

self.inner = inner

3. Create an injector:

from di.injectors import Injector

injector = Injector()

4. Register your dependencies:

injector.bind(’version’).annotated_with(’latest’).to_value(’ 1.1.1’)injector.bind(’inner’).to_class(Inner).as_prototype()

5. Create provided objects:

my_outer = injector.inject_into(Outer)print(my_outer.inner.version)# 1.1.1

Please refer to the !!!Guide!!! for further information on quite everything about Python-DI.

1.2 Guide

1.2.1 Getting started

This section gives more in-depth information on each aspect of Python-DI.

3

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

Requirements

• Python (3.3)

• nose (1.3, to run the tests)

• Sphinx (1.1.3, to build the docs)

Installation

You can install Python-DI from the Package Index:

pip install python-di

Alternatively you could clone the latest release from GitHub and install the package manually using Setuptools:

git clone https://github.com/lukasbuenger/python-di/releases/tag/0.1.1cd python-di# python3 or pythonpython setup.py install

Create your Injector

The Injector is the main entry point for your application. So we create one:

injector = Injector()

There are

1.3 Background

1.3.1 Motivation

Note: The following write up reflects my personal opinion. I don’t (and hopefully never will) consider any of it asthe only truth. Opinions are supposed to change and the more there are, the better an eventual consensus will be. Italso contains traces of humor.

Dependency Injection: A State of the (Python) Union address

Ever since I first came in touch with the Dependency Injection pattern and the ideas behind the IoC concept I’ve beenusing and exploring DI helpers, frameworks and tools for almost any language that I’ve worked or just messed aroundwith. DI immediately stroke and still strikes me as a pattern so simple, yet very genius and powerful.

You find mature and well maintained DI implementations for probably every popular language out there. Just to namea few established ones:

• Google Guice (Java)

• Spring (originally for Java but lots of ports)

• AngularJS (Javascript, also by Google)

• Symfony (PHP)

4 Chapter 1. Contents

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

• Zend Framework (PHP)

• SwiftSuspenders (ActionScript)

• Unity (.NET)

In the Python world however, until very recently at least, there has never been a real main contender. There are some40 odd packages on PyPi addressing DI, but most of them are abandoned. This is probably due to the fact, that thereare a couple of more pythonic ways to achieve decoupling than an injector framework. I would even dare to say thatgood Python code implements simple Dependency Injection anyway.

However, until very recently because last spring (2013) Pinject got released. It’s owned by (what a surprise) Google,though not (yet) an official product and as you’d expect it from a company that is heavily promoting and supportingthe DI pattern, it’s a bomb!

So ...

There is Pinject: Why reinventing the wheel?

There are few but very valid points against using Dependency Injection in general (and this one is even the most validof them all). They mostly address one of the following issues:

• Every DI framework has a performance footprint depending on the framework implementation and the conven-tions (many configuration files, classes etc.) it enforces.

• Usually, Dependency Injection increases complexity as one has to keep track of all the DI processes on top ofthose your app ships with anyway.

• Code depends on a Dependency Injection framework.

While the first point is obviously not circumventable (even with heavy optimizing you’ll have some sort of footprint),the other two can be minimized if not even avoided.

From my experience I came to believe that the complexity increase is more a psychological issue. Of course, the ideasbehind the Dependency Injection pattern may not be the easiest to wrap your head around and the advantages of usingDI in your apps are not that super-obvious. But still I think that the nature of the major frameworks is what keepssome people thinking of Dependency Injection as a possible complexity beast.

Often over-engineered, with docs full of cryptic design decisions, offering thousands of boilerplate-heavy ways to con-figure your bindings, eventually even forcing you into previously unknown config markup territories (XML, YAMLetc.); at first sight frameworks like Spring et al seem to merely complicate things than actually enhancing your devel-opment process.

This leads to common resentments like “This feels so powerful and kind of just right but wow, real engineer stuff,probably two much overhead for my not THAT large-scale app.” which in turn leads a developer to drop DI with thesour feeling of not yet being ready for the world of the real bad-ass programmers which in in turn can lead to the deathby heart attack of another developer, who had to pick up the pieces (which of course implement no DI at all) after thefirst developer decided to leave the Machine, become a public school teacher and never ever try to be bad-ass again.

DI frameworks by Google like AngularJS, Guice and aforementioned Pinject all follow a very concise philosophy andaddress these issues to a certain extent. But they all force you in doing things a certain (at times very specific) way.One could argue that Google ways of things are designed by some of the most skilled people in the business and Icouldn’t agree more, but still I wouldn’t want to be restricted to them.

And I felt somehow restricted with Pinject, especially with its conventions on binding specifications. Simple thingsbecame too complicated too quickly. The API documentation is quite good, but you must find some joy in readingmostly uncommented, tightly formatted code if you want to explore the inner workings of Pinject, which I consideressential in order to understand and therefore safely use any library of choice.

The implicit binding inspection feature even scared me a bit. I had a sudden vision of me banging my head overduplicate constructor argument names within some implicitly declared binding that a co-worker pushed before going

1.3. Background 5

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

to the very same bar I intended to visit before I ran into the bug in the first place. Such things don’t happen at Google,I guess. But to be fair, Pinject’s error handling and raising is exceptionally explicit and therefore brilliant. So this isnever a con, it just doesn’t feel right to me.

And last but not least, Pinject is not (yet) compatible with Python 3 and therefore misses out on some new features,that come in very handy when dealing with DI.

Conclusion

To me Pinject is an inspiring piece of software, because it introduces a set of best practices and utilities that for thefirst time feels really pythonic, is quite accessible and as far as I can tell bullet-proof by any means. By far the best DIlibrary in the Python world. This library is heavily inspired by Pinject itself.

Still, it does enforce some conventions that I consider out of (pragmatism vs. concept) balance and it does promote non-explicit binding specifications by providing implicit bindings, which suggests out-of-the-box magic. In my opinion,these points add significantly to the complexity. And I would call low complexity the main priority of Python-DI.

1.3.2 Core Concepts

Here’s a short description of what Python-DI tries to be:

Important: A Dependency Injection container for Python ...

• ... with a limited set of features.

• ... exposing an overall explicit API.

• ... implemented with focus on simplicity and source code readability.

• ... that, although enforcing and promoting a few very basic rules, can be non-invasively used with any existingcode base.

A limited feature set

In order to be as simple as possible, Python-DI offers the following limited feature set:

• Binding string values, so called binding keys, to either:

– a static value, e.g. version 1.1.

– a function within singleton or prototype scope.

– a class constructor within singleton or prototype scope.

• Annotating binding keys in order to provide the ability to declare dependency on different objects for the samebinding key.

• Injecting dependencies into any function (unbound or bound) or class constructor unless they are built-in.

Exposing an overall explicit API

The client API mainly consists of only three methods:

bind() Register an item with a binding key.

inject_into() Inject all declared dependencies into a function (unbound or bound) or class constructor.

6 Chapter 1. Contents

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

get_instance() Return an object according to the rules of the binding found for a passed binding key.

The API raises exceptions that are explicit and verbose, both in class name and message content.

Read more about this in the !!!Injector!!! section.

Focus on simplicity and source code readability

Python provides us with some extremely powerful tools like Metaprogramming or the Python Decorators that literallycry for being used to implement DI and decoupling, but in order to keep it simple, I avoided using anything exceptstandard OOP techniques. So you won’t have to fully understand any of those (or other) techniques to fully understandPython-DI.

The only not so common concept that Python-DI uses, are Function Annotations. This feature allows to annotatefunction values with any expression:

def foo(an_integer:int, a_string:’Bar’)pass

Python itself does nothing with Function Annotations, but it provides tools like the inspect library for retrieving andanalyzing them. In Python-DI, Function Annotations are used to declare dependency on annotations and optionallyeven binding keys.

Note: As you may have noticed, this last paragraph exposed another possible simplicity leak. For more on that, visitthe !!!Terminology!!! section.

As far as source code readability is concerned, it may be a matter of taste, but I consider well organised and commentedOO code the most painless to read in almost any case.

A few very basic rules

The only domain where Python-DI enforces convention is the Function Signature of functions or constructors that youwant to provide with their dependencies.

• There will be an injection attempt for any explicit positional or keyword argument in the inspected sig-nature.

Given the following code example:

def foo(positional, keyword_or_positional=None, *args, keyword_only, **kwargs):# do something

An injection attempt is made for the arguments positional, keyword_or_positional andkeyword_only. If a binding can’t be found, the injection fails.

• Declaring dependency on annotated bindings enforces the use of string-typed Function Annotations.

Given a factory bound to ‘widget’ annotated with ‘text’. You’d declare dependency on that factorylike so:

def need_text_widget(widget:’text’):# do something with widget

• Declaring dependency on annotations AND bindings enforces the use of string-typed Function Annotationsusing the pattern ANNOTATION_FORMAT

Given:

1.3. Background 7

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

– a factory bound to ‘widget’ annotated with ‘text’.

– a factory bound to ‘widget’ annotated with ‘int’.

– an injector.

You’d declare dependency on those factories like this:

def need_text_widget(text_widget:’widget:text’, int_widget:’widget:int’):# do something with widget

In this case the argument names are ignored and only the Function Annotations get analyzed.

Read more about this in the !!!Declaring dependencies!!! section.

Last but not least, Python-DI “enforces” the use of Python 3.3 or higher.

1.3.3 Terminology

When reading about Dependency Injection, you’ll always come across the same few terms, but the names of commoncomponents within different frameworks do not always relate to their counterparts from theory. At some point youmay interact with an Injector object that injects, whereelse you deal with an ObjectGraph that provides. This can geta little confusing, so here’s a list of terms you’ll find in this documentation and what they mean within the context ofPython-DI.

To inject vs. To provide Python-DI provides an injectee with everything it declared as dependency by injecting thedependencies into the injectee. It injects the dependencies only after it provided them with possible dependen-cies too.

To bind We actually bind an identifier to a provider. By doing so we tell our injector to use this very providerwhenever an injection request for this identifier occurs.

To annotate or Annotation This can mean two different things:

• Annotating a binding lets you have different objects injected for the same identifier. On the other side,you can annotate to a dependency declaration to request the object bound to the annotated identifier.

• Python-DI uses Function Annotations to declare and identify dependencies.

Within this documentation the binding annotations are referred to as annotations and the Python feature isreferred to as Function Annotations.

Singleton The term Singleton in Python-DI does NOT refer to the Singleton pattern, but describes an object that getsinstantiated when first requested and reused for all further requests. Although it might not seem obvious at firstsight, there is a huge difference between the two. Here you’ll find some further discussion on that topic.

Prototype In Python-DI this term describes an item (constructor, function etc.), that acts as blueprint for creatingobjects of a certain type. Whenever an injection of a Prototype is requested, the blueprint is used to create anew object which in turn will get used to respond to the request.

Scope The term Scope describes how and when a requested object gets created and returned. If bound as singleton,an object in question will be created upon first request and reused for all further requests. If bound as prototype,every request will be responded to with a new creation result. A bound item is either in singleton scope or inprototype scope.

Injection Describes the process of inspecting a given item’s (class, function) dependencies and providing it with whatit needs, following the rules defined during binding.

Injection request Describes the process of providing an object with one declared dependency. The injection processconsists of injection requests for each dependency.

8 Chapter 1. Contents

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

Provider The entity (or set of objects) that controls the creation of an object, the scope it should live in and how itshould get returned.

Injector An entity that “contains” the information for injecting dependencies ( Definition on Wikipedia). WithinPython-DI, represented by the Injector class. Additionally, in Python-DI the Injector exposes the mainAPI.

Binding An object that connects an identifier with a provider and delegating given injection requests to the latter.

Factory In this documentation we refer to factories as bound or unbound functions that are not class constructors andreturn a newly created object. This is a very good explanation of the Factory pattern in Python.

DI container A synonym to !!!Injector!!!

1.4 Package documentation

1.4.1 di.injectors

class di.injectors.Injector(settings=None, **lazy_settings)Bases: builtins.object

The Injector is responsible for binding and injecting dependencies.

Parameters

• settings (_Settings) – An optional explicit settings object.

• lazy_settings (dict) – Will get passed to a Settings object if no explicit settings gotpassed.

bind(name)Starts a binding chain.

Parameters name (str) – The name of the binding

Return type Binding

get_instance(key, *args, **kwargs)Returns an item provided by the provider that is bound to key.

Parameters key (str) – The binding key.

Raises BindingNotFound

Raises AnnotationNotFound

Return type

inject_into(item, *args, **kwargs)Injects dependencies into a given item function or class constructor. The following parameters will getprocessed:

•Arguments that can be passed as either positional or keyword(inspect.Parameter.POSITIONAL_OR_KEYWORD)

•Positional only arguments (inspect.Parameter.POSITIONAL_ONLY)

Additionally, overloaded positional and keyword arguments will get merged into the injection parameters(inspect.Parameter.VAR_POSITIONAL and inspect.Parameter.VAR_POSITIONAL re-spectively)

1.4. Package documentation 9

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

Note: Overloaded positionals (*args) and keywords (**kwargs) won’t get passed to nested depen-dencies.

Parameters item (type, types.FunctionType) – A function or class

Raises UnknownParameterKind

Raises InjectionFailed

Return type

1.4.2 di.providers

class di.providers.ClassScopeProxy(injector, item=None)Bases: di.providers.ScopeProxy

A proxy exposing an interface for scope selection when binding classes (types).

Note: At the moment, ClassScopeProxy is just a plain subclass of ScopeProxy with no specific imple-mentations whatsoever. It exists mostly for semantic reasons and with eventual future changes in mind.

class di.providers.FactoryProvider(injector, item=None)Bases: di.providers.Provider

A factory provider. Returns a function that is decorated with injection.

class di.providers.FunctionScopeProxy(injector, item=None)Bases: di.providers.ScopeProxy

A proxy exposing an interface for scope selection when binding function.

Note: At the moment, FunctionScopeProxy is just a plain subclass of ScopeProxy with no specificimplementations whatsoever. It exists mostly for semantic reasons and with eventual future changes in mind.

class di.providers.MainProviderProxy(injector, item=None)Bases: di.providers.ProviderProxy

The main entry point for a binding chain. Exposes factory methods to determine the item type provider.

to_class(item)Sets a ClassScopeProxy object as inner provider that later on should determine the scope the providershould work in.

Parameters item (type) – The item you want to provide for the current binding chain.

Return type ClassScopeProxy

to_function(item)Sets a FunctionScopeProxy object as inner provider that later on should determine the scope theprovider should work in.

Parameters item (types.FunctionType) – The item you want to provide for the currentbinding chain.

Return type FunctionScopeProxy

10 Chapter 1. Contents

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

to_value(item)Sets a ValueProvider object as inner provider.

Parameters item – The item you want to provide for the current binding chain.

Return type ValueProvider

class di.providers.PrototypeProvider(injector, item=None)Bases: di.providers.Provider

A prototype provider. Returns a newly provided item for each request.

provide(*args, **kwargs)Returns a newly created object.

Return type

class di.providers.Provider(injector, item=None)Bases: builtins.object

Basic interface for providers. Providers (or chain of providers) defines the way a binding gets injected uponrequest.

Parameters

• injector (Injector) – The injector hosting the DI.

• item – The item you want to provide.

provide(*args, **kwargs)Provides an object according to configuration and implementation.

Raises NotImplemented

Return type

class di.providers.ProviderProxy(injector, item=None)Bases: di.providers.Provider

A proxy class that delegates provide request to an inner provider object. The ProviderProxy object shouldexpose an interface to set its inner providers.

provide(*args, **kwargs)Delegates the call to its inner provider.

Raises ProviderProxyError

Return type

class di.providers.ScopeProxy(injector, item=None)Bases: di.providers.ProviderProxy

A proxy exposing an interface for scope selection.

as_factory()Sets a FactoryProvider object as inner provider.

Parameters item – The item you want to provide for the current binding chain.

Return type FactoryProvider

1.4. Package documentation 11

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

as_prototype()Sets a PrototypeProvider as inner provider.

as_singleton()Sets a SingletonProvider as inner provider.

class di.providers.SingletonProvider(injector, item=None)Bases: di.providers.Provider

A singleton provider. The item gets provided and when first requested and then cached for further requests.

provide(*args, **kwargs)Please note that no args or kwargswill get passed to the injection process, as it would be a huge liabilityto create a singleton with eventual runtime variables.

Return type

class di.providers.ValueProvider(injector, item=None)Bases: di.providers.Provider

A value provider. Returns a static value.

1.4.3 di.binding_specs

class di.binding_specs.BindingSpecBases: builtins.object

Basic interface for binding specifications.

configure(bind)Should implement binding instructions, e.g.:

bind(’widget’).annotated_with(’text’).to_class(TextWidget).as_prototype()

Parameters bind (types.FunctionType) – A reference to the bind() method of the re-lated context.

1.4.4 di.bindings

di.bindings.ANNOTATION_FORMAT = ‘{name}:{annotation}’Standard binding key format for name/annotation pairs

class di.bindings.Binding(binding_map, name, provider)Bases: builtins.object

Handles binding annotations by providing an extended binding key to the passed injector in case of anannotation request. If not, delegating attributes to provider. In both cases the actual key -> valueassignment happens within the Binding.

Parameters

• binding_map (StrictDict) – A binding map.

• name (str) – The name of the binding that eventually will get annotated.

• provider (Provider) – A provider object that you want to bind to a name/annotation pair.

Raises BindingAlreadyExists

12 Chapter 1. Contents

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

annotated_with(annotation)Extends the binding key with annotation and delegates further provider instructions to (returns) thewrapped provider. Adds

Parameters annotation (str) – An annotation name.

Return type Provider

di.bindings.from_key(key)Returns a tuple (name, annotation) for a key formatted as defined in ANNOTATION_FORMAT, e.g.(’widget’, ’text’) for key ’widget:text’.

Parameters key (str) – A key

Return type tuple

di.bindings.from_param(param, use_argument_names=True)Returns a tuple (name, annotation) for a inspect.Parameter object.

Parameters

• param (inspect.Parameter) – A parameter object

• use_argument_names (bool) – Whether to use the parameter object name or rely on anno-tation only.

Raises InvalidAnnotation

Raises MissingDependencyDeclaration

Return type tuple

di.bindings.get_key(name, annotation=None)Returns the binding key for a name/annotation pair, formatted with ANNOTATION_FORMAT, e.g.’widget:text’.

Parameters

• name (str) – A binding name

• annotation (str) – A binding annotation

Return type str

1.4.5 di.settings

class di.settings.Settings(**kwargs)Bases: builtins.object

A simple settings container. Should hold all values needed for configuring an Injector. As for now, thereare only two:

Parameters

• binding_specs (list of BindingSpec) – A list of binding specs that should be processed.

• use_argument_names (bool) – Whether dependency declaration works with annotationsonly or with argument name / annotation pairs.

binding_specs

Returns A list of BindingSpec objects that should be processed.

use_argument_names

1.4. Package documentation 13

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

Returns bool, whether dependency declaration works with annotations only or with argumentname / annotation pairs.

1.4.6 di.exceptions

exception di.exceptions.AnnotationAlreadyExistsBases: di.exceptions.DIError

Gets raised when a binding with a given annotation already exists.

exception di.exceptions.AnnotationNotFoundBases: di.exceptions.DIError

Gets raised when a binding does exist, but not the annotation in question.

exception di.exceptions.BindingAlreadyExistsBases: di.exceptions.DIError

Gets raised when a a binding tries to map to a key that already exists.

exception di.exceptions.BindingNotFoundBases: di.exceptions.DIError

Gets raised when a dependency declaration has no related binding.

exception di.exceptions.DIErrorBases: builtins.Exception

Base error class

exception di.exceptions.InjectionFailedBases: di.exceptions.DIError

Error during injection. Catches and formats any other error that occurs during injection.

exception di.exceptions.InvalidAnnotationBases: di.exceptions.DIError

Use strings to annotate arguments used with PythonDI. You’ll probably see a lot of these errors if you don’t.

exception di.exceptions.InvalidBindingTypeBases: di.exceptions.DIError

Gets raised when the passed item to bind mismatches the expected data type.

exception di.exceptions.MissingDependencyDeclarationBases: di.exceptions.DIError

Gets raised when any positional or keyword only argument of an injectee lacks an annotated dependency decla-ration. This error only occurs if use_arg_names is False.

exception di.exceptions.NoInnerProviderFoundBases: di.exceptions.DIError

Raised when a ProviderProxy without an inner provider has to provide().

exception di.exceptions.UnknownParameterKindBases: di.exceptions.DIError

This one is only for security reasons. It eventually gets raised when an unknowninspect.Parameter.kind is detected during injection. However, the current implementation ofinject_into() handles all possible cases known until Python 3.3.

14 Chapter 1. Contents

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

1.4.7 di.types

class di.types.StrictDict(seq=None, **kwargs)A simple dict subclass. It prevents overriding existing keys and lazy deletion.

__setitem__(key, value)Sets a key/value pair. Raises a KeyError if the key already exist.

Parameters

• key – The key

• value – The value

Raises KeyError

__delitem__(key)Removes a key/value pair based upon the given key. Raises a KeyError if the key doesn’t exist.

Parameters key – The key

Raises KeyError

1.4. Package documentation 15

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

16 Chapter 1. Contents

CHAPTER

TWO

INDICES AND TABLES

• genindex

• modindex

• search

17

Pythonic Dependency Injection (py-di) Documentation, Release 0.1.1

18 Chapter 2. Indices and tables

PYTHON MODULE INDEX

ddi.binding_specs, 12di.bindings, 12di.exceptions, 14di.injectors, 9di.providers, 10di.settings, 13

19