Closed nightblure closed 1 month ago
@nightblure привет, спасибо)
по идее здесь аналогично инжектору работает. Доку еще не написал, но можно в тестах посмотреть https://github.com/modern-python/that-depends/blob/main/tests/providers/test_providers_overriding.py
Получается в провайдер подкладывается мок и в последующих резолвингах будет отдаваться этот мок.
override провайдером делать не стал, нам не нужно было такое. Туда только объект готовый можно положить, который вернется, как есть. Если нужно в райтайме собирать контейнер, то можно попробовать динамически добавить провайдеры https://github.com/modern-python/that-depends/blob/main/tests/test_dynamic_container.py Или selector использовать https://that-depends.readthedocs.io/providers/selector.html
Спасибо за ответ! Почитал тесты и там кажется не совсем то, о чем я спрашивал. Там вы тестируете простейший кейс с отдачей мока, который был задан - с этим все понятно. Я имел в виду кейс с переопределением провайдеров для некоторой функции, у которой имеются дефолтные аргументы с провайдерами. Возможно, мне стоит привести небольшой кусок код, чтобы стало понятнее, могу попробовать этот кейс обкатать с вашей имплементацией и потом отписаться, если там не сработает
ну по идее нигде значения не подставляются и везде остаются ссылки на сами провайдеры. Поэтому после оверрайдинга не надо ходить по коду и заменять значения. Они меняются автоматом, так как при получении значения вызовется провайдер, в котором уже зашит переопределенный объект
@lesnik512 привет)
Я немного потестил и пришел к выводу, что отчасти мой вопрос выше неактуален. Отчасти, потому что мой кейс вроде бы работает как ожидается, кроме случаев с синглтонами. И здесь либо у меня неправильные ожидания, либо что-то работает не так
Краткое описание проблемы в том, что в некоторых кейсах синглтон-провайдер отдает закешированное значение вместо того, чтобы быть вновь проинициализированным. Я понимаю, что синглтон задуман как объект, который должен быть инициализирован лишь однажды, но мне кажется в кейсах с переопределением эта логика может быть неактуальна. Привожу код, в комментах отметил места с поломками. При этом с фэктори-провайдером оба кейса будут работать, тоже отметил комментарием внутри di-контейнера
from dataclasses import dataclass
from that_depends import Provide, BaseContainer, providers, inject
@dataclass
class Settings:
redis_url: str = 'url_1'
class Redis:
def __init__(self, url: str):
self.url = url
class DIContainer(BaseContainer):
MOCK_REDIS_URL = 'url_mock'
settings = providers.Singleton(Settings)
redis = providers.Singleton(Redis, url=settings.redis_url) # CASES DOESNT WORK
# redis = providers.Factory(Redis, url=settings.redis_url) # CASES WORKS
def get_mock_settings():
mock_settings = Settings(redis_url=DIContainer.MOCK_REDIS_URL)
return mock_settings
@inject
def func(redis=Provide[DIContainer.redis]):
return redis.url
def case_1():
mock_settings = get_mock_settings()
DIContainer.settings.override(mock_settings)
redis_url = func()
assert redis_url == DIContainer.MOCK_REDIS_URL
DIContainer.settings.reset_override()
redis_url = func()
assert redis_url == 'url_1' # ASSERTION ERROR
def case_2():
assert DIContainer.redis.sync_resolve().url == 'url_1'
mock_settings = get_mock_settings()
DIContainer.settings.override(mock_settings)
redis_url = func()
assert redis_url == DIContainer.MOCK_REDIS_URL # ASSERTION ERROR
if __name__ == '__main__':
case_1()
# case_2()
@nightblure с ходу не понял) завтра попробую это в тест кейс оформить
@nightblure понял, про что ты. Ушел думать)
считаю, что это валидное поведение. Просто если что-то оверрайдишь, нужно еще tear_down делать, чтобы сбросить состояние контейнера
import typing
from dataclasses import dataclass
from that_depends import Provide, BaseContainer, providers, inject
DEFAULT_REDIS_URL: typing.Final = 'url_1'
MOCK_REDIS_URL: typing.Final = 'url_2'
@dataclass
class Settings:
redis_url: str = DEFAULT_REDIS_URL
class Redis:
def __init__(self, url: str):
self.url = url
class DIContainer(BaseContainer):
settings = providers.Singleton(Settings)
redis = providers.Singleton(Redis, url=settings.redis_url)
@inject
def func(redis: Redis = Provide[DIContainer.redis]):
return redis.url
async def test_case_1():
DIContainer.settings.override(Settings(redis_url=MOCK_REDIS_URL))
assert func() == MOCK_REDIS_URL
DIContainer.settings.reset_override()
await DIContainer.tear_down()
assert func() == DEFAULT_REDIS_URL # ASSERTION ERROR
async def test_case_2():
DIContainer.reset_override()
await DIContainer.tear_down()
assert DIContainer.redis.sync_resolve().url == DEFAULT_REDIS_URL
DIContainer.settings.override(Settings(redis_url=MOCK_REDIS_URL))
await DIContainer.tear_down()
redis_url = func()
assert redis_url == MOCK_REDIS_URL # ASSERTION ERROR
да, все так) здесь требуется дополнительный явный "сброс" состояния. но это поведение показалось мне не очень логичным с пользовательской точки зрения по следующим причинам:
вызывая метод override
как пользователь я сразу ожидаю переопределения провайдера, независимо от того какой конкретно провайдер сейчас я использую. В моем примере этого не происходит, ведь помимо override
на самом деле нужно вызывать tear_down
;
то же самое с методом reset_override
: вызывая его, я ожидаю "сброс" состояния провайдера. Но фактически сброс не произойдет, если я не сделаю дополнительный вызов tear_down
;
в подобных кейсах "сброс" состояния не потребуется, например, если будет использован провайдер Factory
(как и для других провайдеров типа Factory
, для которых по сути "сбрасывать" фактически нечего)
моя логика опирается только на нейминг методов, и я понимаю, что нейминг можно интерпретировать по-разному и я могу заблуждаться)
Согласен, что кто-то может на этом подорваться) но делать автоматический tear down при overriding'е тоже не очень решение - провайдер не знает ничего о контейнере и других зависимостях.
Думаю, достаточно будет эти риски подсветить в доке по оверрайдингу.
валидное решение) спасибо! по ишью тогда вопросов больше нет
Привет!
Хорошая работа, поставил звездочку. Сам делаю свою облегченную копию dependency-injector и хочу задать вопрос по поводу переопределения зависимостей в тестах
Немного контекста: пользуясь инжекторами мы вынуждены указывать параметрам функций дефолтные аргументы (обычно эти функции это хендлеры эндпоинтов). И как мы знаем, сначала происходит импорт всех файлов с кодом и функции в питоне создаются всего один раз, при этом туда "намертво" прикрепляются дефолтные значения типа
param=Provide[SomeContainer.some_provider]
илиparam=SomeContainer.some_provider
И вопрос следующий: есть ли в вашем фреймворке фича переопределения зависимостей? В dependency-injector этот метод называется override_providers (можно посмотреть пример кода в https://python-dependency-injector.ets-labs.org/examples/fastapi-sqlalchemy.html). Это чуть ли не основная фича ioc/di-контейнера имхо, но здесь я ее не нашел, возможно, недосмотрел. Нашел только метод переопределения у самого провайдера, но не понимаю зачем это нужно и в каких кейсах используется, если у вас нет фичи "связывания" или "wire" из того же dependency-injector. Wire здесь упомянут потому, что без него кажется мы никак не сможем переопределить в тестах нужные зависимости, потому что как я упомянул выше дефолтные значения аргументов были уже определены и их без условных вайров/патчей никак не подменить