punq icon indicating copy to clipboard operation
punq copied to clipboard

An IoC container for Python 3.8+

Punq

.. image:: https://codecov.io/gh/bobthemighty/punq/branch/master/graph/badge.svg?token=52hQhaggnk :target: https://codecov.io/gh/bobthemighty/punq

.. image:: https://readthedocs.org/projects/punq/badge/?version=latest :target: https://punq.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status

An unintrusive library for dependency injection in modern Python. Inspired by Funq_, Punq is a dependency injection library you can understand.

  • No global state
  • No decorators
  • No weird syntax applied to arguments
  • Small and simple code base with 100% test coverage and developer-friendly comments.

Installation

Punq is available on the cheese shop_.

.. code:: bash

pip install punq

Documentation is available on Read the docs_.

Quick Start

Punq avoids global state, so you must explicitly create a container in the entrypoint of your application:

.. code:: python

import punq

container = punq.Container()

Once you have a container, you can register your application's dependencies. In the simplest case, we can register any arbitrary object with some key:

.. code:: python

container.register("connection_string", instance="postgresql://...")

We can then request that object back from the container:

.. code:: python

conn_str = container.resolve("connection_string")

Usually, though, we want to register some object that implements a useful service.:

.. code:: python

class ConfigReader: def get_config(self): pass

class EnvironmentConfigReader(ConfigReader): def get_config(self): return { "logging": { "level": os.env.get("LOGGING_LEVEL", "debug") }, "greeting": os.env.get("GREETING", "Hello world") }

container.register(ConfigReader, EnvironmentConfigReader)

Now we can resolve the ConfigReader service, and receive a concrete implementation:

.. code:: python

config = container.resolve(ConfigReader).get_config()

If our application's dependencies have their own dependencies, Punq will inject those, too:

.. code:: python

class Greeter: def greet(self): pass

class ConsoleGreeter(Greeter): def init(self, config_reader: ConfigReader): self.config = config_reader.get_config()

  def greet(self):
     print(self.config['greeting'])

container.register(Greeter, ConsoleGreeter) container.resolve(Greeter).greet()

If you just want to resolve an object without having any base class, that's okay:

.. code:: python

class Greeter: def init(self, config_reader: ConfigReader): self.config = config_reader.get_config()

  def greet(self):
     print(self.config['greeting'])

container.register(Greeter) container.resolve(Greeter).greet()

And if you need to have a singleton object for some reason, we can tell punq to register a specific instance of an object:

.. code:: python

class FileWritingGreeter: def init(self, path, greeting): self.path = path self.message = greeting self.file = open(self.path, 'w')

  def greet(self):
     self.file.write(self.message)

one_true_greeter = FileWritingGreeter("/tmp/greetings", "Hello world") container.register(Greeter, instance=one_true_greeter)

You might not know all of your arguments at registration time, but you can provide them later:

.. code:: python

container.register(Greeter, FileWritingGreeter) greeter = container.resolve(Greeter, path="/tmp/foo", greeting="Hello world")

Conversely, you might want to provide arguments at registration time, without adding them to the container:

.. code:: python

container.register(Greeter, FileWritingGreeter, path="/tmp/foo", greeting="Hello world")

Fuller documentation is available on Read the docs_.

Github workflows, nox configuration, and linting gratefully stolen from Hypermodern Python_

.. _cheese shop: https://pypi.org/project/punq/ .. _Read the docs: http://punq.readthedocs.io/en/latest/ .. _Funq: https://github.com/jlyonsmith/Funq .. _Hypermodern Python: https://github.com/cjolowicz/cookiecutter-hypermodern-python