A pythonic dependency injection library.
.. image:: https://badge.fury.io/py/pinject.svg :target: https://pypi.org/project/pinject/ .. image:: https://github.com/google/pinject/workflows/PyPI/badge.svg :target: https://github.com/google/pinject/actions?query=workflow%3APyPI .. image:: https://pepy.tech/badge/pinject :target: https://pepy.tech/badge/pinject .. image:: https://pepy.tech/badge/pinject/month :target: https://pepy.tech/badge/pinject
Pinject is a dependency injection library for python.
The primary goal of Pinject is to help you assemble objects into graphs in an easy, maintainable way.
If you are already familiar with other dependency injection libraries, you may want to read the condensed summary section at the end, so that you get an idea of what Pinject is like and how it might differ from libraries you're used to.
There is a changelog of differences between released versions near the end of this README.
If you're wondering why to use a dependency injection library at all: if you're writing a lot of object-oriented code in python, then it will make your life easier. See, for instance:
If you're wondering why to use Pinject instead of another python dependency injection library, a few of reasons are:
@inject_this and @annotate_that just to get started. With Pinject, you call new_object_graph(), one line, and you're good to go.Look at the simplest getting-started examples for Pinject and for other similar libraries. Pinject should be uniformly easier to use, clearer to read, and less boilerplate that you need to add. If you don't find this to be the case, email!
The easiest way to install Pinject is to get the latest released version from PyPI:
.. code-block:: shell
sudo pip install pinjectIf you are interested in the developing version, you can install the next version from Test PyPI:
.. code-block:: shell
sudo pip install \
--no-deps \
--no-cache \
--upgrade \
--index-url https://test.pypi.org/simple/ \
pinjectYou can also check out all the source code, including tests, designs, and TODOs:
.. code-block:: shell
git clone https://github.com/google/pinject
The most important function in the pinject module is
new_object_graph(). This creates an ObjectGraph, which you can use to
instantiate objects using dependency injection. If you pass no args to
new_object_graph(), it will return a reasonably configured default
ObjectGraph.
.. code-block:: python
>>> class OuterClass(object):
... def __init__(self, inner_class):
... self.inner_class = inner_class
...
>>> class InnerClass(object):
... def __init__(self):
... self.forty_two = 42
...
>>> obj_graph = pinject.new_object_graph()
>>> outer_class = obj_graph.provide(OuterClass)
>>> print outer_class.inner_class.forty_two
42
>>>As you can see, you don't need to tell Pinject how to construct its
ObjectGraph, and you don't need to put decorators in your code. Pinject has
reasonable defaults that allow it to work out of the box.
A Pinject binding is an association between an arg name and a provider.
In the example above, Pinject created a binding between the arg name
inner_class and an implicitly created provider for the class
InnerClass. The binding it had created was how Pinject knew that it
should pass an instance of InnerClass as the value of the inner_class
arg when instantiating OuterClass.
Pinject creates implicit bindings for classes. The implicit bindings assume
your code follows PEP8 conventions: your classes are named in CamelCase,
and your args are named in lower_with_underscores. Pinject transforms
class names to injectable arg names by lowercasing words and connecting them
with underscores. It will also ignore any leading underscore on the class
name.
+-------------+-------------+
| Class name | Arg name |
+=============+=============+
| Foo | foo |
+-------------+-------------+
| FooBar | foo_bar |
+-------------+-------------+
| _Foo | foo |
+-------------+-------------+
| _FooBar | foo_bar |
+-------------+-------------+
If two classes map to the same arg name, whether those classes are in the same module or different modules, Pinject will not create an implicit binding for that arg name (though it will not raise an error).
So far, the examples have not told new_object_graph() the classes for
which it should create implicit bindings. new_object_graph() by default
looks in all imported modules, but you may occasionally want to restrict the
classes for which new_object_graph() creates implicit bindings. If so,
new_object_graph() has two args for this purpose.
modules arg specifies in which (python) modules to look for classes; this defaults to ALL_IMPORTED_MODULES.classes arg specifies a exact list of classes; this defaults to None... code-block:: python
>>> class SomeClass(object):
... def __init__(self, foo):
... self.foo = foo
...
>>> class Foo(object):
... pass
...
>>> obj_graph = pinject.new_object_graph(modules=None, classes=[SomeClass])
>>> # obj_graph.provide(SomeClass) # would raise a NothingInjectableForArgError
>>> obj_graph = pinject.new_object_graph(modules=None, classes=[SomeClass, Foo])
>>> some_class = obj_graph.provide(SomeClass)
>>>One thing that can get tedious about dependency injection via initializers is
that you need to write __init__() methods that copy args to fields. These
__init__() methods can get repetitive, especially when you have several
initializer args.
.. code-block:: python
>>> class ClassWithTediousInitializer(object):
... def __init__(self, foo, bar, baz, quux):
... self._foo = foo
... self._bar = bar
... self._baz = baz
... self._quux = quux
...
>>>Pinject provides decorators that you can use to avoid repetitive initializer bodies.
@copy_args_to_internal_fields prepends an underscore, i.e., it copies an arg named foo to a field named _foo. It's useful for normal classes.@copy_args_to_public_fields copies the arg named as-is, i.e., it copies an arg named foo to a field named foo. It's useful for data objects... code-block:: python
>>> class ClassWithTediousInitializer(object):
... @pinject.copy_args_to_internal_fields
... def __init__(self, foo, bar, baz, quux):
... pass
...
>>> cwti = ClassWithTediousInitializer('a-foo', 'a-bar', 'a-baz', 'a-quux')
>>> print cwti._foo
'a-foo'
>>>When using these decorators, you'll normally pass in the body of the
initializer, but you can put other statements there if you need to. The args
will be copied to fields before the initializer body is executed.
These decorators can be applied to initializers that take **kwargs but not
initializers that take *pargs (since it would be unclear what field name
to use).
To create any bindings more complex than the implicit class bindings described
above, you use a binding spec. A binding spec is any python class that
inherits from BindingSpec. A binding spec can do three things:
configure() method can create explicit bindings to classes or instances, as well as requiring bindings without creating them.dependencies() method can return depended-on binding specs.The new_object_graph() function takes a sequence of binding spec instances
as its binding_specs arg. new_object_graph() takes binding spec
instances, rather than binding spec classes, so that you can manually inject
any initial dependencies into the binding specs as needed.
Binding specs should generally live in files named binding_specs.py, where
each file is named in the plural even if there is exactly one binding spec in
it. Ideally, a directory's worth of functionality should be coverable with a
single binding spec. If not, you can create multiple binding specs in the
same binding_specs.py file. If you have so many binding specs that you
need to split them into multiple files, you should name them each with a
_binding_specs.py suffix.
configure() methodsPinject creates implicit bindings for classes, but sometimes the implicit
bindings aren't what you want. For instance, if you have
SomeReallyLongClassName, you may not want to name your initializer args
some_really_long_class_name but instead use something shorter like
long_name, just for this class.
For such situations, you can create explicit bindings using the
configure() method of a binding spec. The configure() method takes a
function bind() as an arg and calls that function to create explicit
bindings.
.. code-block:: python
>>> class SomeClass(object):
... def __init__(self, long_name):
... self.long_name = long_name
...
>>> class SomeReallyLongClassName(object):
... def __init__(self):
... self.foo = 'foo'
...
>>> class MyBindingSpec(pinject.BindingSpec):
... def configure(self, bind):
... bind('long_name', to_class=SomeReallyLongClassName)
...
>>> obj_graph = pinject.new_object_graph(binding_specs=[MyBindingSpec()])
>>> some_class = obj_graph.provide(SomeClass)
>>> print some_class.long_name.foo
'foo'
>>>The bind() function passed to a binding function binds its first arg,
which must be an arg name (as a str), to exactly one of two kinds of
things.
to_class binds to a class. When the binding is used, Pinject injects an instance of the class.to_instance binds to an instance of some object. Every time the binding is used, Pinject uses that instance... code-block:: python
>>> class SomeClass(object):
... def __init__(self, foo):
... self.foo = foo
...
>>> class MyBindingSpec(pinject.BindingSpec):
... def configure(self, bind):
... bind('foo', to_instance='a-foo')
...
>>> obj_graph = pinject.new_object_graph(binding_specs=[MyBindingSpec()])
>>> some_class = obj_graph.provide(SomeClass)
>>> print some_class.foo
'a-foo'
>>>The configure() method of a binding spec also may take a function
require() as an arg and use that function to require that a binding be
present without actually defining that binding. require() takes as args
the name of the arg
No open issues yet, or sync has not completed.