A Django DI manager
Project description
Rhazes
A Dependency Injection framework for Django Framework.
Versions and Requirements
Written for Django 4.2 using python 3.9. Other python versions (3.6+) should also be supported. It may work with other Django versions as well (but requires changes being applied to setup.cfg
).
How it works
Once Rhazes ApplicationContext
is initialized it will scan for classes marked with @bean
decorator under packages listed in settings.INSTALLED_APPS
or settings.RHAZES_PACKAGES
(preferably).
Afterwards, it creates a graph of these classes and their dependencies to each other and starts to create objects for each class and register them as beans under ApplicationContext
.
If everything works perfectly, you can access the beans using ApplicationContext.get_bean(CLASS)
to get beans of a type.
Example
Let's assume we have bean classes like below:
from abc import ABC, abstractmethod
from rhazes.decorator import bean
class UserStorage(ABC):
@abstractmethod
def get_user(user_id: int):
pass
@bean(_for=UserStorage, primary=False) # primary is False by default too
class DatabaseUserStorage(UserStorage):
def get_user(user_id: int):
return None
@bean(_for=UserStorage, primary=True) # set as primary implementation of UserStorage
class CacheUserStorage(UserStorage):
def get_user(user_id: int):
return None
@bean()
class ProductManager:
def __init__(self, user_storage: UserStorage):
self.user_storage = user_storage
def get_user_products(user_id):
user = self.user_storage.get_user(user_id)
# Do something to find products of user?
Now assuming you have the above classes defined user some packages that will be scanned by Rhazes, you can access them like this:
from rhazes.context import ApplicationContext
from somepackage import UserStorage, DatabaseUserStorage, CacheUserStorage, ProductManager
application_context = ApplicationContext
# scan packages at settings.INSTALLED_APPS or settings.RHAZES_PACKAGES
application_context.initialize()
# Get ProductManager bean using its class
product_manager: ProductManager = application_context.get_bean(ProductManager)
# Get UserStorage (interface) bean
# this will be CacheUserStorage implementation since primary was set to true
user_storage: UserStorage = application_context.get_bean(UserStorage)
# Specifically get beans of classes (not the interface)
cache_user_storage: CacheUserStorage = application_context.get_bean(CacheUserStorage) # to directly get CacheUserStorage
database_user_storage: DatabaseUserStorage = application_context.get_bean(DatabaseUserStorage) # to directly get DatabaseUserStorage
Bean factory
Bean factories are just classes that produce a bean. They are beans themselves!
from rhazes.protocol import BeanFactory
@bean
class SomeBeanFactory(BeanFactory):
# optional: if you haven't defined "_for" in @bean, you can determine it here
@classmethod
def produces(cls):
return SomeBean
def produce(self):
return SomeBean()
Singleton
You can define beans as singleton.
@bean(singleton=True)
class SomeBean:
pass
At this point this bean will always be the same instance when being injected into another class (another bean or @inject
(read further))
Lazy Bean Dependencies
If the bean you are defining is depended on another bean but you don't want to immediately instantiate that other bean you can mark it as lazy.
@bean
class DependencyA:
pass
@bean(lazy_dependencies=[DependencyA])
class DependencyB:
def __int__(self, dependency_a: DependencyA):
self.dependency_a = dependency_a
Now dependency_a
will not be instantiated (built) until there is a call to it from inside DependencyB
instances.
Injection
You can inject beans into functions or classes as long as your function (or class __init__
function) has good support for **kwargs
.
These classes or functions need to be called with determined input parameter names. Example:
@bean
class SomeBean:
pass
@inject()
def function(bean: SomeBean, random_input: str):
...
# You can call it like this:
function(random_input="something") # `bean` will be injected automatically
Example for classes:
@bean
class SomeBean:
pass
@inject
class MyClazz:
def __init__(self, bean: SomeBean, random_input: str):
...
MyClazz(random_input="something") # `bean` will be injected automatically
To explicitly inject some beans and not others:
@bean
class SomeBean1:
pass
@bean
class SomeBean2:
pass
@inject(injections=[SomeBean1])
def function(bean1: SomeBean1, bean2: SomeBean2, random_input: str):
...
# You can call it like this:
function(bean2=SomeBean2(), random_input="something") # `bean1` will be injected automatically
Inject into Django views
At this stage only injection into class views are tested. Example:
@inject()
class NameGeneratorView(APIView):
# You could optionally use @inject() here or at .setup()
def __init__(self, string_generator: StringGeneratorService, **kwargs):
self.string_generator = string_generator
super(NameGeneratorView, self).__init__(**kwargs)
def get(self, request, *args, **kwargs):
qs: dict = request.GET.dict()
return Response(data={
"name": self.string_generator.generate(
int(qs.get("length", 10))
)
})
This example is taken from here.
Contribution
Read the contribution guidelines.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.