class_docstr: A Tornado Application used to implement the Bokeh Server.
  
  Args:
      applications (dict[str,Application] or Application) :
          A map from paths to ``Application`` instances.
  
          If the value is a single Application, then the following mapping
          is generated:
  
          .. code-block:: python
  
              applications = {{ '/' : applications }}
  
          When a connection comes in to a given path, the associate
          Application is used to generate a new document for the session.
  
      prefix (str, optional) :
          A URL prefix to use for all Bokeh server paths. (default: None)
  
      ico_path (str, optional) :
          A path to a .ico file to return for ``/favicon.ico``.
  
      extra_websocket_origins (list[str], optional) :
          A list of hosts that can connect to the websocket.
  
          This is typically required when embedding a Bokeh server app in an
          external web site using :func:`~bokeh.embed.server_document` or
          similar.
  
          If None, ``["localhost"]`` will be assumed (default: None)
  
      extra_patterns (seq[tuple], optional) :
          A list of tuples of (str, http or websocket handler)
  
          Use this argument to add additional endpoints to custom deployments
          of the Bokeh Server.
  
          If None, then ``[]`` will be used. (default: None)
  
      secret_key (str, optional) :
          A secret key for signing session IDs.
  
          Defaults to the current value of the environment variable
          ``BOKEH_SECRET_KEY``
  
      sign_sessions (bool, optional) :
          Whether to cryptographically sign session IDs
  
          Defaults to the current value of the environment variable
          ``BOKEH_SIGN_SESSIONS``. If ``True``, then ``secret_key`` must
          also be provided (either via environment setting or passed as
          a parameter value)
  
      generate_session_ids (bool, optional) :
          Whether to generate a session ID if one is not provided
          (default: True)
  
      keep_alive_milliseconds (int, optional) :
          Number of milliseconds between keep-alive pings
          (default: {DEFAULT_KEEP_ALIVE_MS})
  
          Pings normally required to keep the websocket open. Set to 0 to
          disable pings.
  
      check_unused_sessions_milliseconds (int, optional) :
          Number of milliseconds between checking for unused sessions
          (default: {DEFAULT_CHECK_UNUSED_MS})
  
      unused_session_lifetime_milliseconds (int, optional) :
          Number of milliseconds for unused session lifetime
          (default: {DEFAULT_UNUSED_LIFETIME_MS})
  
      stats_log_frequency_milliseconds (int, optional) :
          Number of milliseconds between logging stats
          (default: {DEFAULT_STATS_LOG_FREQ_MS})
  
      mem_log_frequency_milliseconds (int, optional) :
          Number of milliseconds between logging memory information
          (default: {DEFAULT_MEM_LOG_FREQ_MS})
  
          Enabling this feature requires the optional dependency ``psutil`` to be
          installed.
  
      use_index (bool, optional) :
          Whether to generate an index of running apps in the ``RootHandler``
          (default: True)
  
      index (str, optional) :
          Path to a Jinja2 template to serve as the index for "/" if use_index
          is True. If None, the basic built in app index template is used.
          (default: None)
  
      redirect_root (bool, optional) :
          When there is only a single running application, whether to
          redirect requests to ``"/"`` to that application automatically
          (default: True)
  
          If there are multiple Bokeh applications configured, this option
          has no effect.
  
      websocket_max_message_size_bytes (int, optional):
          Set the Tornado ``websocket_max_message_size`` value.
          (default: {DEFAULT_WEBSOCKET_MAX_MESSAGE_SIZE_BYTES})
  
      websocket_compression_level (int, optional):
          Set the Tornado WebSocket ``compression_level`` documented in
          https://docs.python.org/3.7/library/zlib.html#zlib.compressobj.
  
      websocket_compression_mem_level (int, optional):
          Set the Tornado WebSocket compression ``mem_level`` documented in
          https://docs.python.org/3.7/library/zlib.html#zlib.compressobj.
  
      index (str, optional):
          Path to a Jinja2 template to use for the root URL
  
      auth_provider (AuthProvider, optional):
          An AuthProvider instance
  
      include_headers (list, optional) :
          List of request headers to include in session context
          (by default all headers are included)
  
      exclude_headers (list, optional) :
          List of request headers to exclude in session context
          (by default all headers are included)
  
      include_cookies (list, optional) :
          List of cookies to include in session context
          (by default all cookies are included)
  
      exclude_cookies (list, optional) :
          List of cookies to exclude in session context
          (by default all cookies are included)
  
      session_token_expiration (int, optional) :
          Duration in seconds that a new session token is valid
          for session creation. After the expiry time has elapsed,
          the token will not be able create a new session
          (default: {DEFAULT_SESSION_TOKEN_EXPIRATION})
  
  Any additional keyword arguments are passed to ``tornado.web.Application``.
