Create a DI Container in Python

Create a DI Container in Python

DI Container is a system that automatically passes an instance of the appropriate type to the argument of the constructor when creating an instance of a class. It makes it easier to implement DI patterns and expresses loosely coupled code.

If you want to know more about DI patterns, please refer to another article. Actually, it's very easy to make, so I'll do my best.

I will link the completed repository. Repository

DEMO

Register in the DI class that this is the implementation class, or that this class will be used in a singleton. After that, the flow is to ask the DI class to create an instance of this class.

Look at the squid DEMO.

class Animal:
    def __init__(self, kind: str, name: str):
        self.kind = kind
        self.name = name

    def __str__(self):
        return "I am {kind}. My name is {name}".format(kind=self.kind, name=self.name)


class Cat(Animal):
    def __init__(self, name: str = "Cathy"):
        super().__init__('Cat', name)


di = DI()
di.register(Animal, Cat)
print(di.resolve(Animal))
# > I am Cat. My name is Cathy.


class Person:
    def __init__(self, name: str, role: str):
        self.name = name
        self.role = role

    def __str__(self):
        return "I am {name}. My role is {role}.".format(name=self.name, role=self.role)

nukumizu = Person('Nukumizu', 'Actor')
di.register_singleton(Person, nukumizu)
print(di.resolve(Person))
# > I am Nukumizu. My role is Actor.

Overall picture

The minimum requirements for a DI Container are the following three.

--Container class for registering the corresponding class or singleton --Resolver class that generates registered classes, etc. --DI class of the above two façade classes

Let's take a look at the actual code in order.

Container Let's start with the Container class. This class provides the following functionality:

--Ability to register implementation class for base class --Ability to register singleton instances --Ability to get implementation class from base class --Ability to get registered singleton instances --Ability to check if it is registered --Ability to clear registered data

Let's take a look at the actual code.

container.py


class DIContainer:
    concrete_table = {}
    singleton_table = {}

    def register(self, base_cls: type, concrete_cls: type):
        if not issubclass(concrete_cls, base_cls):
            raise TypeError('Concrete class is required {} not {}.'.format(base_cls, concrete_cls))
        self.concrete_table[base_cls] = concrete_cls

    def register_singleton(self, t: type, instance: object):
        if not isinstance(instance, t):
            raise TypeError('Instance type is required {} not {}.'.format(t, type(instance)))
        self.singleton_table[t] = instance

    def get(self, t: type):
        if self.is_registered_concrete(t):
            return self.concrete_table[t]
        return t

    def get_singleton(self, t: type):
        if self.is_registered_singleton(t):
            return self.singleton_table[t]

        raise KeyError('{} is not registered as singleton.'.format(t))

    def is_registered_singleton(self, t: type):
        return t in self.singleton_table.keys()

    def is_registered_concrete(self, t: type):
        return t in self.concrete_table.keys()

    def is_registered(self, t: type):
        return self.is_registered_concrete(t) or self.is_registered_singleton(t)

    def clear(self):
        self.concrete_table.clear()
        self.singleton_table.clear()

Since I only register honestly, I think there is no place to explain in particular. I'm sorry if the code is hard to read.

Resolver Next, let's look at the Resolver class. This class provides the following functionality:

--Ability to generate registered classes --Ability to instantiate class init arguments

There are only two.

Let's take a look at the actual code.

resolver.py


from container import DIContainer


class Resolver:
    def __init__(self, container: DIContainer):
        self.container = container

    def resolve(self, cls: type):
        if self.container.is_registered_singleton(cls):
            return self.container.get_singleton(cls)
        cls = self.container.get(cls)
        init_args = self.resolve_init_args(cls)
        return cls(**init_args)

    def resolve_init_args(self, cls: type):
        init_args_annotations = get_init_args_annotations(cls)
        defaults = get_init_default_values(cls)
        result = {}
        args_count = len(init_args_annotations)
        for key, t in init_args_annotations.items():
            if self.container.is_registered(t) or len(defaults) < args_count:
                result[key] = self.resolve(t)
            else:
                result[key] = defaults[len(defaults) - args_count]
            args_count -= 1

        return result


def get_init_args_annotations(cls: type):
    if hasattr(cls.__init__, '__annotations__'):
        return cls.__init__.__annotations__
    return {}


def get_init_default_values(cls: type):
    if hasattr(cls.__init__, '__defaults__'):
        result = cls.__init__.__defaults__
        return [] if result is None else result
    return []

