A small dependency injection library
Project description
futile_di
pip install futile-di-azya0
Описание
futile_di - маленькая библиотека для инъекции зависимостей, не привязанной к реализации конкретного фреймворка (вроде Depends из FastAPI).
Допустим у нас есть генератор для контроля цикла жизни сессии:
def get_session():
with create_session() as session:
yield session
Тогда, чтобы получить сессию нам нужно:
- Создавать сильную зависимость
Или
- Хранить созданный генератор на стеке функции-консьюмера
- Вручную вызывать
next(...)для получения сессии
def get_user(generator=get_session()):
session = next(generator)
...
Вместо этого можно использовать механизм инъекции зависимости, который будет делать это "под капотом":
from futile_di_azya0 import Depends, inject
def get_session() -> Session:
with create_session() as session:
yield session
@inject
def get_user(session: Session = Depends(get_session)):
...
На данный момент @inject и Depends поддерживают:
- синхронные функции
- асинхронные функции
- генераторы
- асинхронные генераторы
Реализация
inject - декоратор для синхронной/асинхронной функции. Он ищет класс Depends (main) как среди параметров функции по умолчанию, так и среди переданных значений. Далее для каждого main он собирает стек вложенных Depends, чтобы получить значение для текущего (main), параллельно сохраняя контекст до конца выполнения функции:
При анализе вложенных main Depends ищутся только среди параметров по умолчанию
Поддерживаемые типы для обёртки в Depends:
class DependsType(Enum):
VALUE = 0,
SYNC = 1,
ASYNC = 2,
GENERATOR = 3,
ASYNC_GENERATOR = 4,
Если это VALUE, то он просто возвращает значение. Если это SYNC или ASYNC, то он вызывает их, получает значение и возвращает его. Если это GENERATOR или ASYNC_GENERATOR, то он получает первое значение, сохраняет генераторы до конца выполнения функции, а потом вызывает ещё один next(...)/await anext(...), для того, чтобы генератор мог завершить свой контекст. При этом, если исключение StopIteration/StopAsyncIteration не было получено, то исключения не будет.
В конечном итоге генераторы удаляются, чтобы контекст мог закрыться
Пример
class Context(AbstractAsyncContextManager):
def __init__(self):
super().__init__()
self.is_open = False
async def __aenter__(self) -> AbstractContextManager:
await asyncio.sleep(0.01)
self.is_open = True
return self
async def __aexit__(self, exc_type, exc_value, traceback):
await asyncio.sleep(0.01)
self.is_open = False
return await super().__aexit__(exc_type, exc_value, traceback)
context = Context()
async def one_generator() -> AsyncGenerator[int]:
assert not context.is_open
async with context:
assert context.is_open
await asyncio.sleep(0.01)
yield 1
class SomeClass:
def __init__(self, value: int):
self.value = value
def get(self) -> int:
return self.value
def get_instance(value: int = Depends(one_generator)) -> SomeClass:
assert context.is_open
return SomeClass(value)
@inject
async def some_function(arg: SomeClass = Depends(get_instance)) -> int:
await asyncio.sleep(0.01)
assert context.is_open
return arg.get() + 1
assert asyncio.run(some_function()) == 2
assert not context.is_open
Допустим, у нас есть некоторый контекст асинхронный Context. Мы хотим, чтобы:
- Зависимость с этим контекстом имела некоторую глубину вложенности
- Контекст сохранялся до конца выполнения функции, которую мы обернули в
@inject - Асинхронные зависимости могли зависеть от синхронных и наоборот
Этот код был взят из теста к библиотеке. Он показывает работоспособность всех вышеперечисленных запросов.
Тесты
Для futile_di было написано несколько тестов. Запустить их можно через библиотеку pytest из корневой директории командой:
pytest -s
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.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file futile_di_azya0-0.2.1.tar.gz.
File metadata
- Download URL: futile_di_azya0-0.2.1.tar.gz
- Upload date:
- Size: 9.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
61fd44c96a9b9a83aec7258e46298c6eb6d18bc03043a9f47a27b182aa893030
|
|
| MD5 |
fda4d4f14cae4739e1100fb68e4f4f08
|
|
| BLAKE2b-256 |
4bca4a261cb042212f235de846125c8ae7b46edde455c69bae95b38022c03efc
|
File details
Details for the file futile_di_azya0-0.2.1-py3-none-any.whl.
File metadata
- Download URL: futile_di_azya0-0.2.1-py3-none-any.whl
- Upload date:
- Size: 9.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b432f1c074013161d4f7c233d01357922e3568f048ae3d00d0c8235f534cf62
|
|
| MD5 |
0c2d04d9c7143a25baca6d833777b10b
|
|
| BLAKE2b-256 |
efd9b5d99c1d21d7a29c85341f21896fe85fa55c8ed4b153a17924b6bbfcbd03
|