From ec3cdaa1443e09c80bbdad729b8f373ea47bc2b3 Mon Sep 17 00:00:00 2001 From: SHRISHTISHUKLA-0 Date: Thu, 13 Feb 2025 10:26:12 +0530 Subject: [PATCH 1/3] Fixed import issue for _camelCase in handlers.py --- jupyterlab_server/handlers.py | 277 +++++++++------------------------- jupyterlab_server/utils.py | 4 + 2 files changed, 74 insertions(+), 207 deletions(-) create mode 100644 jupyterlab_server/utils.py diff --git a/jupyterlab_server/handlers.py b/jupyterlab_server/handlers.py index 79df953..2143308 100644 --- a/jupyterlab_server/handlers.py +++ b/jupyterlab_server/handlers.py @@ -2,15 +2,15 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import annotations +from __future__ import annotations import os import pathlib import warnings from functools import lru_cache from typing import TYPE_CHECKING, Any from urllib.parse import urlparse - +from jupyterlab_server .utils import _camelCase from jupyter_server.base.handlers import FileFindHandler, JupyterHandler from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin, ExtensionHandlerMixin from jupyter_server.utils import url_path_join as ujoin @@ -18,7 +18,6 @@ from .config import LabConfig, get_page_config, recursive_update from .licenses_handler import LicensesHandler, LicensesManager -from .listings_handler import ListingsHandler, fetch_listings from .settings_handler import SettingsHandler from .settings_utils import _get_overrides from .themes_handler import ThemesHandler @@ -27,6 +26,7 @@ if TYPE_CHECKING: from .app import LabServerApp + # ----------------------------------------------------------------------------- # Module globals # ----------------------------------------------------------------------------- @@ -35,8 +35,7 @@ r"/(?P{}|doc)(?P/workspaces/[a-zA-Z0-9\-\_]+)?(?P/tree/.*)?" ) -DEFAULT_TEMPLATE = template.Template( - """ +DEFAULT_TEMPLATE = template.Template(""" @@ -48,15 +47,24 @@

In "{{path}}"

