/ src / evidently / cli / ui.py
ui.py
  1  import os
  2  import threading
  3  import time
  4  from typing import Optional
  5  from typing import cast
  6  
  7  import requests
  8  import uuid6
  9  from typer import BadParameter
 10  from typer import Option
 11  from typer import echo
 12  
 13  from evidently.cli.main import app
 14  from evidently.ui.service.app import get_config
 15  from evidently.ui.service.demo_projects import DEMO_PROJECT_NAMES_FOR_CLI
 16  from evidently.ui.service.demo_projects import DEMO_PROJECTS
 17  from evidently.ui.service.demo_projects import DEMO_PROJECTS_NAMES
 18  from evidently.ui.service.demo_projects import DemoProjectsNames
 19  from evidently.ui.workspace import RemoteWorkspace
 20  
 21  
 22  def setup_deterministic_generation_uuid(seed: int = 8754):
 23      import uuid
 24  
 25      from faker import Faker
 26  
 27      Faker.seed(seed)
 28      fake = Faker()
 29  
 30      def deterministic_uuid() -> uuid.UUID:
 31          return fake.uuid4(cast_to=None)
 32  
 33      def deterministic_uuid6() -> uuid6.UUID:
 34          return uuid6.UUID(int=deterministic_uuid().int)
 35  
 36      uuid.uuid4 = deterministic_uuid
 37      uuid6.uuid7 = deterministic_uuid6
 38  
 39  
 40  def _create_demo_projects_task(demo_names: list[str], host: str, port: int, secret: Optional[str]):
 41      """Background task that waits for server and creates demo projects."""
 42      base_url = f"http://{host}:{port}"
 43  
 44      # Wait for server to be ready by polling /api/version
 45      max_retries = 30
 46      retry_delay = 0.5
 47      for attempt in range(max_retries):
 48          try:
 49              response = requests.get(f"{base_url}/api/version", timeout=1)
 50              if response.status_code == 200:
 51                  break
 52          except Exception:
 53              pass
 54  
 55          if attempt < max_retries - 1:
 56              time.sleep(retry_delay)
 57          else:
 58              echo(f"Warning: Could not connect to server at {base_url} after {max_retries} attempts")
 59              return
 60  
 61      # Create demo projects using RemoteWorkspace
 62      try:
 63          ws = RemoteWorkspace(base_url, secret=secret)
 64  
 65          for demo_name in demo_names:
 66              if demo_name not in DEMO_PROJECTS:
 67                  echo(f"Warning: Unknown demo project: {demo_name}")
 68                  continue
 69  
 70              # TODO: better type safety
 71              demo_project = DEMO_PROJECTS[cast(DemoProjectsNames, demo_name)]
 72  
 73              # Check if project already exists
 74              existing_projects = ws.list_projects()
 75              has_demo_project = any(p.name == demo_project.name for p in existing_projects)
 76  
 77              if not has_demo_project:
 78                  echo(f"Creating demo project '{demo_project.name}'...")
 79                  demo_project.create(ws)
 80                  echo(f"Demo project '{demo_project.name}' created successfully")
 81              else:
 82                  echo(f"Demo project '{demo_project.name}' already exists, skipping")
 83  
 84      except Exception as e:
 85          echo(f"Error creating demo projects: {e}")
 86  
 87  
 88  @app.command("ui")
 89  def ui(
 90      host: str = Option("127.0.0.1", help="Service host"),
 91      port: int = Option(8000, help="Service port"),
 92      workspace: str = Option("workspace", help="Path to workspace"),
 93      demo_projects: str = Option(
 94          "",
 95          "--demo-projects",
 96          help=f"Comma-separated list of demo projects to generate. Possible values: [{'|'.join(DEMO_PROJECT_NAMES_FOR_CLI)}]",
 97      ),
 98      secret: Optional[str] = Option(None, help="Secret for writing operations"),
 99      litestar_request_max_body_size: Optional[int] = Option(None, help="Request body size limit"),
100      conf_path: Optional[str] = Option(None, help="Path to configuration file"),
101  ):
102      """Start Evidently UI service"""
103      if os.environ.get("EXPERIMENTAL_DETERMINISTIC_UUID"):
104          setup_deterministic_generation_uuid()
105  
106      from evidently.ui.service.app import run
107  
108      demos: list[str] = demo_projects.split(",") if demo_projects else []
109      if "all" in demos:
110          # TODO: better type safety
111          demos = cast(list[str], list(DEMO_PROJECTS_NAMES))
112      missing = [dp for dp in demos if dp not in DEMO_PROJECTS]
113      if missing:
114          raise BadParameter(f"Unknown demo project name '{missing[0]}'")
115  
116      if demos:
117          # Start background task in a daemon thread
118          thread = threading.Thread(
119              target=_create_demo_projects_task,
120              args=(demos, host, port, secret),
121              daemon=True,
122          )
123          thread.start()
124  
125      config = get_config(
126          host=host,
127          port=port,
128          workspace=workspace,
129          secret=secret,
130          request_max_body_size=litestar_request_max_body_size,
131          conf_path=conf_path,
132      )
133      run(config)