class_docstr_tok: ["A","Tornado","Application","used","to","implement","the","Bokeh","Server",".","Args",":","applications","(","dict","[","str",",","Application","]","or","Application",")",":","A","map","from","paths","to","``","Application","``","instances",".","If","the","value","is","a","single","Application",",","then","the","following","mapping","is","generated",":","..","code-block",":",":","python","applications","=","{","{","'\/","'",":","applications","}","}","When","a","connection","comes","in","to","a","given","path",",","the","associate","Application","is","used","to","generate","a","new","document","for","the","session",".","prefix","(","str",",","optional",")",":","A","URL","prefix","to","use","for","all","Bokeh","server","paths",".","(","default",":","None",")","ico_path","(","str",",","optional",")",":","A","path","to","a",".ico","file","to","return","for","``","\/favicon.ico","``",".","extra_websocket_origins","(","list","[","str","]",",","optional",")",":","A","list","of","hosts","that","can","connect","to","the","websocket",".","This","is","typically","required","when","embedding","a","Bokeh","server","app","in","an","external","web","site","using",":","func",":","`","~bokeh.embed.server_document","`","or","similar",".","If","None",",","``","[","``","localhost","''","]","``","will","be","assumed","(","default",":","None",")","extra_patterns","(","seq","[","tuple","]",",","optional",")",":","A","list","of","tuples","of","(","str",",","http","or","websocket","handler",")","Use","this","argument","to","add","additional","endpoints","to","custom","deployments","of","the","Bokeh","Server",".","If","None",",","then","``","[","]","``","will","be","used",".","(","default",":","None",")","secret_key","(","str",",","optional",")",":","A","secret","key","for","signing","session","IDs",".","Defaults","to","the","current","value","of","the","environment","variable","``","BOKEH_SECRET_KEY","``","sign_sessions","(","bool",",","optional",")",":","Whether","to","cryptographically","sign","session","IDs","Defaults","to","the","current","value","of","the","environment","variable","``","BOKEH_SIGN_SESSIONS","``",".","If","``","True","``",",","then","``","secret_key","``","must","also","be","provided","(","either","via","environment","setting","or","passed","as","a","parameter","value",")","generate_session_ids","(","bool",",","optional",")",":","Whether","to","generate","a","session","ID","if","one","is","not","provided","(","default",":","True",")","keep_alive_milliseconds","(","int",",","optional",")",":","Number","of","milliseconds","between","keep-alive","pings","(","default",":","{","DEFAULT_KEEP_ALIVE_MS","}",")","Pings","normally","required","to","keep","the","websocket","open",".","Set","to","0","to","disable","pings",".","check_unused_sessions_milliseconds","(","int",",","optional",")",":","Number","of","milliseconds","between","checking","for","unused","sessions","(","default",":","{","DEFAULT_CHECK_UNUSED_MS","}",")","unused_session_lifetime_milliseconds","(","int",",","optional",")",":","Number","of","milliseconds","for","unused","session","lifetime","(","default",":","{","DEFAULT_UNUSED_LIFETIME_MS","}",")","stats_log_frequency_milliseconds","(","int",",","optional",")",":","Number","of","milliseconds","between","logging","stats","(","default",":","{","DEFAULT_STATS_LOG_FREQ_MS","}",")","mem_log_frequency_milliseconds","(","int",",","optional",")",":","Number","of","milliseconds","between","logging","memory","information","(","default",":","{","DEFAULT_MEM_LOG_FREQ_MS","}",")","Enabling","this","feature","requires","the","optional","dependency","``","psutil","``","to","be","installed",".","use_index","(","bool",",","optional",")",":","Whether","to","generate","an","index","of","running","apps","in","the","``","RootHandler","``","(","default",":","True",")","index","(","str",",","optional",")",":","Path","to","a","Jinja2","template","to","serve","as","the","index","for","``","\/","''","if","use_index","is","True",".","If","None",",","the","basic","built","in","app","index","template","is","used",".","(","default",":","None",")","redirect_root","(","bool",",","optional",")",":","When","there","is","only","a","single","running","application",",","whether","to","redirect","requests","to","``","``","\/","''","``","to","that","application","automatically","(","default",":","True",")","If","there","are","multiple","Bokeh","applications","configured",",","this","option","has","no","effect",".","websocket_max_message_size_bytes","(","int",",","optional",")",":","Set","the","Tornado","``","websocket_max_message_size","``","value",".","(","default",":","{","DEFAULT_WEBSOCKET_MAX_MESSAGE_SIZE_BYTES","}",")","websocket_compression_level","(","int",",","optional",")",":","Set","the","Tornado","WebSocket","``","compression_level","``","documented","in","https",":","\/\/docs.python.org\/3.7\/library\/zlib.html","#","zlib.compressobj",".","websocket_compression_mem_level","(","int",",","optional",")",":","Set","the","Tornado","WebSocket","compression","``","mem_level","``","documented","in","https",":","\/\/docs.python.org\/3.7\/library\/zlib.html","#","zlib.compressobj",".","index","(","str",",","optional",")",":","Path","to","a","Jinja2","template","to","use","for","the","root","URL","auth_provider","(","AuthProvider",",","optional",")",":","An","AuthProvider","instance","include_headers","(","list",",","optional",")",":","List","of","request","headers","to","include","in","session","context","(","by","default","all","headers","are","included",")","exclude_headers","(","list",",","optional",")",":","List","of","request","headers","to","exclude","in","session","context","(","by","default","all","headers","are","included",")","include_cookies","(","list",",","optional",")",":","List","of","cookies","to","include","in","session","context","(","by","default","all","cookies","are","included",")","exclude_cookies","(","list",",","optional",")",":","List","of","cookies","to","exclude","in","session","context","(","by","default","all","cookies","are","included",")","session_token_expiration","(","int",",","optional",")",":","Duration","in","seconds","that","a","new","session","token","is","valid","for","session","creation",".","After","the","expiry","time","has","elapsed",",","the","token","will","not","be","able","create","a","new","session","(","default",":","{","DEFAULT_SESSION_TOKEN_EXPIRATION","}",")","Any","additional","keyword","arguments","are","passed","to","``","tornado.web.Application","``","."]
code: def __init__(
      self,
      applications: Mapping[str, Application | ModifyDoc]
      | Application
      | ModifyDoc,
      *,
      absolute_url: str | None = None,
      prefix: str | None = None,
      extra_websocket_origins: Sequence[str] | None = None,
      extra_patterns: URLRoutes | None = None,
      secret_key: bytes | None = settings.secret_key_bytes(),
      sign_sessions: bool = settings.sign_sessions(),
      generate_session_ids: bool = True,
      keep_alive_milliseconds: int = DEFAULT_KEEP_ALIVE_MS,
      check_unused_sessions_milliseconds: int = DEFAULT_CHECK_UNUSED_MS,
      unused_session_lifetime_milliseconds: int = DEFAULT_UNUSED_LIFETIME_MS,
      stats_log_frequency_milliseconds: int = DEFAULT_STATS_LOG_FREQ_MS,
      mem_log_frequency_milliseconds: int = DEFAULT_MEM_LOG_FREQ_MS,
      use_index: bool = True,
      redirect_root: bool = True,
      websocket_max_message_size_bytes: int = DEFAULT_WEBSOCKET_MAX_MESSAGE_SIZE_BYTES,
      websocket_compression_level: int | None = None,
      websocket_compression_mem_level: int | None = None,
      ico_path: str = settings.ico_path(),
      index: str | None = None,
      auth_provider: AuthProvider = NullAuth(),
      xsrf_cookies: bool = False,
      include_headers: list[str] | None = None,
      include_cookies: list[str] | None = None,
      exclude_headers: list[str] | None = None,
      exclude_cookies: list[str] | None = None,
      session_token_expiration: int = DEFAULT_SESSION_TOKEN_EXPIRATION,
      **kwargs: Any,
  ):
      from ..application.handlers.document_lifecycle import (
          DocumentLifecycleHandler,
      )
      from ..application.handlers.function import FunctionHandler
  
      if callable(applications):
          applications = Application(FunctionHandler(applications))
  
      if isinstance(applications, Application):
          applications = {"/": applications}
      else:
          applications = dict(applications)
  
      for url, app in list(applications.items()):
          if callable(app):
              applications[url] = app = Application(
                  FunctionHandler(app)
              )
          if all(
              not isinstance(handler, DocumentLifecycleHandler)
              for handler in app._handlers
          ):
              app.add(DocumentLifecycleHandler())
  
      self._absolute_url = absolute_url
  
      if prefix is None:
          prefix = ""
      prefix = prefix.strip("/")
      if prefix:
          prefix = "/" + prefix
  
      self._prefix = prefix
  
      self._index = index
  
      if keep_alive_milliseconds < 0:
          # 0 means "disable"
          raise ValueError("keep_alive_milliseconds must be >= 0")
      else:
          if keep_alive_milliseconds == 0:
              log.info("Keep-alive ping disabled")
          elif keep_alive_milliseconds != DEFAULT_KEEP_ALIVE_MS:
              log.info(
                  "Keep-alive ping configured every %d milliseconds",
                  keep_alive_milliseconds,
              )
      self._keep_alive_milliseconds = keep_alive_milliseconds
  
      if check_unused_sessions_milliseconds <= 0:
          raise ValueError(
              "check_unused_sessions_milliseconds must be > 0"
          )
      elif (
          check_unused_sessions_milliseconds != DEFAULT_CHECK_UNUSED_MS
      ):
          log.info(
              "Check for unused sessions every %d milliseconds",
              check_unused_sessions_milliseconds,
          )
      self._check_unused_sessions_milliseconds = (
          check_unused_sessions_milliseconds
      )
  
      if unused_session_lifetime_milliseconds <= 0:
          raise ValueError(
              "unused_session_lifetime_milliseconds must be > 0"
          )
      elif (
          unused_session_lifetime_milliseconds
          != DEFAULT_UNUSED_LIFETIME_MS
      ):
          log.info(
              "Unused sessions last for %d milliseconds",
              unused_session_lifetime_milliseconds,
          )
      self._unused_session_lifetime_milliseconds = (
          unused_session_lifetime_milliseconds
      )
  
      if stats_log_frequency_milliseconds <= 0:
          raise ValueError(
              "stats_log_frequency_milliseconds must be > 0"
          )
      elif (
          stats_log_frequency_milliseconds != DEFAULT_STATS_LOG_FREQ_MS
      ):
          log.info(
              "Log statistics every %d milliseconds",
              stats_log_frequency_milliseconds,
          )
      self._stats_log_frequency_milliseconds = (
          stats_log_frequency_milliseconds
      )
  
      if mem_log_frequency_milliseconds < 0:
          # 0 means "disable"
          raise ValueError(
              "mem_log_frequency_milliseconds must be >= 0"
          )
      elif mem_log_frequency_milliseconds > 0:
          if psutil is None:
              log.warning(
                  "Memory logging requested, but is disabled. Optional dependency 'psutil' is missing. "
                  "Try 'pip install psutil' or 'conda install psutil'"
              )
              mem_log_frequency_milliseconds = 0
          elif (
              mem_log_frequency_milliseconds != DEFAULT_MEM_LOG_FREQ_MS
          ):
              log.info(
                  "Log memory usage every %d milliseconds",
                  mem_log_frequency_milliseconds,
              )
      self._mem_log_frequency_milliseconds = (
          mem_log_frequency_milliseconds
      )
  
      if websocket_max_message_size_bytes <= 0:
          raise ValueError(
              "websocket_max_message_size_bytes must be positive"
          )
      elif (
          websocket_max_message_size_bytes
          != DEFAULT_WEBSOCKET_MAX_MESSAGE_SIZE_BYTES
      ):
          log.info(
              "Torndado websocket_max_message_size set to %d bytes (%0.2f MB)",
              websocket_max_message_size_bytes,
              websocket_max_message_size_bytes / 1024.0**2,
          )
  
      self.auth_provider = auth_provider
  
      if (
          self.auth_provider.get_user
          or self.auth_provider.get_user_async
      ):
          log.info(
              "User authentication hooks provided (no default user)"
          )
      else:
          log.info(
              "User authentication hooks NOT provided (default user enabled)"
          )
  
      kwargs["xsrf_cookies"] = xsrf_cookies
      if xsrf_cookies:
          log.info("XSRF cookie protection enabled")
  
      if session_token_expiration <= 0:
          raise ValueError("session_token_expiration must be > 0")
      else:
          self._session_token_expiration = session_token_expiration
  
      if exclude_cookies and include_cookies:
          raise ValueError(
              "Declare either an include or an exclude list for the cookies, not both."
          )
      self._exclude_cookies = exclude_cookies
      self._include_cookies = include_cookies
  
      if exclude_headers and include_headers:
          raise ValueError(
              "Declare either an include or an exclude list for the headers, not both."
          )
      self._exclude_headers = exclude_headers
      self._include_headers = include_headers
  
      if extra_websocket_origins is None:
          self._websocket_origins = set()
      else:
          self._websocket_origins = set(extra_websocket_origins)
      self._secret_key = secret_key
      self._sign_sessions = sign_sessions
      self._generate_session_ids = generate_session_ids
      log.debug(
          f"These host origins can connect to the websocket: {list(self._websocket_origins)!r}"
      )
  
      # Wrap applications in ApplicationContext
      self._applications = {}
      for url, app in applications.items():
          assert isinstance(
              app, Application
          )  # TODO: unnecessary; improve type flow to remove this
          self._applications[url] = ApplicationContext(
              app, url=url, logout_url=self.auth_provider.logout_url
          )
  
      extra_patterns = extra_patterns or []
      extra_patterns.extend(self.auth_provider.endpoints)
  
      if ico_path == "none":
          self._icon = None
      else:
          with open(ico_path, "rb") as f:
              self._icon = f.read()
      all_patterns: URLRoutes = [
          (r"/favicon.ico", IcoHandler, dict(app=self))
      ]
  
      for key, ctx in self._applications.items():
          app_patterns: URLRoutes = []
          for p in per_app_patterns:
              if key == "/":
                  route = p[0]
              else:
                  route = key + p[0]
              context: RouteContext = {
                  "application_context": self._applications[key]
              }
              if issubclass(p[1], WSHandler):
                  context[
                      "compression_level"
                  ] = websocket_compression_level
                  context["mem_level"] = websocket_compression_mem_level
              route = self._prefix + route
              app_patterns.append((route, p[1], context))
  
          websocket_path = None
          for r in app_patterns:
              if r[0].endswith("/ws"):
                  websocket_path = r[0]
          if not websocket_path:
              raise RuntimeError("Couldn't find websocket path")
          for r in app_patterns:
              assert len(r) == 3  # TODO: handle two valued case as well
              r[2]["bokeh_websocket_path"] = websocket_path
  
          all_patterns.extend(app_patterns)
  
          # if the app requests a custom static path, use that, otherwise add Bokeh's standard static handler
          all_patterns.append(
              create_static_handler(self._prefix, key, ctx.application)
          )
  
      for p in extra_patterns + toplevel_patterns:
          if p[1] == RootHandler:
              if use_index:
                  data = {
                      "applications": self._applications,
                      "prefix": self._prefix,
                      "index": self._index,
                      "use_redirect": redirect_root,
                  }
                  prefixed_pat = (
                      (self._prefix + p[0],) + p[1:] + (data,)
                  )
                  all_patterns.append(prefixed_pat)  # type: ignore[arg-type] # TODO: easy to fix types, but also easy to break logic
          else:
              prefixed_pat = (self._prefix + p[0],) + p[1:]
              all_patterns.append(prefixed_pat)  # type: ignore[arg-type] # TODO: easy to fix types, but also easy to break logic
  
      log.debug("Patterns are:")
      for line in pformat(all_patterns, width=60).split("\n"):
          log.debug("  " + line)
  
      super().__init__(
          all_patterns,  # type: ignore[arg-type] # TODO: this may be another bug in mypy (not sure; but looks suspicious)
          websocket_max_message_size=websocket_max_message_size_bytes,
          **kwargs,
      )
  
imports: from __future__ import annotations
  import logging
  import gc
  import os
  from pprint import pformat
  from types import ModuleType
  from typing import TYPE_CHECKING, Any, Mapping, Sequence
  from urllib.parse import urljoin
  from tornado.ioloop import PeriodicCallback
  from tornado.web import Application, StaticFileHandler
  from ..application import Application
  from ..document import Document
  from ..model import Model
  from ..resources import Resources
  from ..settings import settings
  from ..util.dependencies import import_optional
  from ..util.strings import format_docstring
  from ..util.tornado import fixup_windows_event_loop_policy
  from .auth_provider import NullAuth
  from .connection import ServerConnection
  from .contexts import ApplicationContext
  from .session import ServerSession
  from .urls import per_app_patterns, toplevel_patterns
  from .views.ico_handler import IcoHandler
  from .views.root_handler import RootHandler
  from .views.static_handler import StaticHandler
  from .views.ws import WSHandler
label_desc: Use image node_id 1 to create a new BokehTornado object from inherited base classes: TornadoApplication with example: obj = BokehTornado(applications)