Skip to main content

Python Dependency Injection library

Project description

Введение

pybeandi - Это библиотека, реализующая внедрение зависимостей в Python

Описание

Главным объектом во всей библиотеке является бин (bean). Бины определяются, создаются и управляются контекстом (BeanContext). Вы можете внедрять их в другие бины или запрашивать их от самого контекста. Тем самым ваш код больше не знает, какие именно объекты к нему приходят, что уменьшает связанность объектов и упрощает поддержку и читаемость, так как теперь управлением вашими объектами и их зависимостями управляет контекст.

Установка

pip install pybeandi

Пример

import abc
import sys

from pybeandi.context import BeanContextBuilder
from pybeandi.decorators import bean, after_init
from pybeandi.model import wildcard_ref
from pybeandi.util import is_active


class Service(abc.ABC):

    @abc.abstractmethod
    def hello(self) -> str:
        pass


@bean(bean_id='service', profile_func=is_active('service1'))
class My1Service(Service):

    def hello(self) -> str:
        return 'Hello from 1 service'


@bean(bean_id='service', profile_func=is_active('service2'))
class My2Service(Service):

    def hello(self) -> str:
        return 'Hello from 2 service'


@bean(bean_id='service', profile_func=is_active('service3'), nums=wildcard_ref('[123]'))
class My3Service(Service):

    def __init__(self, nums):
        print('Init service 3', nums)

    def hello(self) -> str:
        return f'Hello from 3 service'

    @after_init(message='after_init_message'))
    def after_init(self, message):
        print("From 3 service", message)


@bean()
def func_bean():
    pass


if __name__ == '__main__':
    ctx_builder = BeanContextBuilder()
    ctx_builder.add_as_bean("after_init_message", "After init called!")
    ctx_builder.add_as_bean('1', 1)
    ctx_builder.add_as_bean('2', 2)
    ctx_builder.add_as_bean('3', 3)
    ctx_builder.profiles.add('service3')
    ctx_builder.import_module(sys.modules[__name__])
    with ctx_builder.init() as ctx:
        print(ctx.beans['service'].hello())
        print('service' in ctx.beans)
        print(ctx.beans)
        print(ctx.profiles)
        print(set(ctx.beans.values()))

Теория

Каждый бин обладает:

  • уникальным идендификаторов (bean_id), который однозначно определяет его внутри контекста;
  • конструктором (factory_func), который создаёт объект бина
  • словарём зависимостей (dependencies), который указывает, какому полю конструктора соответствует какой бин
  • функцией профиля (profile_func), которая принимает на вход список всех активных профилей и возвращает, нужно ли создавать бин

Краткое описание инициализации контекста

Для создания контекста необходимо создать и настроить BeanContextBuilder, затем вызвать его метод init(), результатом которого будет сам BeanContext

ctx_builder = BeanContextBuilder()
ctx_builder.load_yaml(Path('pybeandi.yaml'))
ctx_builder.profiles.add('service3')
ctx_builder.import_module(sys.modules[__name__])
with ctx_builder.init() as ctx:
   print(list(ctx.beans.values()))
   ...

Способы определения объектов

Существует несколько способов сообщить контексту, что данный объект является бинов и отдать его под его контроль.

Напрямую через метод

Этот метод в основном используется внутри библиотеки.

Синтаксис:

ctx_builder.register_bean(bean_id: str,
                          factory_func: Callable[..., Any],
                          dependencies: Dict[str, str],
                          profile_func: ProfileFunction = 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,
      profile_func: ProfileFunction = lambda profs: True,
      **depends_on: Dict[str, str])
class MyService(Service):

    def __init__(self, dep1, dep2, ...):
        ...

    ...

Через метод-фабрику

Этот метод полезен тогда, когда нет доступа к самому классу или для создания иного метода создания бина, чем у его конструктора.

Синтаксис:

@bean(bean_id: str,
      profile_func: ProfileFunction = lambda profs: True,
      **depends_on: Dict[str, str])
def factory_bean(dep1, dep2, ...):
    ...
    return obj

Автопоределения id

pybeandi поддерживают функцию автоопределения id бина, то есть необязательно явно указывать его id в декораторе. В этом случае он будет создан на основе название класса/функции.

@bean() # id = my_service1
class MyService1:
    pass

@bean() #id = my_service2
def my_service2():
    pass

Важно

Для импорта бинов из различных модулей (в том числе из текущего) нужно указать модули

# Для внешнего модуля
import module_name
ctx_b.import_module(module_name)
# Для текущего модуля
ctx_builder.import_module(sys.modules[__name__])

Профили

Профили - это механизм управления инициализации сразу группой бинов в зависимости от настроек. Так, например, если у вас есть две реализации одного интерфейса (абстрактного класса), которые используются или в среде разработки, или в среде на "боевой" машине, то эти реализации могут иметь разные профили, которые определяют что нужно загружать: реализацию для разработчиков или для клиентов.

