Skip to main content

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


Download files

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

Source Distribution

futile_di_azya0-0.2.1.tar.gz (9.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

futile_di_azya0-0.2.1-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

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

Hashes for futile_di_azya0-0.2.1.tar.gz
Algorithm Hash digest
SHA256 61fd44c96a9b9a83aec7258e46298c6eb6d18bc03043a9f47a27b182aa893030
MD5 fda4d4f14cae4739e1100fb68e4f4f08
BLAKE2b-256 4bca4a261cb042212f235de846125c8ae7b46edde455c69bae95b38022c03efc

See more details on using hashes here.

File details

Details for the file futile_di_azya0-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for futile_di_azya0-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8b432f1c074013161d4f7c233d01357922e3568f048ae3d00d0c8235f534cf62
MD5 0c2d04d9c7143a25baca6d833777b10b
BLAKE2b-256 efd9b5d99c1d21d7a29c85341f21896fe85fa55c8ed4b153a17924b6bbfcbd03

See more details on using hashes here.

Supported by

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