pcah/python-clean-architecture

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