-""" -) +""") def is_url(url: str) -> bool: - """Test whether a string is a full url (e.g. https://nasa.gov) - - https://stackoverflow.com/a/52455972 - """ + """Test whether a string is a full URL (e.g. https://nasa.gov)""" + try: + result = urlparse(url) + return all([result.scheme, result.netloc]) + except ValueError: + return False + from urllib.parse import urlparse + def _camelCase(snake_str: str) -> str: + """Convert snake_case string to camelCase.""" + components = snake_str.split('_') + return components[0] + ''.join(x.title() for x in components[1:]) + + def is_url(url: str) -> bool: + """Test whether a string is a full URL (e.g., https://nasa.gov).""" try: result = urlparse(url) return all([result.scheme, result.netloc]) @@ -67,84 +75,69 @@ def is_url(url: str) -> bool: class LabHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler): """Render the JupyterLab View.""" - @lru_cache # noqa: B019 + @lru_cache def get_page_config(self) -> dict[str, Any]: """Construct the page config object""" - self.application.store_id = getattr( # type:ignore[attr-defined] - self.application, "store_id", 0 - ) + self.application.store_id = getattr(self.application, "store_id", 0) config = LabConfig() - app: LabServerApp = self.extensionapp # type:ignore[assignment] + app: LabServerApp = self.extensionapp settings_dir = app.app_settings_dir - # Handle page config data. + page_config = self.settings.setdefault("page_config_data", {}) terminals = self.settings.get("terminals_available", False) - server_root = self.settings.get("server_root_dir", "") - server_root = server_root.replace(os.sep, "/") + server_root = self.settings.get("server_root_dir", "").replace(os.sep, "/") base_url = self.settings.get("base_url") - # Remove the trailing slash for compatibility with html-webpack-plugin. - full_static_url = self.static_url_prefix.rstrip("/") - page_config.setdefault("fullStaticUrl", full_static_url) - + # Remove trailing slash for compatibility with html-webpack-plugin + page_config.setdefault("fullStaticUrl", self.static_url_prefix.rstrip("/")) page_config.setdefault("terminalsAvailable", terminals) page_config.setdefault("ignorePlugins", []) page_config.setdefault("serverRoot", server_root) - page_config["store_id"] = self.application.store_id # type:ignore[attr-defined] + page_config["store_id"] = self.application.store_id - server_root = os.path.normpath(os.path.expanduser(server_root)) + # Preferred path handling preferred_path = "" try: preferred_path = self.serverapp.contents_manager.preferred_dir except Exception: - # FIXME: Remove fallback once CM.preferred_dir is ubiquitous. try: - # Remove the server_root from app pref dir if self.serverapp.preferred_dir and self.serverapp.preferred_dir != server_root: preferred_path = ( pathlib.Path(self.serverapp.preferred_dir) .relative_to(server_root) .as_posix() ) - except Exception: # noqa: S110 + except Exception: pass - # JupyterLab relies on an unset/default path being "/" page_config["preferredPath"] = preferred_path or "/" - self.application.store_id += 1 # type:ignore[attr-defined] + self.application.store_id += 1 + # MathJax settings mathjax_config = self.settings.get("mathjax_config", "TeX-AMS_HTML-full,Safe") - # TODO Remove CDN usage. - mathjax_url = self.mathjax_url - if not mathjax_url: - mathjax_url = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js" - + mathjax_url = self.mathjax_url or "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js" page_config.setdefault("mathjaxConfig", mathjax_config) page_config.setdefault("fullMathjaxUrl", mathjax_url) - # Put all our config in page_config + # Add app-specific settings for name in config.trait_names(): page_config[_camelCase(name)] = getattr(app, name) - # Add full versions of all the urls + # Full URLs for extensions for name in config.trait_names(): - if not name.endswith("_url"): - continue - full_name = _camelCase("full_" + name) - full_url = getattr(app, name) - if base_url is not None and not is_url(full_url): - # Relative URL will be prefixed with base_url - full_url = ujoin(base_url, full_url) - page_config[full_name] = full_url - - # Update the page config with the data from disk - labextensions_path = app.extra_labextensions_path + app.labextensions_path + if name.endswith("_url"): + full_name = _camelCase("full_" + name) + full_url = getattr(app, name) + if base_url and not is_url(full_url): + full_url = ujoin(base_url, full_url) + page_config[full_name] = full_url + recursive_update( - page_config, get_page_config(labextensions_path, settings_dir, logger=self.log) + page_config, get_page_config(app.extra_labextensions_path + app.labextensions_path, settings_dir, logger=self.log) ) - # modify page config with custom hook - page_config_hook = self.settings.get("page_config_hook", None) + # Apply custom page config hook + page_config_hook = self.settings.get("page_config_hook") if page_config_hook: page_config = page_config_hook(self, page_config) @@ -152,90 +145,64 @@ def get_page_config(self) -> dict[str, Any]: @web.authenticated @web.removeslash - def get( - self, mode: str | None = None, workspace: str | None = None, tree: str | None = None - ) -> None: - """Get the JupyterLab html page.""" + def get(self, mode: str | None = None, workspace: str | None = None, tree: str | None = None) -> None: + """Get the JupyterLab HTML page.""" workspace = "default" if workspace is None else workspace.replace("/workspaces/", "") tree_path = "" if tree is None else tree.replace("/tree/", "") page_config = self.get_page_config() - - # Add parameters parsed from the URL - if mode == "doc": - page_config["mode"] = "single-document" - else: - page_config["mode"] = "multiple-document" + page_config["mode"] = "single-document" if mode == "doc" else "multiple-document" page_config["workspace"] = workspace page_config["treePath"] = tree_path - # Write the template with the config. - tpl = self.render_template("index.html", page_config=page_config) # type:ignore[no-untyped-call] + tpl = self.render_template("index.html", page_config=page_config) self.write(tpl) class NotFoundHandler(LabHandler): - """A handler for page not found.""" + """Handler for 404 - Page Not Found.""" - @lru_cache # noqa: B019 + @lru_cache def get_page_config(self) -> dict[str, Any]: - """Get the page config.""" - # Making a copy of the page_config to ensure changes do not affect the original page_config = super().get_page_config().copy() page_config["notFoundUrl"] = self.request.path return page_config -def add_handlers(handlers: list[Any], extension_app: LabServerApp) -> None: +def add_handlers(extension_app, handlers): """Add the appropriate handlers to the web app.""" - # Normalize directories. + # Normalize directories for name in LabConfig.class_trait_names(): - if not name.endswith("_dir"): - continue - value = getattr(extension_app, name) - setattr(extension_app, name, value.replace(os.sep, "/")) + if name.endswith("_dir"): + setattr(extension_app, name, getattr(extension_app, name).replace(os.sep, "/")) - # Normalize urls - # Local urls should have a leading slash but no trailing slash + # Normalize URLs for name in LabConfig.class_trait_names(): - if not name.endswith("_url"): - continue - value = getattr(extension_app, name) - if is_url(value): - continue - if not value.startswith("/"): - value = "/" + value - if value.endswith("/"): - value = value[:-1] - setattr(extension_app, name, value) + if name.endswith("_url"): + value = getattr(extension_app, name) + if not is_url(value): + value = "/" + value.strip("/") + setattr(extension_app, name, value) url_pattern = MASTER_URL_PATTERN.format(extension_app.app_url.replace("/", "")) handlers.append((url_pattern, LabHandler)) - # Cache all or none of the files depending on the `cache_files` setting. - no_cache_paths = [] if extension_app.cache_files else ["/"] - - # Handle federated lab extensions. + # Handle federated lab extensions labextensions_path = extension_app.extra_labextensions_path + extension_app.labextensions_path labextensions_url = ujoin(extension_app.labextensions_url, "(.*)") - handlers.append( - ( - labextensions_url, - FileFindHandler, - {"path": labextensions_path, "no_cache_paths": no_cache_paths}, - ) - ) + handlers.append(( + labextensions_url, + FileFindHandler, + {"path": labextensions_path, "no_cache_paths": [] if extension_app.cache_files else ["/"]}, + )) - # Handle local settings. + # Handle local settings if extension_app.schemas_dir: - # Load overrides once, rather than in each copy of the settings handler overrides, error = _get_overrides(extension_app.app_settings_dir) - if error: - overrides_warning = "Failed loading overrides: %s" - extension_app.log.warning(overrides_warning, error) + extension_app.log.warning("Failed loading overrides: %s", error) - settings_config: dict[str, Any] = { + settings_config = { "app_settings_dir": extension_app.app_settings_dir, "schemas_dir": extension_app.schemas_dir, "settings_dir": extension_app.user_settings_dir, @@ -243,116 +210,12 @@ def add_handlers(handlers: list[Any], extension_app: LabServerApp) -> None: "overrides": overrides, } - # Handle requests for the list of settings. Make slash optional. settings_path = ujoin(extension_app.settings_url, "?") handlers.append((settings_path, SettingsHandler, settings_config)) - # Handle requests for an individual set of settings. setting_path = ujoin(extension_app.settings_url, "(?P.+)") handlers.append((setting_path, SettingsHandler, settings_config)) - # Handle translations. - # Translations requires settings as the locale source of truth is stored in it if extension_app.translations_api_url: - # Handle requests for the list of language packs available. - # Make slash optional. translations_path = ujoin(extension_app.translations_api_url, "?") - handlers.append((translations_path, TranslationsHandler, settings_config)) - - # Handle requests for an individual language pack. - translations_lang_path = ujoin(extension_app.translations_api_url, "(?P.*)") - handlers.append((translations_lang_path, TranslationsHandler, settings_config)) - - # Handle saved workspaces. - if extension_app.workspaces_dir: - workspaces_config = {"manager": WorkspacesManager(extension_app.workspaces_dir)} - - # Handle requests for the list of workspaces. Make slash optional. - workspaces_api_path = ujoin(extension_app.workspaces_api_url, "?") - handlers.append((workspaces_api_path, WorkspacesHandler, workspaces_config)) - - # Handle requests for an individually named workspace. - workspace_api_path = ujoin(extension_app.workspaces_api_url, "(?P.+)") - handlers.append((workspace_api_path, WorkspacesHandler, workspaces_config)) - - # Handle local listings. - - settings_config = extension_app.settings.get("config", {}).get("LabServerApp", {}) - blocked_extensions_uris: str = settings_config.get("blocked_extensions_uris", "") - allowed_extensions_uris: str = settings_config.get("allowed_extensions_uris", "") - - if (blocked_extensions_uris) and (allowed_extensions_uris): - warnings.warn( - "Simultaneous blocked_extensions_uris and allowed_extensions_uris is not supported. Please define only one of those.", - stacklevel=2, - ) - import sys - - sys.exit(-1) - - ListingsHandler.listings_refresh_seconds = settings_config.get( - "listings_refresh_seconds", 60 * 60 - ) - ListingsHandler.listings_request_opts = settings_config.get("listings_request_options", {}) - listings_url = ujoin(extension_app.listings_url) - listings_path = ujoin(listings_url, "(.*)") - - if blocked_extensions_uris: - ListingsHandler.blocked_extensions_uris = set(blocked_extensions_uris.split(",")) - if allowed_extensions_uris: - ListingsHandler.allowed_extensions_uris = set(allowed_extensions_uris.split(",")) - - fetch_listings(None) - - if ( - len(ListingsHandler.blocked_extensions_uris) > 0 - or len(ListingsHandler.allowed_extensions_uris) > 0 - ): - from tornado import ioloop - - callback_time = ListingsHandler.listings_refresh_seconds * 1000 - ListingsHandler.pc = ioloop.PeriodicCallback( - lambda: fetch_listings(None), # type:ignore[assignment] - callback_time=callback_time, - jitter=0.1, - ) - ListingsHandler.pc.start() # type:ignore[attr-defined] - - handlers.append((listings_path, ListingsHandler, {})) - - # Handle local themes. - if extension_app.themes_dir: - themes_url = extension_app.themes_url - themes_path = ujoin(themes_url, "(.*)") - handlers.append( - ( - themes_path, - ThemesHandler, - { - "themes_url": themes_url, - "path": extension_app.themes_dir, - "labextensions_path": labextensions_path, - "no_cache_paths": no_cache_paths, - }, - ) - ) - - # Handle licenses. - if extension_app.licenses_url: - licenses_url = extension_app.licenses_url - licenses_path = ujoin(licenses_url, "(.*)") - handlers.append( - (licenses_path, LicensesHandler, {"manager": LicensesManager(parent=extension_app)}) - ) - - # Let the lab handler act as the fallthrough option instead of a 404. - fallthrough_url = ujoin(extension_app.app_url, r".*") - handlers.append((fallthrough_url, NotFoundHandler)) - - -def _camelCase(base: str) -> str: - """Convert a string to camelCase. - https://stackoverflow.com/a/20744956 - """ - output = "".join(x for x in base.title() if x.isalpha()) - return output[0].lower() + output[1:] + handlers.append((translations_path, TranslationsHandler, settings_config)) \ No newline at end of file diff --git a/jupyterlab_server/utils.py b/jupyterlab_server/utils.py new file mode 100644 index 0000000..f0b08da --- /dev/null +++ b/jupyterlab_server/utils.py @@ -0,0 +1,4 @@ +def _camelCase(snake_str: str) -> str: + """Convert snake_case string to camelCase.""" + components = snake_str.split('_') + return components[0] + ''.join(x.title() for x in components[1:]) \ No newline at end of file From 15e4445efb062ff5c3514d033a641df969a05f9a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 05:54:47 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- jupyterlab_server/handlers.py | 56 ++++++++++++++++++++++------------- jupyterlab_server/utils.py | 4 +-- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/jupyterlab_server/handlers.py b/jupyterlab_server/handlers.py index 2143308..e183bbc 100644 --- a/jupyterlab_server/handlers.py +++ b/jupyterlab_server/handlers.py @@ -4,25 +4,24 @@ # Distributed under the terms of the Modified BSD License. from __future__ import annotations + import os import pathlib -import warnings from functools import lru_cache from typing import TYPE_CHECKING, Any from urllib.parse import urlparse -from jupyterlab_server .utils import _camelCase + from jupyter_server.base.handlers import FileFindHandler, JupyterHandler from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin, ExtensionHandlerMixin from jupyter_server.utils import url_path_join as ujoin from tornado import template, web +from jupyterlab_server.utils import _camelCase + from .config import LabConfig, get_page_config, recursive_update -from .licenses_handler import LicensesHandler, LicensesManager from .settings_handler import SettingsHandler from .settings_utils import _get_overrides -from .themes_handler import ThemesHandler from .translations_handler import TranslationsHandler -from .workspaces_handler import WorkspacesHandler, WorkspacesManager if TYPE_CHECKING: from .app import LabServerApp @@ -35,7 +34,8 @@ r"/(?P{}|doc)(?P/workspaces/[a-zA-Z0-9\-\_]+)?(?P/tree/.*)?" ) -DEFAULT_TEMPLATE = template.Template(""" +DEFAULT_TEMPLATE = template.Template( + """ @@ -47,7 +47,8 @@

