Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Dependency Injection: Python

DZone's Guide to

Dependency Injection: Python

Dependency injection is just one of those things you need to know when you're a programmer. Read on to learn about DI and/or brush up on your skills.

· Web Dev Zone ·
Free Resource

Bugsnag monitors application stability, so you can make data-driven decisions on whether you should be building new features, or fixing bugs. Learn more.

Overview

Dependency Injection (DI) is a software engineering technique for defining the dependencies among objects. Basically, the process of supplying a resource that a given piece of code requires. The required resource is called a dependency.

There are various classes and objects defined when writing code. Most of the time, these classes depend on other classes in order to fulfill their intended purpose. These classes, or a better word might be Components, know the resources they need and how to get them. DI handles defining these dependent resources and provides ways to instantiate or create them externally. Dependency Containers are used to implement this behavior and holds the map of dependencies for the components.

If object A depends on object B, object A must not create or import object B directly. Instead, object A must provide a way for injecting object B. The responsibilities of object creation and dependency injection are delegated to external code.

Why Use Dependency Injection in Your Code?

  • The flexibility of configurable components: As the components are externally configured, there can be various definitions for a component (Control on application structure).
  • Testing Made Easy: Instantiating mock objects and integrating with class definitions is easier.
  • High cohesion: Code with reduced module complexity, increased module reusability.
  • Minimalistic dependencies: As the dependencies are clearly defined, easier to eliminate/reduce unnecessary dependencies.

Implementing DI in Python

Dependency Injection in Python is little different from general static language DI. Python has a microframework library for DI, called dependency_injector. This package has two main entities: containers and providers.

Providers describe how objects are accessed. Containers are simply a collection of providers. Most commonly used provider types are: Singleton, Configuration, and Factory.

Example

The following example demonstrates the usage and implementation of DI in Python. Create a file named email_client.py containing the EmailClient class which depends on the object.

class EmailClient(object):

    def __init__(self, config):
        self._config = config
        self.connect(self._config)

    def connect(self, config):
        # Implement function here
        pass

Create a new file named e mail_reader.py which contains the  EmailReader class and depends on the EmailClient object.

class EmailReader(object):

    def __init__(self, client):
        try:
            self._client = client
        except Exception as e:
            raise e

    def read(self):
        # Implement function here
        pass

Now to define these above dependencies externally, create a new containers.py file. Import the dependency_injector package and the classes to be used in DI.

from dependency_injector import providers, containers
from email_client import EmailClient
from email_reader import EmailReader

Add the class Configs to the file. This class is a container with a configuration provider which provides all the configuration objects.

class Configs(containers.DeclarativeContainer): 
  config = providers.Configuration('config') 
  # other configs

Add another class, Clients. This class is a container defining all kinds of clients. EmailClient is created with a singleton provider, asserting single instances of this class, and defining its dependency on an object.

class Clients(containers.DeclarativeContainer): 
  email_client = providers.Singleton(EmailClient, Configs.config) 
  # other clients 

The third container is the Readersclass, defining the dependency of EmailReader class on the EmailClient class.

class Readers(containers.DeclarativeContainer): 
  email_reader = providers.Factory(EmailReader, client=Clients.email_client) 
  # other readers 
from dependency_injector import providers, containers
from email_client import EmailClient
from email_reader import EmailReader

class Configs(containers.DeclarativeContainer):
    config = providers.Configuration('config')
    # other configs

class Clients(containers.DeclarativeContainer):
    email_client = providers.Singleton(EmailClient, Configs.config)
    # other clients

class Readers(containers.DeclarativeContainer):
    email_reader = providers.Factory(EmailReader, client=Clients.email_client)
    # other readers

To run the example, create the main.py file with following code.

from containers import Readers, Clients, Configs

if __name__ == "__main__":
    Configs.config.override({
        "domain_name": "imap.gmail.com",
        "email_address": "YOUR_EMAIL_ADDRESS",
        "password": "YOUR_PASSWORD",
        "mailbox": "INBOX"
    })
    email_reader = Readers.email_reader()
    print email_reader.read()

In the main.py file, the object is overridden with a given dictionary object. The EmailReader class was instantiated without instantiating the  EmailClient class in the main file, removing the overhead of importing or creating it. That part is taken care by containers file.

For a working example, please refer to my GitHub repo here.

Monitor application stability with Bugsnag to decide if your engineering team should be building new features on your roadmap or fixing bugs to stabilize your application.Try it free.

Topics:
python ,dependency injection ,ioc containers ,web dev ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}