Задание профилей контекста

ctx_builder.profiles.add('profile1')

Задание профилей бина декоратором

Функция профиля принимает множество активных профилей и возвращает bool, что надо инициализировать бин или нет. Также есть несколько уже готовых функций для создания таких функций профиля

@bean(..., profile_func=all_active({'profile1', ...}), ...)  # Все профили активные
@bean(..., profile_func=is_active({'profile1', ...}), ...)  # Профиль активен
@bean(..., profile_func=is_not_active({'profile1', ...}), ...)  # Профиль не активен

Если же условие сложнее, то необходимо задать функцию. Так, например, если условием является то, что профиль 'profile1' нету в активных, то

@bean(..., profile_func=lambda profs: 'profile1' not in profs, ...)

Получение бинов

Для получения бинов из контекста можно воспользоваться несколькими способами

Через зависимости

В параметр dependencies декоратора или функции необходимо задать словарь, где ключ - имя параметра в методе-фабрике или конструкторе, а значение - ссылк на нужный бин.

@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',
}, ...)

Напрямую

Можно получить бин из самого объекта контекста по ссылке на бин:

bean3 = ctx.beans['bean3']
beans_set = ctx.beans[wildcard_ref('command_*')]

Типы ссылок на бин

Ссылками на бины могуть быть:

  • идентификатор бина str (аналог IdBeanRef)
  • объект IdBeanRef(bean_id: str) (сокращённо id_ref). Аргумент optional=False определяет, можно ли не вставлять бин, если он не определён в контексте
  • объект WildcardBeanRef(wildcard: str) - представляет зависимость на множество бинов c соответствующим wildcard id. Например, ссылка WildcardBeanRef([123]) ссылкается на множество всех бинов c id 1, 2 или 3 (сокращённо wildcard_ref)
  • объект RegexBeanRef(regex: str) - зависимость на множество бинов по регулярному выражению (сокращённо regex_ref)

Дополнительно

ctx.beans # доступ к бинам
ctx.beans.ids() # множество id бинов
ctx.beans.values() # список бинов без id

Узнать, существует ли бин с такой ссылкой, можно через

'bean3' in ctx.beans

Причём если существует несколько подходящих бинов, то будет возвращено True

Менеджер контекста

BeanContext может быть использован в менеджере контекста:

with ctx_builder.init() as ctx:
    print(list(ctx.beans.values()))

Файл конфигурации

pybeandi позволяет задавать часть конфигурации через специальный YAML-файл. Его формат:

Также возможно использование переменных среды в конфигурации, тогда перед значением необходимо написать !ENV, а переменные среды указывать как ${<Перемен. среды>}

pybeandi:
    profiles:
      active:
        - profile1
        - ...
    beans:
      bean_id1: bean1
      service_vals:
        - val1
        - val2
      service_dict:
        key1: val1
        key2: val2
      db_url: !ENV jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_PATH}

profiles.active - активные профили

beans - бины базовых типов (строки, списки, словари и т.д.)

Состояние разработки

pybeandi находится на стадии ранней разработки, что значит, что имена, сигнатуры и пути классов, методов и др. могут меняться без объявления.

Если у Вас есть какие-либо пожелания/замечания по поводу библиотеки или хотите просто пообщаться: 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.

Source Distribution

pybeandi-0.2.4.tar.gz (17.9 kB view details)

Uploaded Source

Built Distribution

pybeandi-0.2.4-py3-none-any.whl (17.0 kB view details)

Uploaded Python 3

File details

Details for the file pybeandi-0.2.4.tar.gz.

File metadata

  • Download URL: pybeandi-0.2.4.tar.gz
  • Upload date:
  • Size: 17.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.2 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.8.12

File hashes

Hashes for pybeandi-0.2.4.tar.gz
Algorithm Hash digest
SHA256 a8f1e4d781ee0457888019472c7b877fa92de8621071c30590d2d62ff299edcb
MD5 22a950cb4f522eddb509e9e3e3f91cf2
BLAKE2b-256 4e794bea8e235718f89676735dc897d4b9294a771e456287fba33afbb48f6f57

See more details on using hashes here.

File details

Details for the file pybeandi-0.2.4-py3-none-any.whl.

File metadata

  • Download URL: pybeandi-0.2.4-py3-none-any.whl
  • Upload date:
  • Size: 17.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/34.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.63.0 importlib-metadata/4.11.2 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.8.12

File hashes

Hashes for pybeandi-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 e5741b50f1ea01e42a147c5a5b3172a1f8820f8f97b5ea97af3cca9d16cd75b7
MD5 ebc07ef1b02044a49d0560110906f459
BLAKE2b-256 594017103f908f45cf57b06ef5127357a1930355882b9d1a71be80c023e3ebd4

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page