I built a minimal, type-safe dependency injection container for Python
Is there a point to dependency injection container in Python? - Stack Overflow
Suggestions for dependency injection / IoC container libraries
(Better) Dependency Injection in FastAPI
Β» pip install dependency-injector
Hey everyone,
Coming from a Java background, Iβve always appreciated the power and elegance of the Spring frameworkβs dependency injection. However, as I began working more with Python, I noticed that most DI solutions felt unnecessarily complex. So, I decided to build my own: Fusebox.
What My Project Does Fusebox is a lightweight, zero-dependency dependency injection (DI) container for Python. It lets you register classes and inject dependencies using simple decorators, making it easy to manage and wire up your applicationβs components without any runtime patching or hidden magic. It supports both class and function injection, interface-to-implementation binding, and automatic singleton caching.
Target Audience Fusebox is intended for Python developers who want a straightforward, type-safe way to manage dependenciesβwhether youβre building production applications, prototypes, or even just experimenting with DI patterns. If you appreciate the clarity of Javaβs Spring DI but want something minimal and Pythonic, this is for you.
Comparison Most existing Python DI libraries require complex configuration or introduce heavy abstractions. Fusebox takes a different approach: it keeps things simple and explicit, with no runtime patching, metaclass tricks, or bulky config files. Dependency registration and injection are handled with just two decoratorsβ@component and @inject.
Links:
-
GitHub
-
PyPI
Feedback, suggestions, and PRs are very welcome! If you have any questions about the design or implementation, Iβm happy to chat.
has no usable concept of interface based programming (as in C# interfaces)
Just because the compiler can't check that you're using the interface correctly doesn't mean there's "no usable concept of interfaces". You document an interface, and write unit tests.
As for globals, it's not like public static methods and fields on C# or Java classes are really any different. Consider how java.lang.Math works, for example. Now consider the fact that java.lang.Math isn't a Singleton. They did that for a good reason.
With all these goodies is there really any point to a dependency injection container
I doubt it, but then I never really saw the point of them in C# or Java, either. Dependency injection is a programming technique, in my view. And there's really not that much to it, either.
I have always suspected that Dependency injection as a design pattern was a bad smell that has been created by everything must be a class "Nazi thinking"
No, it isn't. Dependency injection is a good idea a lot of the time. You don't need a class to inject dependencies into, either. Every time you pass something to a free function as a parameter, instead of having the function call another function to get the information, you're basically doing the same thing: inversion of control. Python also lets you treat modules similarly to classes in a lot of ways (certainly more ways than Java and C# do). There are problems that can be solved by passing modules as parameters to functions. :)
So far I think I can cover factories, Singletons, Multi-instance objects, just by using Globals.
Singletons are the bad smell, if anything. In nearly every case, in my extensive experience, they exist because someone thought it would be Bad(TM) on principle to have a global, without really thinking through the possible options, or why they wanted that kind of access to a single shared object, or even why globals are "Bad(TM) on principle" in the first place.
You could make a global function in Python that acts as a factory. However, I would say it's more Pythonic to do any of the following:
a) first, make really, really, really sure you can't just do what you want with __init__. I mean, in a dynamically typed language, you can do a heck of a lot that way.
b) If __init__ won't cut it, try using __new__ to control the behaviour.
In Python, classes are objects themselves, which are callable. By default, calling them instantiates the class. With __new__, you can hook into that.
c) Use a decorator applied to the class. Here is an example that makes a Singleton (just because):
def _singleton(cls):
instance = cls()
result = lambda: instance
result.__doc__ = cls.__doc__
return result
@_singleton
class example(object): pass
The way this works: when you decorate the class, _singleton() is called, with the class being passed in. An instance is constructed and cached, and _singleton() returns an anonymous function that will return the instance when called. To complete the charade, the class's documentation is attached to the anonymous function. Then Python rebinds the class' name in the global scope to the returned anonymous function. So when you call it, you get the same instance of the class, every time.
Now, this can still be worked around, of course (you can do something like example().__class__() to get another instance), but it's much more clear that you're doing something wrong than if you simply ignored a factory function in order to use the constructor normally. Plus, it means the calling code actually acts as if it were calling the constructor normally :)
The Duck Typing is the thing that is getting me at the moment, so used to defining interfaces then basing classes on these interfaces and letting the static stuff cover my stupidity that I feel that without static typing, containers are a bit useless.
You need to shift your thinking: stop worrying about what the thing you've been passed is, and worry about whether it can do what you want it to do. That's how duck typing works.
The conclusion
(The following is the most relevant part of the original post. I admit, I waxed a little poetical, and so I though I should simply include the most important sentences in their own section. That said, I feel that the poetic waxing is important enough that I have not deleted it.)
Dependency injection is still used. There will always be the need for objects to communicate with already instantiated objects. There will be a need for "parent" objects (or containers, or whatever) to set up the state of their "children". These will need to be passed in through some method or set through some assignment, but in the abstract sense, it is the same thing.
The original answer:
Type systems
Python is, in many ways, the second most mathematically pure language I've ever encountered -- it follows only Scheme (though I've not ever used Haskell, I understand that to be up there too).
Python supports closures natively, it has continuations (yield syntax), multiple inheritance, and a loop comprehension syntax which is largely unparalleled. All of these bring it far closer to Alonzo Church's original vision in Lambda Calculus (an McCarthy's original ideas behind Lisp). Python 3 makes it even more pure with set comprehensions (which make my heart flutter just thinking of their inherent beauty).
Constantly and continually there is the idea that the data stored in the variables of Python have more in common with their counterparts in Mathematics, so much that the interface of an object can be diminished to simply be, "the adjectives or set of adjectives which describe the object". Basically, an object's interface is wholly and completely contained in its __dict__ and displayed with dir.
All of that considered, it does start making one wonder whether the "traditional" way of looking at things ("traditional" with quotes because Python is as old as Java) really can work in such a language. When even arguments lists in Python have an OOP feeling to them (kwargs, anyone?) it really does turn the world upside-down.
Dependency injection is still used. There will always be the need for objects to communicate with already instantiated objects. There will be a need for "parent" objects (or containers, or whatever) to set up the state of their "children". These will need to be passed in through some method or set through some assignment, but in the abstract sense, it is the same thing. But there is an implicit trust that the contents of the passed object's __dict__ will contain an appropriate description. Because of this, it becomes far less, "once I have instantiated this object, I will bestow upon it the very things needed for the life," and far more, "Well, yea, an object needs a state, so I'm giving it one."
And this exposes a hidden aspect of static typing. In order for something which expects an IFoo to work, it has to have full and complete knowledge of what it means to be an IFoo, even if it will never need 90% of that definition. Meanwhile, duck typing makes the depending object know only that properties X, Y, and Z should be there at run-time.
Globals
As to globals. Avoid them unless you have no other choice. You are better off using a Singleton if only because Singletons allow you to log when the value changes. This is not true of globals.
There is a rule of programming, the more exposed and complicated your interface, the harder it will be to maintain. If something is placed in a global variable, anything in that module, and possibly anything in ANY module can modify the value. The code can almost become non-deterministic. I assure you, sadness follows.