Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for pybeandi, version 0.0.4
Filename, size File type Python version Upload date Hashes
Filename, size pybeandi-0.0.4-py3-none-any.whl (8.7 kB) File type Wheel Python version py3 Upload date Hashes View hashes
Filename, size pybeandi-0.0.4.tar.gz (10.7 kB) File type Source Python version None Upload date Hashes View hashes

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page