Python Forum
super lightweight (only 35 lines) dependency injection (ioc) support for Python
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
super lightweight (only 35 lines) dependency injection (ioc) support for Python
#1
Hi,

A super simple and risk-free way to do dependency injection (ioc) in Python. The entire code base is only 35 lines (empty lines included). No need to use a complex 3rd-party framework or be concerned about the long-term viability of the library.

Please check it out at https://github.com/freemant2000/disl

Thanks Big Grin
Gribouillis likes this post
Reply
#2
This looks interesting. I still need to find a concrete example of where to use this in my code but I'll definitely try this one day or the other. Thanks for sharing!
Reply
#3
(Jan-13-2023, 08:14 AM)Gribouillis Wrote: This looks interesting. I still need to find a concrete example of where to use this in my code but I'll definitely try this one day or the other. Thanks for sharing!

I am glad that you find it useful. It is typically used when your code needs to get a configuration value (db Url, api key, etc.) or in service layer code (i.e., the code that gets called by your UI code to read or/and update the DB).

Let me know if I can be of any help with the code Smile
Reply
#4
I've been playing with this, why not make the container class a subclass of dict with a custom attribute access? This makes the interface of the container more friendly and one can use dictionary operations to update it.
from dataclasses import dataclass


@dataclass
class Inject:
    name: str = None

class Wirer(dict):

    def __setattr__(self, name, bean):
        self[name] = bean

    def __getattr__(self, name):
        if name in self:
            b = self[name]
            # must do this first in case of mutual dependencies
            # also avoids further calls to __getattr__
            object.__setattr__(self, name, b)
            self._wire_bean(name, b)
            return b
        else:
            raise AttributeError(name)

    def _wire_bean(self, name, b):
        if not hasattr(b, "__dict__"):
            # built-in object such as a string
            return b
        attrs = vars(b)
        for n, v in attrs.items():
            if isinstance(v, Inject):
                dep_name = v.name or n
                attrs[n] = getattr(self, dep_name)

if __name__ == '__main__':
    class ProductDb:
        def __init__(self):
            self.db_path=Inject("database_path") # specify the bean name

        def get_products(self):
            print(f"getting products from {self.db_path}")

    di = Wirer()
    di.pdb = ProductDb()
    di.database_path = "c:/Users/kent/test.db"
    di.pdb.get_products()

    di = Wirer(pdb=ProductDb(), database_path='c:/spam/spam.db')
    di.pdb.get_products()
    di.pdb.get_products()
    
Output:
getting products from c:/Users/kent/test.db getting products from c:/spam/spam.db
kenttong likes this post
Reply
#5
It looks really nice!

Thanks Big Grin
Reply
#6
In a new version, I'm able to inject partial data
    class Spam:
        def __init__(self):
            self.eggs = Inject()
            self.ham = Inject()

        def __str__(self):
            return f"<Spam {self.eggs} {self.ham}>"

    spam = Wirer(spam=Spam(), ham='HAM').spam
    print(spam)
    spam = Wirer(spam=spam, eggs='EGGS').spam
    print(spam)
Output:
<Spam Inject(name=None) HAM> <Spam EGGS HAM>
In fact the Wirer behaves much like the builtin partial function. Instead of partial functions, it manipulates partial objects, or partially wired objects. This is a great paradigm!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  D-Pad/No dependency platformer + tilemap michael1789 0 1,399 Jun-27-2020, 08:59 PM
Last Post: michael1789

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020