/ src / evidently / ui / service / local_service.py
local_service.py
  1  import logging
  2  import os
  3  import time
  4  from typing import Dict
  5  from typing import Optional
  6  
  7  import litestar
  8  from litestar import Litestar
  9  from litestar import Request
 10  from litestar import Response
 11  from litestar.di import Provide
 12  from litestar.logging import LoggingConfig
 13  
 14  from evidently.errors import EvidentlyError
 15  from evidently.ui.service.api.artifacts import artifacts_router
 16  from evidently.ui.service.api.llm_judges import llm_judges_router
 17  from evidently.ui.service.api.projects import create_projects_api
 18  from evidently.ui.service.api.projects import projects_api_dependencies
 19  from evidently.ui.service.api.prompts import prompts_router
 20  from evidently.ui.service.api.service import service_api
 21  from evidently.ui.service.api.static import assets_router
 22  from evidently.ui.service.components.base import AppBuilder
 23  from evidently.ui.service.components.base import Component
 24  from evidently.ui.service.components.base import ComponentContext
 25  from evidently.ui.service.components.base import ServiceComponent
 26  from evidently.ui.service.components.dashboard import DashboardComponent
 27  from evidently.ui.service.components.datasets import DatasetComponent
 28  from evidently.ui.service.components.security import NoSecurityComponent
 29  from evidently.ui.service.components.security import SecurityComponent
 30  from evidently.ui.service.components.storage import LocalStorageComponent
 31  from evidently.ui.service.components.storage import StorageComponent
 32  from evidently.ui.service.components.telemetry import TelemetryComponent
 33  from evidently.ui.service.components.tracing import TracingComponent
 34  from evidently.ui.service.config import AppConfig
 35  from evidently.ui.service.config import ConfigContext
 36  from evidently.ui.service.errors import EvidentlyServiceError
 37  
 38  
 39  def evidently_service_exception_handler(_: Request, exc: EvidentlyServiceError) -> Response:
 40      return exc.to_response()
 41  
 42  
 43  def evidently_exception_handler(_: Request, exc: EvidentlyError) -> Response:
 44      return Response(content={"detail": exc.get_message()}, status_code=500)
 45  
 46  
 47  class LocalServiceComponent(ServiceComponent):
 48      debug: bool = False
 49  
 50      @property
 51      def debug_enabled(self) -> bool:
 52          return self.debug or os.environ.get("EVIDENTLY_DEBUG") is not None
 53  
 54      def get_api_route_handlers(self, ctx: ComponentContext):
 55          guard = ctx.get_component(SecurityComponent).get_auth_guard()
 56          return [
 57              create_projects_api(guard),
 58              service_api(),
 59              artifacts_router(guard),
 60              prompts_router(guard),
 61              llm_judges_router(),
 62          ]
 63  
 64      def get_dependencies(self, ctx: ComponentContext) -> Dict[str, Provide]:
 65          from evidently.ui.service.managers.artifacts import ArtifactManager
 66  
 67          deps = super().get_dependencies(ctx)
 68          deps.update(projects_api_dependencies)
 69          deps["artifact_manager"] = Provide(ArtifactManager.provide)
 70          return deps
 71  
 72      def get_route_handlers(self, ctx: ComponentContext):
 73          return [assets_router()]
 74  
 75      def apply(self, ctx: ComponentContext, builder: AppBuilder):
 76          super().apply(ctx, builder)
 77          assert isinstance(ctx, ConfigContext)
 78          builder.exception_handlers[EvidentlyServiceError] = evidently_service_exception_handler
 79          builder.exception_handlers[EvidentlyError] = evidently_exception_handler
 80          builder.kwargs["debug"] = self.debug_enabled
 81          if self.debug_enabled:
 82              log_config = create_logging()
 83              builder.kwargs["logging_config"] = LoggingConfig(**log_config)
 84  
 85  
 86  def create_logging() -> dict:
 87      logging.Formatter.converter = time.gmtime
 88      return {
 89          "version": 1,
 90          "log_exceptions": "always",
 91          "disable_existing_loggers": False,
 92          "formatters": {
 93              "default": {
 94                  "()": "logging.Formatter",
 95                  "format": "%(asctime)s %(levelname)-8s %(name)-15s %(message)s",
 96                  "datefmt": "%Y-%m-%dT%H:%M:%SZ",
 97              },
 98              "access": {
 99                  "()": "logging.Formatter",
100                  "format": "%(asctime)s %(levelname)-8s %(name)-15s %(message)s",
101                  "datefmt": "%Y-%m-%dT%H:%M:%SZ",
102              },
103              "standard": {
104                  "()": "logging.Formatter",
105                  "format": "%(asctime)s %(levelname)-8s %(name)-15s %(message)s",
106                  "datefmt": "%Y-%m-%dT%H:%M:%SZ",
107              },
108          },
109          "handlers": {
110              "default": {
111                  "formatter": "default",
112                  "class": "logging.StreamHandler",
113                  "stream": "ext://sys.stdout",
114              },
115              "access": {
116                  "formatter": "access",
117                  "class": "logging.StreamHandler",
118                  "stream": "ext://sys.stdout",
119              },
120          },
121          "loggers": {
122              "litestar": {"handlers": ["default"]},
123              "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
124              "uvicorn.error": {"level": "INFO"},
125              "uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False},
126          },
127      }
128  
129  
130  class LitestarComponent(Component):
131      __section__ = "litestar"
132      request_max_body_size: Optional[int] = None
133  
134      def finalize(self, ctx: ComponentContext, app: Litestar):
135          if self.request_max_body_size is not None:
136              if hasattr(app, "request_max_body_size"):
137                  app.request_max_body_size = self.request_max_body_size
138              else:
139                  logging.warning(
140                      f"Litestar version {litestar.__version__.formatted()}"
141                      f" does not support 'request_max_body_size' parameter"
142                  )
143  
144  
145  class LocalConfig(AppConfig):
146      security: SecurityComponent = NoSecurityComponent()
147      service: ServiceComponent = LocalServiceComponent()
148      storage: StorageComponent = LocalStorageComponent()
149      telemetry: TelemetryComponent = TelemetryComponent()
150      dashboard: DashboardComponent = DashboardComponent()
151      datasets: DatasetComponent = DatasetComponent()
152      tracing: TracingComponent = TracingComponent()
153      litestar: LitestarComponent = LitestarComponent()