Dependency Injection: build mechanics for contextualized scope
lhaze opened this issue · 0 comments
lhaze commented
A contextualized scope is the scope that has its context bound to some related object. A request scope has its context bound to some request, and session or a thread one -- accordingly.
Let's refine the key DI concepts:
- request is describing what is needed
- resolution is describing the way to construct the instance of a dependency
- scope is describing how many instances to construct and how long should they live
- context is describing how the instance is related to its scope
from dataclasses import dataclass
import typing as t
from enum import Enum
from functools import partial
from pca.utils.dependency_injection import Container
Constructor = t.Union[t.Type, t.Callable]
Kwargs = t.Dict[str, t.Any] # keyword-arguments of a Constructor
# empty interfaces, because for now no other requirements are known
IRequest = t.NewType('IRequest', object)
ISession = t.NewType('ISession', object)
@dataclass(frozen=True)
class DIRequest:
"""
Describes the way a component might be requested from the Container.
It can be at one of two states:
* indeterminate - the context value will become determined when you bound its value to
the state of some target (a DI component) with `DIRequest.determine` method.
* determined - the context is static, nothing is to be determined.
You can check the container with the DIRequest only when the context is determined.
NB: DIRequest is immutable, so calling `DIRequest.determine` on a indeterminate context will
produce a new DIRequest instance.
:cvar interface: an interface of the dependency. It's often used along with annotations about
the variable/argument that requests the dependency.
:cvar name: an arbitrary string describing dependency (an alternative to `interface` argument).
Simple to use, but it doesn't give any information about its interface.
:cvar qualifier: an object of any type that adds a finer grade granularity about
the dependency; i.e. you may want a component of `IDao` interface: not just anyone, but
the one that is modeling some specific schema; the qualifier describes the latter
condition.
:cvar get_qualifier: a function that can produce a qualifier from a component. Signature of
(target: Component) -> qualifier: str
"""
name: str = None
interface: t.Type = None
qualifier: t.Any = None
get_qualifier: t.Callable[[t.Any], t.Any] = None
class Scopes(Enum):
"""
Describes how often instances should be constructed and how long should they live.
INSTANCE: every time the dependency is requested, a new instance is created
REQUEST: once per request, whatever might mean the concept of 'request'
SESSION: once per session, whatever might mean the concept of 'session'
THREAD: once per thread, like in multithreading module
SINGLETON: once per its container lifetime
"""
INSTANCE: 'Scopes' = partial(Container.instance_scope)
REQUEST: 'Scopes' = partial(Container.request_scope)
SESSION: 'Scopes' = partial(Container.session_scope)
THREAD: 'Scopes' = partial(Container.thread_scope)
SINGLETON: 'Scopes' = partial(Container.singleton_scope)
@dataclass(frozen=True)
class DIContext:
"""
Describes how the instance is related to its scope:
:cvar request_object: an identifier of the request bound (iff it exists)
:cvar session_object: an identifier of the session bound (iff it exists)
"""
request: DIRequest
request_object: t.Optional[RequestIdentifier] = None
session_object: t.Optional[SessionIdentifier] = None
@dataclass(frozen=True)
class DIResolution:
"""
Describes what has been registered as a dependency:
:cvar constructor: a type or a callable that builds the dependency instance
:cvar kwargs: a dict that specifies keyword arguments for the dependency constructor
"""
constructor: Constructor
kwargs: Kwargs = None