    def init_tornado_settings(self):
        """Set up the tornado settings dict."""
        base_url = self.hub.base_url
        jinja_options = dict(autoescape=True, enable_async=True)
        jinja_options.update(self.jinja_environment_options)
        base_path = self._template_paths_default()[0]
        if base_path not in self.template_paths:
            self.template_paths.append(base_path)
        loader = ChoiceLoader(
            [
                PrefixLoader({'templates': FileSystemLoader([base_path])}, '/'),
                FileSystemLoader(self.template_paths),
            ]
        )
        jinja_env = Environment(loader=loader, **jinja_options)
        # We need a sync jinja environment too, for the times we *must* use sync
        # code - particularly in RequestHandler.write_error. Since *that*
        # is called from inside the asyncio event loop, we can't actulaly just
        # schedule it on the loop - without starting another thread with its
        # own loop, which seems not worth the trouble. Instead, we create another
        # environment, exactly like this one, but sync
        del jinja_options['enable_async']
        jinja_env_sync = Environment(loader=loader, **jinja_options)

        login_url = url_path_join(base_url, 'login')
        logout_url = self.authenticator.logout_url(base_url)

        # if running from git, disable caching of require.js
        # otherwise cache based on server start time
        parent = os.path.dirname(os.path.dirname(jupyterhub.__file__))
        if os.path.isdir(os.path.join(parent, '.git')):
            version_hash = ''
        else:
            version_hash = datetime.now().strftime("%Y%m%d%H%M%S")

        oauth_no_confirm_list = set()
        for service in self._service_map.values():
            if service.oauth_no_confirm:
                self.log.warning(
                    "Allowing service %s to complete OAuth without confirmation on an authorization web page",
                    service.name,
                )
                oauth_no_confirm_list.add(service.oauth_client_id)

        # configure xsrf cookie
        # (user xsrf_cookie_kwargs works as override)
        xsrf_cookie_kwargs = self.tornado_settings.setdefault("xsrf_cookie_kwargs", {})
        if not xsrf_cookie_kwargs:
            # default to cookie_options
            xsrf_cookie_kwargs.update(self.tornado_settings.get("cookie_options", {}))

        # restrict xsrf cookie to hub base path
        xsrf_cookie_kwargs["path"] = self.hub.base_url

        settings = dict(
            log_function=log_request,
            config=self.config,
            log=self.log,
            db=self.db,
            proxy=self.proxy,
            hub=self.hub,
            activity_resolution=self.activity_resolution,
            admin_users=self.authenticator.admin_users,
            admin_access=self.admin_access,
            api_page_default_limit=self.api_page_default_limit,
            api_page_max_limit=self.api_page_max_limit,
            authenticator=self.authenticator,
            spawner_class=self.spawner_class,
            base_url=self.base_url,
            default_url=self.default_url,
            public_url=urlparse(self.public_url) if self.public_url else "",
            cookie_secret=self.cookie_secret,
            cookie_host_prefix_enabled=self.cookie_host_prefix_enabled,
            cookie_max_age_days=self.cookie_max_age_days,
            redirect_to_server=self.redirect_to_server,
            login_url=login_url,
            logout_url=logout_url,
            static_path=os.path.join(self.data_files_path, 'static'),
            static_url_prefix=url_path_join(self.hub.base_url, 'static/'),
            static_handler_class=CacheControlStaticFilesHandler,
            token_expires_in_max_seconds=self.token_expires_in_max_seconds,
            subdomain_hook=self.subdomain_hook,
            template_path=self.template_paths,
            template_vars=self.template_vars,
            jinja2_env=jinja_env,
            jinja2_env_sync=jinja_env_sync,
            version_hash=version_hash,
            subdomain_host=self.subdomain_host,
            domain=self.domain,
            statsd=self.statsd,
            implicit_spawn_seconds=self.implicit_spawn_seconds,
            allow_named_servers=self.allow_named_servers,
            default_server_name=self._default_server_name,
            named_server_limit_per_user=self.named_server_limit_per_user,
            oauth_provider=self.oauth_provider,
            oauth_no_confirm_list=oauth_no_confirm_list,
            concurrent_spawn_limit=self.concurrent_spawn_limit,
            spawn_throttle_retry_range=self.spawn_throttle_retry_range,
            active_server_limit=self.active_server_limit,
            authenticate_prometheus=self.authenticate_prometheus,
            internal_ssl=self.internal_ssl,
            internal_certs_location=self.internal_certs_location,
            internal_authorities=self.internal_ssl_authorities,
            internal_trust_bundles=self.internal_trust_bundles,
            internal_ssl_key=self.internal_ssl_key,
            internal_ssl_cert=self.internal_ssl_cert,
            internal_ssl_ca=self.internal_ssl_ca,
            trusted_alt_names=self.trusted_alt_names,
            shutdown_on_logout=self.shutdown_on_logout,
            eventlog=self.eventlog,
            app=self,
            xsrf_cookies=True,
        )
        # allow configured settings to have priority
        settings.update(self.tornado_settings)
        self.tornado_settings = settings
        # constructing users requires access to tornado_settings
        self.tornado_settings['users'] = self.users
        self.tornado_settings['services'] = self._service_map
Set up the tornado settings dict.
jupyterhub/app.py