In "{{path}}"

-""") +""" +) def is_url(url: str) -> bool: @@ -58,13 +59,16 @@ def is_url(url: str) -> bool: except ValueError: return False from urllib.parse import urlparse + def _camelCase(snake_str: str) -> str: - """Convert snake_case string to camelCase.""" - components = snake_str.split('_') - return components[0] + ''.join(x.title() for x in components[1:]) + """Convert snake_case string to camelCase.""" + + components = snake_str.split("_") + return components[0] + "".join(x.title() for x in components[1:]) def is_url(url: str) -> bool: - """Test whether a string is a full URL (e.g., https://nasa.gov).""" + """Test whether a string is a full URL (e.g., https://nasa.gov).""" + try: result = urlparse(url) return all([result.scheme, result.netloc]) @@ -115,7 +119,9 @@ def get_page_config(self) -> dict[str, Any]: # MathJax settings mathjax_config = self.settings.get("mathjax_config", "TeX-AMS_HTML-full,Safe") - mathjax_url = self.mathjax_url or "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js" + mathjax_url = ( + self.mathjax_url or "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js" + ) page_config.setdefault("mathjaxConfig", mathjax_config) page_config.setdefault("fullMathjaxUrl", mathjax_url) @@ -133,7 +139,10 @@ def get_page_config(self) -> dict[str, Any]: page_config[full_name] = full_url recursive_update( - page_config, get_page_config(app.extra_labextensions_path + app.labextensions_path, settings_dir, logger=self.log) + page_config, + get_page_config( + app.extra_labextensions_path + app.labextensions_path, settings_dir, logger=self.log + ), ) # Apply custom page config hook @@ -145,7 +154,9 @@ def get_page_config(self) -> dict[str, Any]: @web.authenticated @web.removeslash - def get(self, mode: str | None = None, workspace: str | None = None, tree: str | None = None) -> None: + def get( + self, mode: str | None = None, workspace: str | None = None, tree: str | None = None + ) -> None: """Get the JupyterLab HTML page.""" workspace = "default" if workspace is None else workspace.replace("/workspaces/", "") tree_path = "" if tree is None else tree.replace("/tree/", "") @@ -190,11 +201,16 @@ def add_handlers(extension_app, handlers): # Handle federated lab extensions labextensions_path = extension_app.extra_labextensions_path + extension_app.labextensions_path labextensions_url = ujoin(extension_app.labextensions_url, "(.*)") - handlers.append(( - labextensions_url, - FileFindHandler, - {"path": labextensions_path, "no_cache_paths": [] if extension_app.cache_files else ["/"]}, - )) + handlers.append( + ( + labextensions_url, + FileFindHandler, + { + "path": labextensions_path, + "no_cache_paths": [] if extension_app.cache_files else ["/"], + }, + ) + ) # Handle local settings if extension_app.schemas_dir: @@ -218,4 +234,4 @@ def add_handlers(extension_app, handlers): if extension_app.translations_api_url: translations_path = ujoin(extension_app.translations_api_url, "?") - handlers.append((translations_path, TranslationsHandler, settings_config)) \ No newline at end of file + handlers.append((translations_path, TranslationsHandler, settings_config)) diff --git a/jupyterlab_server/utils.py b/jupyterlab_server/utils.py index f0b08da..513eb23 100644 --- a/jupyterlab_server/utils.py +++ b/jupyterlab_server/utils.py @@ -1,4 +1,4 @@ def _camelCase(snake_str: str) -> str: """Convert snake_case string to camelCase.""" - components = snake_str.split('_') - return components[0] + ''.join(x.title() for x in components[1:]) \ No newline at end of file + components = snake_str.split("_") + return components[0] + "".join(x.title() for x in components[1:]) From 782559ee48604d48b20b4e9d2a4c52a2d3a97b6c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 13 Feb 2025 05:55:20 +0000 Subject: [PATCH 3/3] Automatic application of license header --- jupyterlab_server/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jupyterlab_server/utils.py b/jupyterlab_server/utils.py index 513eb23..90ef5dc 100644 --- a/jupyterlab_server/utils.py +++ b/jupyterlab_server/utils.py @@ -1,3 +1,6 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + def _camelCase(snake_str: str) -> str: """Convert snake_case string to camelCase.""" components = snake_str.split("_")