I will explain get_init_args_annotations outside the class. All you need to create a DIContainer is the constructor argument information. It won't start without this. The way to get the constructor information in python is annotations. Now you can get an associative array with the argument name Key and the class type Value. An example is shown below.

class Person:
    def __init__(self, name: str, age: int):
        pass
        
print(Person.__init__.__annotaions__)
# {'name': <class 'str'> , 'age': <class 'int'>}

Create an instance of the class based on the information obtained by \ __ annotations__.

Now let's check the flow around the generation process.

  1. First, if the target class is registered in the singleton, just return the instance as it is.
  2. Next, check if the implementation class of the target class is registered in Container.
  3. If registered, make sure to instantiate the implementation class.
  4. If the constructor of the class to be generated requires an argument, create an instance of the argument class first. (That is, have the argument class generated by the resolve function.)
  5. After creating all instances of the argument class with depth-first priority, pass it to the argument of the target class and generate it.

It feels like recursively creating classes. Now that you have all the functions as a DI Container, you can use it by creating a façade.

di.py


from container import DIContainer
from resolver import Resolver


class DI:
    def __init__(self):
        self.container = DIContainer()
        self.resolver = Resolver(self.container)

    def register_singleton(self, t: type, instance: object):
        self.container.register_singleton(t, instance)

    def register(self, base_cls: type, concrete_cls: type):
        self.container.register(base_cls, concrete_cls)

    def resolve(self, t: type):
        return self.resolver.resolve(t)

    def clear(self):
        self.container.clear()

At this point, you can use DI Container like DEMO.

Summary

Once you understand it, you will be less likely to forget how to use it. Knowing the DI pattern, I solved some questions about handling Interface relationships.

By introducing DI Container to my library at my previous workplace, it was extremely convenient not to have to check how to generate classes one by one. Of course, there are disadvantages, but it is still convenient, so I will use it when it can be used.

I will link the completed repository. Repository

I would appreciate it if you could let me know if there are any deficiencies.

Thank you for staying with us until the end.

Recommended Posts

Create a DI Container in Python
Create a function in Python
Create a dictionary in Python
Create a binary file in Python
Create a Kubernetes Operator in Python
Create a random string in Python
Create a simple GUI app in Python
Create a JSON object mapper in Python
[GPS] Create a kml file in Python
Create a Python module
Create SpatiaLite in Python
Create a Python environment
Create a Vim + Python test environment in 1 minute
Create a GIF file using Pillow in Python
I want to create a window in Python
Create a standard normal distribution graph in Python
How to create a JSON file in Python
Create a virtual environment with conda in Python
Create a simple momentum investment model in Python
Create a new page in confluence with Python
Create a datetime object from a string in Python (Python 3.3)
Create a package containing global commands in Python
Create a MIDI file in Python using pretty_midi
Create a loop antenna pattern in Python in KiCad
[Docker] Create a jupyterLab (python) environment in 3 minutes!
Take a screenshot in Python
Create a Wox plugin (Python)
Create gif video in Python
Make a bookmarklet in Python
Create a python numpy array
Draw a heart in Python
Create a directory with python
DI (Dependency Injection) in Python
Create a data collection bot in Python using Selenium
[LINE Messaging API] Create a rich menu in Python
Create a plugin to run Python Doctest in Vim (2)
Create a plugin to run Python Doctest in Vim (1)
In Python, create a decorator that dynamically accepts arguments Create a decorator
Create a fake Minecraft server in Python with Quarry
Maybe in a python (original title: Maybe in Python)
Write a binary search in Python
[python] Manage functions in a list
Hit a command in Python (Windows)
Create a CSV reader in Flask
Create a local scope in Python without polluting the namespace
Create a list in Python with all followers on twitter
Create a python GUI using tkinter
Create a Python environment on Mac (2017/4)
Draw a scatterplot matrix in python
Create a child account for connect with Stripe in Python
Let's create a script that registers with Ideone.com in Python.
ABC166 in Python A ~ C problem
Create a virtual environment with Python!
Write A * (A-star) algorithm in Python
Create Gmail in Python without API
Solve ABC036 A ~ C in Python
Create a python environment on centos
Create code that outputs "A and pretending B" in python
Write a pie chart in Python
Write a vim plugin in Python
Create Python project documentation in Sphinx