Package: pybeandi
Введениеpybeandi - Это библиотека, реализующая внедрение зависимостей в PythonОписаниеГлавным объектом во всей библиотеке являетсябин(bean).
Бины определяются, создаются и управляютсяконтекстом(BeanContext).
Вы можете внедрять их в другие бины или запрашивать их от самого контекста.
Тем самым ваш код больше не знает, какие именно объекты к нему приходят,
что уменьшает связанность объектов и упрощает поддержку и читаемость,
так как теперь управлением вашими объектами и их зависимостями управляет контекст.Установкаpip install pybeandiПримерimportabcimportsysfrompybeandi.contextimportBeanContextBuilderfrompybeandi.decoratorsimportbean,after_initfrompybeandi.modelimportwildcard_reffrompybeandi.utilimportis_activeclassService(abc.ABC):@abc.abstractmethoddefhello(self)->str:pass@bean(bean_id='service',profile_func=is_active('service1'))classMy1Service(Service):defhello(self)->str:return'Hello from 1 service'@bean(bean_id='service',profile_func=is_active('service2'))classMy2Service(Service):defhello(self)->str:return'Hello from 2 service'@bean(bean_id='service',profile_func=is_active('service3'),nums=wildcard_ref('[123]'))classMy3Service(Service):def__init__(self,nums):print('Init service 3',nums)defhello(self)->str:returnf'Hello from 3 service'@after_init(message='after_init_message'))defafter_init(self,message):print("From 3 service",message)@bean()deffunc_bean():passif__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__])withctx_builder.init()asctx:print(ctx.beans['service'].hello())print('service'inctx.beans)print(ctx.beans)print(ctx.profiles)print(set(ctx.beans.values()))ТеорияКаждый бин обладает:уникальным идендификаторов (bean_id), который однозначно определяет его внутри контекста;конструктором (factory_func), который создаёт объект бинасловарём зависимостей (dependencies), который указывает, какому полю конструктора соответствует какой бинфункцией профиля (profile_func), которая принимает на вход список всех активных профилей и возвращает, нужно ли создавать бинКраткое описание инициализации контекстаДля создания контекста необходимо создать и настроитьBeanContextBuilder,
затем вызвать его методinit(), результатом которого будет самBeanContextctx_builder=BeanContextBuilder()ctx_builder.load_yaml(Path('pybeandi.yaml'))ctx_builder.profiles.add('service3')ctx_builder.import_module(sys.modules[__name__])withctx_builder.init()asctx:print(list(ctx.beans.values()))...Способы определения объектовСуществует несколько способов сообщить контексту, что данный объект является бинов и отдать его под его контроль.Напрямую через методЭтот метод в основном используется внутри библиотеки.Синтаксис:ctx_builder.register_bean(bean_id:str,factory_func:Callable[...,Any],dependencies:Dict[str,str],profile_func:ProfileFunction=lambdaprofs:True)# Если класс декорирован @beanctx_builder.register_bean_by_class(Service)Добавление вручнуюПолезно для добавления строк, списков или других уже готовых объектов как бинов.Синтаксис:ctx_builder.add_as_bean(bean_id:str,obj:Any)СканированиеЧерез декорирование классаПозволяет задеклорировать бин, не создавая лишних сущностей.Синтаксис:@bean(bean_id:str,profile_func:ProfileFunction=lambdaprofs:True,**depends_on:Dict[str,str])classMyService(Service):def__init__(self,dep1,dep2,...):......Через метод-фабрикуЭтот метод полезен тогда, когда нет доступа к самому классу или для создания иного метода создания бина,
чем у его конструктора.Синтаксис:@bean(bean_id:str,profile_func:ProfileFunction=lambdaprofs:True,**depends_on:Dict[str,str])deffactory_bean(dep1,dep2,...):...returnobjАвтопоределения idpybeandi поддерживают функцию автоопределения id бина,
то есть необязательно явно указывать его id в декораторе.
В этом случае он будет создан на основе название класса/функции.@bean()# id = my_service1classMyService1:pass@bean()#id = my_service2defmy_service2():passВажноДля импорта бинов из различных модулей (в том числе из текущего) нужно указать модули# Для внешнего модуляimportmodule_namectx_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=lambdaprofs:'profile1'notinprofs,...)Получение биновДля получения бинов из контекста можно воспользоваться несколькими способамиЧерез зависимостиВ параметрdependenciesдекоратора или функции необходимо задать словарь,
где ключ - имя параметра в методе-фабрике или конструкторе, а значение - ссылк на нужный бин.@bean(...,dep1='bean1',dep2='bean2')classBean3:def__init__(self,dep1,dep2):...@bean(...,dep1='bean1',dep2='bean2')deffactory_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'inctx.beansПричём если существует несколько подходящих бинов, то будет возвращеноTrueМенеджер контекстаBeanContextможет быть использован в менеджере контекста:withctx_builder.init()asctx:print(list(ctx.beans.values()))Файл конфигурацииpybeandi позволяет задавать часть конфигурации через специальный YAML-файл. Его формат:Также возможно использование переменных среды в конфигурации, тогда перед значением необходимо написать !ENV,
а переменные среды указывать как ${<Перемен. среды>}pybeandi:profiles:active:-profile1-...beans:bean_id1:bean1service_vals:-val1-val2service_dict:key1:val1key2:val2db_url:!ENVjdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_PATH}profiles.active- активные профилиbeans- бины базовых типов (строки, списки, словари и т.д.)Состояние разработкиpybeandi находится на стадии ранней разработки, что значит, что имена, сигнатуры и пути классов, методов и др. могут меняться без объявления.Если у Вас есть какие-либо пожелания/замечания по поводу библиотеки или хотите просто пообщаться:Telegramили вGithub Issues