/ src / evidently / ui / service / components / base.py
base.py
  1  from abc import ABC
  2  from typing import Any
  3  from typing import Callable
  4  from typing import ClassVar
  5  from typing import Dict
  6  from typing import Generic
  7  from typing import List
  8  from typing import Literal
  9  from typing import Optional
 10  from typing import Type
 11  from typing import TypeVar
 12  from typing import overload
 13  
 14  from litestar import Litestar
 15  from litestar import Router
 16  from litestar.di import Provide
 17  from litestar.types import ControllerRouterHandler
 18  from litestar.types import ExceptionHandlersMap
 19  from litestar.types import Middleware
 20  
 21  from evidently._pydantic_compat import Extra
 22  from evidently.legacy.ui.utils import parse_json
 23  from evidently.pydantic_utils import PolymorphicModel
 24  
 25  SECTION_COMPONENT_TYPE_MAPPING: Dict[str, Type["Component"]] = {}
 26  
 27  
 28  T = TypeVar("T", bound="Component")
 29  
 30  
 31  class AppBuilder:
 32      def __init__(self, context: "ComponentContext"):
 33          self.context = context
 34          self.dependencies: Dict[str, Provide] = {}
 35          self.route_handlers: List[ControllerRouterHandler] = []
 36          self.api_route_handlers: List[ControllerRouterHandler] = []
 37          self.exception_handlers: ExceptionHandlersMap = {}
 38          self.middlewares: List[Middleware] = []
 39          self.type_decoders: List[tuple[Callable[[Any], bool], Callable[[Any, Any], Any]]] = []
 40          self.kwargs: Dict[str, Any] = {}
 41  
 42      def build_api_router(self):
 43          return Router(path="/api", route_handlers=self.api_route_handlers)
 44  
 45      def build(self) -> Litestar:
 46          api_router = self.build_api_router()
 47          return Litestar(
 48              route_handlers=[api_router] + self.route_handlers,
 49              exception_handlers=self.exception_handlers,
 50              dependencies=self.dependencies,
 51              middleware=self.middlewares,
 52              type_decoders=self.type_decoders,
 53              **self.kwargs,
 54          )
 55  
 56  
 57  class ComponentContext:
 58      @overload
 59      def get_component(self, type_: Type[T], required: Literal[True] = True) -> T:
 60          pass
 61  
 62      @overload
 63      def get_component(self, type_: Type[T], required: Literal[False] = False) -> Optional[T]:
 64          pass
 65  
 66      def get_component(self, type_: Type[T], required: bool = True) -> Optional[T]:
 67          raise NotImplementedError
 68  
 69  
 70  class Component(PolymorphicModel, ABC):
 71      __section__: ClassVar[str] = ""
 72      __require__: ClassVar[List[Type["Component"]]] = []
 73      __priority__: ClassVar[int] = 0
 74      priority: Optional[int] = None
 75  
 76      def get_priority(self) -> int:
 77          return self.priority if self.priority is not None else self.__priority__
 78  
 79      def get_requirements(self) -> List[Type["Component"]]:
 80          return self.__require__
 81  
 82      class Config:
 83          extra = Extra.forbid
 84          alias_required = False
 85          is_base_type = True
 86  
 87      def __init_subclass__(cls):
 88          super().__init_subclass__()
 89          if cls.__section__:
 90              SECTION_COMPONENT_TYPE_MAPPING[cls.__section__] = cls
 91  
 92      def get_dependencies(self, ctx: ComponentContext) -> Dict[str, Provide]:
 93          return {}
 94  
 95      def get_middlewares(self, ctx: ComponentContext):
 96          return []
 97  
 98      def get_route_handlers(self, ctx: ComponentContext):
 99          return []
100  
101      def get_api_route_handlers(self, ctx: ComponentContext):
102          return []
103  
104      def apply(self, ctx: ComponentContext, builder: AppBuilder):
105          builder.dependencies.update(self.get_dependencies(ctx))
106          builder.middlewares.extend(self.get_middlewares(ctx))
107          builder.route_handlers.extend(self.get_route_handlers(ctx))
108          builder.api_route_handlers.extend(self.get_api_route_handlers(ctx))
109  
110      def finalize(self, ctx: ComponentContext, app: Litestar):
111          pass
112  
113  
114  DT = TypeVar("DT")
115  
116  
117  class FactoryComponent(Component, Generic[DT], ABC):
118      dependency_name: ClassVar[str]
119      use_cache: ClassVar[bool] = True
120      sync_to_thread: ClassVar[Optional[bool]] = False
121  
122      def dependency_factory(self) -> Callable[..., DT]:
123          raise NotImplementedError(self.__class__)
124  
125      def get_dependencies(self, ctx: ComponentContext) -> Dict[str, Provide]:
126          return {
127              self.dependency_name: Provide(
128                  self.dependency_factory(), use_cache=self.use_cache, sync_to_thread=self.sync_to_thread
129              )
130          }
131  
132  
133  class ServiceComponent(Component):
134      host: str = "127.0.0.1"
135      port: int = 8000
136  
137      def get_dependencies(self, ctx: ComponentContext) -> Dict[str, Provide]:
138          # todo: maybe not put utils here
139          return {
140              "parsed_json": Provide(parse_json, sync_to_thread=False),
141          }