| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- """Utilities for installing extensions"""
- # Copyright (c) Jupyter Development Team.
- # Distributed under the terms of the Modified BSD License.
- from __future__ import annotations
- import logging
- import os
- import sys
- import typing as t
- from jupyter_core.application import JupyterApp
- from jupyter_core.paths import ENV_CONFIG_PATH, SYSTEM_CONFIG_PATH, jupyter_config_dir
- from tornado.log import LogFormatter
- from traitlets import Bool
- from jupyter_server._version import __version__
- from jupyter_server.extension.config import ExtensionConfigManager
- from jupyter_server.extension.manager import ExtensionManager, ExtensionPackage
- def _get_config_dir(user: bool = False, sys_prefix: bool = False) -> str:
- """Get the location of config files for the current context
- Returns the string to the environment
- Parameters
- ----------
- user : bool [default: False]
- Get the user's .jupyter config directory
- sys_prefix : bool [default: False]
- Get sys.prefix, i.e. ~/.envs/my-env/etc/jupyter
- """
- if user and sys_prefix:
- sys_prefix = False
- if user:
- extdir = jupyter_config_dir()
- elif sys_prefix:
- extdir = ENV_CONFIG_PATH[0]
- else:
- extdir = SYSTEM_CONFIG_PATH[0]
- return extdir
- def _get_extmanager_for_context(
- write_dir: str = "jupyter_server_config.d", user: bool = False, sys_prefix: bool = False
- ) -> tuple[str, ExtensionManager]:
- """Get an extension manager pointing at the current context
- Returns the path to the current context and an ExtensionManager object.
- Parameters
- ----------
- write_dir : str [default: 'jupyter_server_config.d']
- Name of config directory to write extension config.
- user : bool [default: False]
- Get the user's .jupyter config directory
- sys_prefix : bool [default: False]
- Get sys.prefix, i.e. ~/.envs/my-env/etc/jupyter
- """
- config_dir = _get_config_dir(user=user, sys_prefix=sys_prefix)
- config_manager = ExtensionConfigManager(
- read_config_path=[config_dir],
- write_config_dir=os.path.join(config_dir, write_dir),
- )
- extension_manager = ExtensionManager(
- config_manager=config_manager,
- )
- return config_dir, extension_manager
- class ArgumentConflict(ValueError):
- pass
- _base_flags: dict[str, t.Any] = {}
- _base_flags.update(JupyterApp.flags)
- _base_flags.pop("y", None)
- _base_flags.pop("generate-config", None)
- _base_flags.update(
- {
- "user": (
- {
- "BaseExtensionApp": {
- "user": True,
- }
- },
- "Apply the operation only for the given user",
- ),
- "system": (
- {
- "BaseExtensionApp": {
- "user": False,
- "sys_prefix": False,
- }
- },
- "Apply the operation system-wide",
- ),
- "sys-prefix": (
- {
- "BaseExtensionApp": {
- "sys_prefix": True,
- }
- },
- "Use sys.prefix as the prefix for installing extensions (for environments, packaging)",
- ),
- "py": (
- {
- "BaseExtensionApp": {
- "python": True,
- }
- },
- "Install from a Python package",
- ),
- }
- )
- _base_flags["python"] = _base_flags["py"]
- _base_aliases: dict[str, t.Any] = {}
- _base_aliases.update(JupyterApp.aliases)
- class BaseExtensionApp(JupyterApp):
- """Base extension installer app"""
- _log_formatter_cls = LogFormatter # type:ignore[assignment]
- flags = _base_flags
- aliases = _base_aliases
- version = __version__
- user = Bool(False, config=True, help="Whether to do a user install")
- sys_prefix = Bool(True, config=True, help="Use the sys.prefix as the prefix")
- python = Bool(False, config=True, help="Install from a Python package")
- def _log_format_default(self) -> str:
- """A default format for messages"""
- return "%(message)s"
- @property
- def config_dir(self) -> str: # type:ignore[override]
- return _get_config_dir(user=self.user, sys_prefix=self.sys_prefix)
- # Constants for pretty print extension listing function.
- # Window doesn't support coloring in the commandline
- GREEN_ENABLED = "\033[32menabled\033[0m" if os.name != "nt" else "enabled"
- RED_DISABLED = "\033[31mdisabled\033[0m" if os.name != "nt" else "disabled"
- GREEN_OK = "\033[32mOK\033[0m" if os.name != "nt" else "ok"
- RED_X = "\033[31m X\033[0m" if os.name != "nt" else " X"
- # ------------------------------------------------------------------------------
- # Public API
- # ------------------------------------------------------------------------------
- def toggle_server_extension_python(
- import_name: str,
- enabled: bool | None = None,
- parent: t.Any = None,
- user: bool = False,
- sys_prefix: bool = True,
- ) -> None:
- """Toggle the boolean setting for a given server extension
- in a Jupyter config file.
- """
- sys_prefix = False if user else sys_prefix
- config_dir = _get_config_dir(user=user, sys_prefix=sys_prefix)
- manager = ExtensionConfigManager(
- read_config_path=[config_dir],
- write_config_dir=os.path.join(config_dir, "jupyter_server_config.d"),
- )
- if enabled:
- manager.enable(import_name)
- else:
- manager.disable(import_name)
- # ----------------------------------------------------------------------
- # Applications
- # ----------------------------------------------------------------------
- flags = {}
- flags.update(BaseExtensionApp.flags)
- flags.pop("y", None)
- flags.pop("generate-config", None)
- flags.update(
- {
- "user": (
- {
- "ToggleServerExtensionApp": {
- "user": True,
- }
- },
- "Perform the operation for the current user",
- ),
- "system": (
- {
- "ToggleServerExtensionApp": {
- "user": False,
- "sys_prefix": False,
- }
- },
- "Perform the operation system-wide",
- ),
- "sys-prefix": (
- {
- "ToggleServerExtensionApp": {
- "sys_prefix": True,
- }
- },
- "Use sys.prefix as the prefix for installing server extensions",
- ),
- "py": (
- {
- "ToggleServerExtensionApp": {
- "python": True,
- }
- },
- "Install from a Python package",
- ),
- }
- )
- flags["python"] = flags["py"]
- _desc = "Enable/disable a server extension using frontend configuration files."
- class ToggleServerExtensionApp(BaseExtensionApp):
- """A base class for enabling/disabling extensions"""
- name = "jupyter server extension enable/disable"
- description = _desc
- flags = flags
- _toggle_value = Bool()
- _toggle_pre_message = ""
- _toggle_post_message = ""
- def toggle_server_extension(self, import_name: str) -> None:
- """Change the status of a named server extension.
- Uses the value of `self._toggle_value`.
- Parameters
- ---------
- import_name : str
- Importable Python module (dotted-notation) exposing the magic-named
- `load_jupyter_server_extension` function
- """
- # Create an extension manager for this instance.
- config_dir, extension_manager = _get_extmanager_for_context(
- user=self.user, sys_prefix=self.sys_prefix
- )
- try:
- self.log.info(f"{self._toggle_pre_message.capitalize()}: {import_name}")
- self.log.info(f"- Writing config: {config_dir}")
- # Validate the server extension.
- self.log.info(f" - Validating {import_name}...")
- config = extension_manager.config_manager
- enabled = False
- if config:
- jpserver_extensions = config.get_jpserver_extensions()
- if import_name not in jpserver_extensions:
- msg = (
- f"The module '{import_name}' could not be found. Are you "
- "sure the extension is installed?"
- )
- raise ValueError(msg)
- enabled = jpserver_extensions[import_name]
- # Interface with the Extension Package and validate.
- extpkg = ExtensionPackage(name=import_name, enabled=enabled)
- if not extpkg.validate():
- msg = "validation failed"
- raise ValueError(msg)
- version = extpkg.version
- self.log.info(f" {import_name} {version} {GREEN_OK}")
- # Toggle extension config.
- config = extension_manager.config_manager
- if config:
- if self._toggle_value is True:
- config.enable(import_name)
- else:
- config.disable(import_name)
- # If successful, let's log.
- self.log.info(f" - Extension successfully {self._toggle_post_message}.")
- except Exception as err:
- self.log.error(f" {RED_X} Validation failed: {err}")
- def start(self) -> None:
- """Perform the App's actions as configured"""
- if not self.extra_args:
- sys.exit("Please specify a server extension/package to enable or disable")
- for arg in self.extra_args:
- self.toggle_server_extension(arg)
- class EnableServerExtensionApp(ToggleServerExtensionApp):
- """An App that enables (and validates) Server Extensions"""
- name = "jupyter server extension enable"
- description = """
- Enable a server extension in configuration.
- Usage
- jupyter server extension enable [--system|--sys-prefix]
- """
- _toggle_value = True # type:ignore[assignment]
- _toggle_pre_message = "enabling"
- _toggle_post_message = "enabled"
- class DisableServerExtensionApp(ToggleServerExtensionApp):
- """An App that disables Server Extensions"""
- name = "jupyter server extension disable"
- description = """
- Disable a server extension in configuration.
- Usage
- jupyter server extension disable [--system|--sys-prefix]
- """
- _toggle_value = False # type:ignore[assignment]
- _toggle_pre_message = "disabling"
- _toggle_post_message = "disabled"
- class ListServerExtensionsApp(BaseExtensionApp):
- """An App that lists (and validates) Server Extensions"""
- name = "jupyter server extension list"
- version = __version__
- description = "List all server extensions known by the configuration system"
- def list_server_extensions(self) -> None:
- """List all enabled and disabled server extensions, by config path
- Enabled extensions are validated, potentially generating warnings.
- """
- configurations = (
- {"user": True, "sys_prefix": False},
- {"user": False, "sys_prefix": True},
- {"user": False, "sys_prefix": False},
- )
- for option in configurations:
- config_dir = _get_config_dir(**option)
- print(f"Config dir: {config_dir}")
- write_dir = "jupyter_server_config.d"
- config_manager = ExtensionConfigManager(
- read_config_path=[config_dir],
- write_config_dir=os.path.join(config_dir, write_dir),
- )
- jpserver_extensions = config_manager.get_jpserver_extensions()
- for name, enabled in jpserver_extensions.items():
- # Attempt to get extension metadata
- print(f" {name} {GREEN_ENABLED if enabled else RED_DISABLED}")
- try:
- print(f" - Validating {name}...")
- extension = ExtensionPackage(name=name, enabled=enabled)
- if not extension.validate():
- msg = "validation failed"
- raise ValueError(msg)
- version = extension.version
- print(f" {name} {version} {GREEN_OK}")
- except Exception as err:
- self.log.debug("", exc_info=True)
- print(f" {RED_X} {err}")
- # Add a blank line between paths.
- self.log.info("")
- def start(self) -> None:
- """Perform the App's actions as configured"""
- self.list_server_extensions()
- _examples = """
- jupyter server extension list # list all configured server extensions
- jupyter server extension enable --py <packagename> # enable all server extensions in a Python package
- jupyter server extension disable --py <packagename> # disable all server extensions in a Python package
- """
- class ServerExtensionApp(BaseExtensionApp):
- """Root level server extension app"""
- name = "jupyter server extension"
- version = __version__
- description: str = "Work with Jupyter server extensions"
- examples = _examples
- subcommands: dict[str, t.Any] = {
- "enable": (EnableServerExtensionApp, "Enable a server extension"),
- "disable": (DisableServerExtensionApp, "Disable a server extension"),
- "list": (ListServerExtensionsApp, "List server extensions"),
- }
- def start(self) -> None:
- """Perform the App's actions as configured"""
- super().start()
- # The above should have called a subcommand and raised NoStart; if we
- # get here, it didn't, so we should self.log.info a message.
- subcmds = ", ".join(sorted(self.subcommands))
- sys.exit("Please supply at least one subcommand: %s" % subcmds)
- main = ServerExtensionApp.launch_instance
- if __name__ == "__main__":
- main()
|