Symbiont — мой микро-фреймворк для управления зависимостями
С огромным запозданием по датам я хочу вам коротко представить небольшой проект, который случайно родился во время работы над собственной системой управления умным домом.
Как можно заметить по датам публикаций в блоге, я отсутствовал более месяца, что связано с болезнями и пережитой операцией. Однако теперь я встал на ноги и снова готов генерировать контент. 🙂
Сразу уточню, что кейс весьма специфический, поэтому скорее всего на очередном вашем проекте больше подойдут библиотеки и фреймворки использующие другую архитектуру (тот же Depends у FastAPI). Однако в моём случае мне показалось удобным использовать именно этот подход.
Начнём с установки:
poetry add symbiont
Сам модуль состоит из модулей (Module), Injectable (даже пытаться перевести это не стану) и собственно DependencyInjector'а, который занимается логикой инициализации всего этого добра.
Предполагается, что логика ваших приложений разделена на модули, в каждом из которых есть некая часть, которую используют другие части. Не совсем понятно? Давайте на примере:
from symbiont import Module, Injectable, DependencyInjector
class UsersRepository(Injectable):
...
class UsersService(Injectable):
users: UsersRepository
...
class UsersModule(
Module,
providers=[UsersRepository, UsersService]
):
...
Здесь у нас знакомая ситуация. Предположим, что UsersRepository содержит в себе работу с БД, которая затем используется в бизнес-логике внутри UsersService (ведь мы инжектим UsersRepository как users). Ну и объединяется всё это в общий модуль UsersModule через параметр providers.
Где-то в другом месте мы создаём наш рутовый модуль, который импортирует модули:
class RootModule(
Module,
imports=[UsersModule]
):
users: UsersService
injector = DependencyInjector()
root = injector.initialize(RootModule)
Инициализация через DependencyInjector создаёт объекты всех модулей, автоматически инициализируя и вставляя Injectable зависимости между ними.
Напоследок покажу небольшую уловку, которая использует children свойство модуля, содержащее все подмодули и Injectable, чтобы вызывать на них переданный метод:
class ExampleModule(
Module,
providers=[...]
):
async def start(self):
...
class RootModule(
Module,
imports=[ExampleModule, ...]
):
async def call(self, method: str) -> None:
await self._waterfall_call(self, method)
async def _waterfall_call(self, module: Module, attr: str) -> None:
for submodule in (m for m in module.children if isinstance(m, Module)):
await self._waterfall_call(submodule, attr)
if module is not self:
if method := getattr(module, attr, None):
await method()
root = DependencyInjector().initialize(RootModule)
root.call('start')
Пользуйтесь с осторожностью, код доступен на GitHub. Любые предложения, исправления и улучшения приветствуются.