Python Dependency Injection library
Project description
Введение
pyddi - Это библиотека, реализующая внедрение зависимостей в Python
Описание
Главным объектом во всей библиотеке является бин (bean). Бины определяются, создаются и управляются контекстом (BeanContext). Вы можете внедрять их в другие бины или запрашивать их от самого контекста. Тем самым ваш код больше не знает, какие именно объекты к нему приходят, что уменьшает связанность объектов и упрощает поддержку и читаемость, так как теперь управлением вашими объектами и их зависимостями управляет контекст.
Пример
import abc
from pybeandi.context import BeanContextBuilder
from pybeandi.decorators import bean
class Service(abc.ABC):
@abc.abstractmethod
def hello(self) -> str:
pass
@bean(bean_id='service1', profiles={'service1'})
class My1Service(Service):
def hello(self) -> str:
return 'Hello from 1 service'
@bean(bean_id='service2', profiles={'service2'})
class My2Service(Service):
def hello(self) -> str:
return 'Hello from 2 service'
@bean(bean_id='service3', profiles={'service3'}, text="str")
class My3Service(Service):
def hello(self) -> str:
return f'Hello from 3 service and {self.text}'
def __init__(self, text):
self.text = text
if __name__ == '__main__':
ctx_builder = BeanContextBuilder()
ctx_builder.load_yaml('pybeandi.yaml')
ctx_builder.profiles.add('service2')
ctx_builder.scan(globals())
ctx = ctx_builder.init()
print(ctx.beans[Service].hello())
print(Service in ctx.beans)
print(len(ctx.beans))
print(ctx.beans)
print(ctx.profiles)
Теория
Каждый бин обладает:
- уникальным идендификаторов (bean_id), который однозначно определяет его внутри контекста;
- конструктором (factory_func), который создаёт объект бина
- словарём зависимостей (dependencies), который указывает, какому полю конструктора соответствует какой бин
- функцией профиля (profile_func), которая принимает на вход список всех активных профилей и возвращает, нужно ли создавать бин
Краткое описание инициализации контекста
Для создания контекста необходимо создать и настроить BeanContextBuilder
,
затем вызвать его метод init()
, результатом которого будет сам BeanContext
ctx_builder = BeanContextBuilder()
ctx_builder.load_yaml('pybeandi.yaml')
ctx_builder.profiles.add('service2')
ctx_builder.scan(globals())
ctx = ctx_builder.init()
...
Способы определения объектов
Существует несколько способов сообщить контексту, что данный объект является бинов и отдать его под его контроль.
Напрямую через метод
Этот метод в основном используется внутри библиотеки.
Синтаксис:
ctx_builder.register_bean(bean_id: str,
factory_func: Callable[..., Any],
dependencies: Dict[str, str],
profile_func: Callable[[Set[str]], bool] = lambda profs: True)
# Если класс декорирован @bean
ctx_builder.register_bean_by_class(Service)
Добавление вручную
Полезно для добавления строк, списков или других уже готовых объектов как бинов.
Синтаксис:
ctx_builder.add_as_bean(bean_id: str, obj: Any)
Сканирование
Через декорирование класса
Позволяет задеклорировать бин, не создавая лишних сущностей.
Синтаксис:
@bean(bean_id: str,
profiles: Set[str] = None,
profile_func: Callable[[Set[str]], bool] = lambda profs: True,
**depends_on: Dict[str, str])
class MyService(Service):
def __init__(self, dep1, dep2, ...):
...
...
Через метод-фабрику
Этот метод полезен тогда, когда нет доступа к самому классу или для создания иного метода создания бина, чем у его конструктора.
Синтаксис:
@bean(bean_id: str,
profiles: Set[str] = None,
profile_func: Callable[[Set[str]], bool] = lambda profs: True,
**depends_on: Dict[str, str])
def factory_bean(dep1, dep2, ...):
...
return obj
Важно
Если в коде используется декоратор @bean
, то после необходимо обязательно вызвать
ctx_builder.scan(globals())
А декорированные классы и методы-фабрики должны находиться в области видимости
Профили
Профили - это механизм управления инициализации сразу группой бинов в зависимости от настроек. Так, например, если у вас есть две реализации одного интерфейса (абстрактного класса), которые используются или в среде разработки, или в среде на "боевой" машине, то эти реализации могут иметь разные профили, которые определяют что нужно загружать: реализацию для разработчиков или для клиентов.
Задание профилей контекста
ctx_builder.profiles.add('profile1')
Задание профилей бина декоратором
Если необходимо только добавить условие, что все перечисленные профили должны быть активны, то достаточно
@bean(..., profiles={'profile1', ...}, ...)
Если же условие сложнее, то необходимо задать функцию. Так, например, если условием является то, что профиль 'profile1' нету в активных, то
@bean(..., profile_func=lambda profs: 'profile1' not in profs, ...)
Получение бинов
Для получения бинов из контекста можно воспользоваться несколькими способами
Через зависимости
В параметр dependencies декоратора или функции необходимо задать словарь, где ключ - имя параметра в методе-фабрике или конструкторе, а значение - id нужного бина
@bean(..., dep1='bean1', dep2='bean2')
class Bean3:
def __init__(self, dep1, dep2):
...
@bean(..., dep1='bean1', dep2='bean2')
def factory_bean3(dep1, dep2):
...
ctx.register_bean(..., dependencies={
'dep1': 'bean1',
'dep2': 'bean2',
}, ...)
Напрямую
Можно получить бин из самого объекта контекста по id или классу бина:
bean3 = ctx.beans['bean3']
bean3 = ctx.beans[Bean3]
Причём, если запросить бин по классу, то будут искать все бины, которые являются объектом этого класса или его потомков.
Дополнительно
Узнать, существует ли бин с такой ссылкой, можно через
Bean3 in ctx.beans
Причём если существует несколько подходящих бинов, то будет возвращено True
Если необходимо узнать количество бинов в контексте, то используется
len(ctx.beans)
Файл конфигурации
pyddi позволяет задавать часть конфигурации через специальный YAML-файл. Его формат:
pyddi:
profiles:
active:
- profile1
- ...
beans:
bean_id1: bean1
...
profiles.active
- активные профили
beans
- бины базовых типов (строки, списки, словари и т.д.)
Состояние разработки
pyddi находится на стадии ранней разработки, что значит, что имена, сигнатуры и пути классов, методов и др. могут меняться без объявления.
Если у Вас есть какие-либо пожелания/замечания по поводу библиотеки или хотите просто пообщаться: Telegram или в Github Issues
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.