Usage recommandation to add checks on provided value
Opened this issue · 3 comments
I am trying to implement a behavior that is really close to ThreadLocalSingleton scopes.
I have a container that is created when app starts. It holds data about the current customer.
So when the app starts, the value is None.
But at runtime, I want to forbid any injection if the current value is None.
Of course, I would like to keep this DI logic near containers.
(No if not context: ... in MyService below)
I am currently able to do this kind of tricks by having 2 providers like below.
my_context will be overrode within each requests.
_safe_my_context will validate it before being injected in other components.
I'm not 100% comfortable with this.
I am feeling like there's room for a DelegatedThreadLocalSingleton that could accept MyContext | None but would provide only a MyContext. Just didn't find examples of Delegate... of how to implement it properly.
Thanks for the lib!
# app/my_context.py
@dataclass
class MyContext:
customer_id: str
# app/my_service.py
class MyService:
def __init__(self, context: MyContext):
...
# app/containers.py
def _default_context() -> MyContext | None:
return None
def _ensure_context(context: MyContext | None) -> MyContext:
if context is None:
msg = "Context not set"
raise ValueError(msg)
return context
class MyContainer(containers.DeclarativeContainer):
my_context = providers.ThreadLocalSingleton(_default_context)
_safe_my_context = providers.Callable(_ensure_context, context=my_context)
some_service = providers.Factory(MyService, _safe_my_context)This looks like perfect use case for contextvars. In combination with DI, you can have a Resource provider to init/reset the ContextVar and then use it in your code instead of injections (or maybe move resource injection to middleware?). There is also ContextLocalSingleton. Probably not exactly what you are looking for, but it synergizes well with modern web frameworks.
Interesting, didn't know contextvars ! Effectively, looks like it solve this example.
Was able to do something like:
my_context_var: ContextVar[MyContext] = ContextVar("my_context")
class MyContainer(containers.DeclarativeContainer):
my_context = providers.Callable(my_context_var.get)
some_service = providers.Factory(MyService, my_context)Then I can manage the ContextVar in my controller somewhere else:
def request_handler(rqst):
reset_token = my_context_var.set(MyContext(customer_id=rqst.customer_id))
try:
process(rqst)
finally:
my_context_var.reset(reset_token)That being said,
I am still curious to know whether it's possible or not to have a custom validation logic on the provided value, beyond the provided_type check already supported.
(Something that would require some initialization before behind available, so being Optional typed in the provider)
I thought (but not tried yet) about overriding the set_provides of a provider to add custom logic.
Not sure about the impact (e.g. eagerly resolved or not ? ...)
Any idea ?
I do not think there is an elegant way to handle the validation the way to described. Indeed you can override set_provides to add a wrapper that will perform validation on the result, but at this point why not being explicit and pass it (wrapped function) to the Factory provider itself.