first comit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .version import __version__, version_info # noqa: F401
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Launch the root jupyter command"""
|
||||
from __future__ import annotations
|
||||
|
||||
from .command import main
|
||||
|
||||
main()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
321
venv/lib/python3.10/site-packages/jupyter_core/application.py
Normal file
321
venv/lib/python3.10/site-packages/jupyter_core/application.py
Normal file
@@ -0,0 +1,321 @@
|
||||
"""
|
||||
A base Application class for Jupyter applications.
|
||||
|
||||
All Jupyter applications should inherit from this.
|
||||
"""
|
||||
|
||||
# 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 copy import deepcopy
|
||||
from pathlib import Path
|
||||
from shutil import which
|
||||
|
||||
from traitlets import Bool, List, Unicode, observe
|
||||
from traitlets.config.application import Application, catch_config_error
|
||||
from traitlets.config.loader import ConfigFileNotFound
|
||||
|
||||
from .paths import (
|
||||
allow_insecure_writes,
|
||||
issue_insecure_write_warning,
|
||||
jupyter_config_dir,
|
||||
jupyter_config_path,
|
||||
jupyter_data_dir,
|
||||
jupyter_path,
|
||||
jupyter_runtime_dir,
|
||||
)
|
||||
from .utils import ensure_dir_exists, ensure_event_loop
|
||||
|
||||
# mypy: disable-error-code="no-untyped-call"
|
||||
|
||||
# aliases and flags
|
||||
|
||||
base_aliases: dict[str, t.Any] = {}
|
||||
if isinstance(Application.aliases, dict):
|
||||
# traitlets 5
|
||||
base_aliases.update(Application.aliases)
|
||||
_jupyter_aliases = {
|
||||
"log-level": "Application.log_level",
|
||||
"config": "JupyterApp.config_file",
|
||||
}
|
||||
base_aliases.update(_jupyter_aliases)
|
||||
|
||||
base_flags: dict[str, t.Any] = {}
|
||||
if isinstance(Application.flags, dict):
|
||||
# traitlets 5
|
||||
base_flags.update(Application.flags)
|
||||
_jupyter_flags: dict[str, t.Any] = {
|
||||
"debug": (
|
||||
{"Application": {"log_level": logging.DEBUG}},
|
||||
"set log level to logging.DEBUG (maximize logging output)",
|
||||
),
|
||||
"generate-config": ({"JupyterApp": {"generate_config": True}}, "generate default config file"),
|
||||
"y": (
|
||||
{"JupyterApp": {"answer_yes": True}},
|
||||
"Answer yes to any questions instead of prompting.",
|
||||
),
|
||||
}
|
||||
base_flags.update(_jupyter_flags)
|
||||
|
||||
|
||||
class NoStart(Exception):
|
||||
"""Exception to raise when an application shouldn't start"""
|
||||
|
||||
|
||||
class JupyterApp(Application):
|
||||
"""Base class for Jupyter applications"""
|
||||
|
||||
name = "jupyter" # override in subclasses
|
||||
description = "A Jupyter Application"
|
||||
|
||||
aliases = base_aliases
|
||||
flags = base_flags
|
||||
|
||||
def _log_level_default(self) -> int:
|
||||
return logging.INFO
|
||||
|
||||
jupyter_path = List(Unicode())
|
||||
|
||||
def _jupyter_path_default(self) -> list[str]:
|
||||
return jupyter_path()
|
||||
|
||||
config_dir = Unicode()
|
||||
|
||||
def _config_dir_default(self) -> str:
|
||||
return jupyter_config_dir()
|
||||
|
||||
@property
|
||||
def config_file_paths(self) -> list[str]:
|
||||
path = jupyter_config_path()
|
||||
if self.config_dir not in path:
|
||||
# Insert config dir as first item.
|
||||
path.insert(0, self.config_dir)
|
||||
return path
|
||||
|
||||
data_dir = Unicode()
|
||||
|
||||
def _data_dir_default(self) -> str:
|
||||
d = jupyter_data_dir()
|
||||
ensure_dir_exists(d, mode=0o700)
|
||||
return d
|
||||
|
||||
runtime_dir = Unicode()
|
||||
|
||||
def _runtime_dir_default(self) -> str:
|
||||
rd = jupyter_runtime_dir()
|
||||
ensure_dir_exists(rd, mode=0o700)
|
||||
return rd
|
||||
|
||||
@observe("runtime_dir")
|
||||
def _runtime_dir_changed(self, change: t.Any) -> None:
|
||||
ensure_dir_exists(change["new"], mode=0o700)
|
||||
|
||||
generate_config = Bool(False, config=True, help="""Generate default config file.""")
|
||||
|
||||
config_file_name = Unicode(config=True, help="Specify a config file to load.")
|
||||
|
||||
def _config_file_name_default(self) -> str:
|
||||
if not self.name:
|
||||
return ""
|
||||
return self.name.replace("-", "_") + "_config"
|
||||
|
||||
config_file = Unicode(
|
||||
config=True,
|
||||
help="""Full path of a config file.""",
|
||||
)
|
||||
|
||||
answer_yes = Bool(False, config=True, help="""Answer yes to any prompts.""")
|
||||
|
||||
def write_default_config(self) -> None:
|
||||
"""Write our default config to a .py config file"""
|
||||
if self.config_file:
|
||||
config_file = self.config_file
|
||||
else:
|
||||
config_file = str(Path(self.config_dir, self.config_file_name + ".py"))
|
||||
|
||||
if Path(config_file).exists() and not self.answer_yes:
|
||||
answer = ""
|
||||
|
||||
def ask() -> str:
|
||||
prompt = "Overwrite %s with default config? [y/N]" % config_file
|
||||
try:
|
||||
return input(prompt).lower() or "n"
|
||||
except KeyboardInterrupt:
|
||||
print("") # empty line
|
||||
return "n"
|
||||
|
||||
answer = ask()
|
||||
while not answer.startswith(("y", "n")):
|
||||
print("Please answer 'yes' or 'no'")
|
||||
answer = ask()
|
||||
if answer.startswith("n"):
|
||||
return
|
||||
|
||||
config_text = self.generate_config_file()
|
||||
print("Writing default config to: %s" % config_file)
|
||||
ensure_dir_exists(Path(config_file).parent.resolve(), 0o700)
|
||||
with Path.open(Path(config_file), mode="w", encoding="utf-8") as f:
|
||||
f.write(config_text)
|
||||
|
||||
def migrate_config(self) -> None:
|
||||
"""Migrate config/data from IPython 3"""
|
||||
try: # let's see if we can open the marker file
|
||||
# for reading and updating (writing)
|
||||
f_marker = Path.open(Path(self.config_dir, "migrated"), "r+")
|
||||
except FileNotFoundError: # cannot find the marker file
|
||||
pass # that means we have not migrated yet, so continue
|
||||
except OSError: # not readable and/or writable
|
||||
return # so let's give up migration in such an environment
|
||||
else: # if we got here without raising anything,
|
||||
# that means the file exists
|
||||
f_marker.close()
|
||||
return # so we must have already migrated -> bail out
|
||||
|
||||
from .migrate import get_ipython_dir, migrate
|
||||
|
||||
# No IPython dir, nothing to migrate
|
||||
if not Path(get_ipython_dir()).exists():
|
||||
return
|
||||
|
||||
migrate()
|
||||
|
||||
def load_config_file(self, suppress_errors: bool = True) -> None: # type:ignore[override]
|
||||
"""Load the config file.
|
||||
|
||||
By default, errors in loading config are handled, and a warning
|
||||
printed on screen. For testing, the suppress_errors option is set
|
||||
to False, so errors will make tests fail.
|
||||
"""
|
||||
self.log.debug("Searching %s for config files", self.config_file_paths)
|
||||
base_config = "jupyter_config"
|
||||
try:
|
||||
super().load_config_file(
|
||||
base_config,
|
||||
path=self.config_file_paths,
|
||||
)
|
||||
except ConfigFileNotFound:
|
||||
# ignore errors loading parent
|
||||
self.log.debug("Config file %s not found", base_config)
|
||||
|
||||
if self.config_file:
|
||||
path, config_file_name = os.path.split(self.config_file)
|
||||
else:
|
||||
path = self.config_file_paths # type:ignore[assignment]
|
||||
config_file_name = self.config_file_name
|
||||
|
||||
if not config_file_name or (config_file_name == base_config):
|
||||
return
|
||||
|
||||
try:
|
||||
super().load_config_file(config_file_name, path=path)
|
||||
except ConfigFileNotFound:
|
||||
self.log.debug("Config file not found, skipping: %s", config_file_name)
|
||||
except Exception:
|
||||
# Reraise errors for testing purposes, or if set in
|
||||
# self.raise_config_file_errors
|
||||
if (not suppress_errors) or self.raise_config_file_errors:
|
||||
raise
|
||||
self.log.warning("Error loading config file: %s", config_file_name, exc_info=True)
|
||||
|
||||
# subcommand-related
|
||||
def _find_subcommand(self, name: str) -> str:
|
||||
name = f"{self.name}-{name}"
|
||||
return which(name) or ""
|
||||
|
||||
@property
|
||||
def _dispatching(self) -> bool:
|
||||
"""Return whether we are dispatching to another command
|
||||
|
||||
or running ourselves.
|
||||
"""
|
||||
return bool(self.generate_config or self.subapp or self.subcommand)
|
||||
|
||||
subcommand = Unicode()
|
||||
|
||||
@catch_config_error
|
||||
def initialize(self, argv: t.Any = None) -> None:
|
||||
"""Initialize the application."""
|
||||
# don't hook up crash handler before parsing command-line
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
if argv:
|
||||
subc = self._find_subcommand(argv[0])
|
||||
if subc:
|
||||
self.argv = argv
|
||||
self.subcommand = subc
|
||||
return
|
||||
self.parse_command_line(argv)
|
||||
cl_config = deepcopy(self.config)
|
||||
if self._dispatching:
|
||||
return
|
||||
self.migrate_config()
|
||||
self.load_config_file()
|
||||
# enforce cl-opts override configfile opts:
|
||||
self.update_config(cl_config)
|
||||
if allow_insecure_writes:
|
||||
issue_insecure_write_warning()
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the whole thing"""
|
||||
if self.subcommand:
|
||||
os.execv(self.subcommand, [self.subcommand] + self.argv[1:]) # noqa: S606
|
||||
raise NoStart()
|
||||
|
||||
if self.subapp:
|
||||
self.subapp.start()
|
||||
raise NoStart()
|
||||
|
||||
if self.generate_config:
|
||||
self.write_default_config()
|
||||
raise NoStart()
|
||||
|
||||
@classmethod
|
||||
def launch_instance(cls, argv: t.Any = None, **kwargs: t.Any) -> None:
|
||||
"""Launch an instance of a Jupyter Application"""
|
||||
# Ensure an event loop is set before any other code runs.
|
||||
loop = ensure_event_loop()
|
||||
try:
|
||||
super().launch_instance(argv=argv, **kwargs)
|
||||
except NoStart:
|
||||
return
|
||||
loop.close()
|
||||
|
||||
|
||||
class JupyterAsyncApp(JupyterApp):
|
||||
"""A Jupyter application that runs on an asyncio loop."""
|
||||
|
||||
name = "jupyter_async" # override in subclasses
|
||||
description = "An Async Jupyter Application"
|
||||
|
||||
# Set to True for tornado-based apps.
|
||||
_prefer_selector_loop = False
|
||||
|
||||
async def initialize_async(self, argv: t.Any = None) -> None:
|
||||
"""Initialize the application asynchronoously."""
|
||||
|
||||
async def start_async(self) -> None:
|
||||
"""Run the application in an event loop."""
|
||||
|
||||
@classmethod
|
||||
async def _launch_instance(cls, argv: t.Any = None, **kwargs: t.Any) -> None:
|
||||
app = cls.instance(**kwargs)
|
||||
app.initialize(argv)
|
||||
await app.initialize_async(argv)
|
||||
await app.start_async()
|
||||
|
||||
@classmethod
|
||||
def launch_instance(cls, argv: t.Any = None, **kwargs: t.Any) -> None:
|
||||
"""Launch an instance of an async Jupyter Application"""
|
||||
loop = ensure_event_loop(cls._prefer_selector_loop)
|
||||
coro = cls._launch_instance(argv, **kwargs)
|
||||
loop.run_until_complete(coro)
|
||||
loop.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
JupyterApp.launch_instance()
|
||||
405
venv/lib/python3.10/site-packages/jupyter_core/command.py
Normal file
405
venv/lib/python3.10/site-packages/jupyter_core/command.py
Normal file
@@ -0,0 +1,405 @@
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
"""The root `jupyter` command.
|
||||
|
||||
This does nothing other than dispatch to subcommands or output path info.
|
||||
"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
import site
|
||||
import sys
|
||||
import sysconfig
|
||||
from pathlib import Path
|
||||
from shutil import which
|
||||
from subprocess import Popen
|
||||
from typing import Any
|
||||
|
||||
from . import paths
|
||||
from .version import __version__
|
||||
|
||||
|
||||
class JupyterParser(argparse.ArgumentParser):
|
||||
"""A Jupyter argument parser."""
|
||||
|
||||
@property
|
||||
def epilog(self) -> str | None:
|
||||
"""Add subcommands to epilog on request
|
||||
|
||||
Avoids searching PATH for subcommands unless help output is requested.
|
||||
"""
|
||||
return "Available subcommands: %s" % " ".join(list_subcommands())
|
||||
|
||||
@epilog.setter
|
||||
def epilog(self, x: Any) -> None:
|
||||
"""Ignore epilog set in Parser.__init__"""
|
||||
|
||||
def argcomplete(self) -> None:
|
||||
"""Trigger auto-completion, if enabled"""
|
||||
try:
|
||||
import argcomplete # type: ignore[import-not-found]
|
||||
|
||||
argcomplete.autocomplete(self)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def jupyter_parser() -> JupyterParser:
|
||||
"""Create a jupyter parser object."""
|
||||
parser = JupyterParser(
|
||||
description="Jupyter: Interactive Computing",
|
||||
)
|
||||
group = parser.add_mutually_exclusive_group(required=False)
|
||||
# don't use argparse's version action because it prints to stderr on py2
|
||||
group.add_argument(
|
||||
"--version", action="store_true", help="show the versions of core jupyter packages and exit"
|
||||
)
|
||||
subcommand_action = group.add_argument(
|
||||
"subcommand", type=str, nargs="?", help="the subcommand to launch"
|
||||
)
|
||||
# For argcomplete, supply all known subcommands
|
||||
subcommand_action.completer = lambda *args, **kwargs: list_subcommands() # type: ignore[attr-defined] # noqa: ARG005
|
||||
|
||||
group.add_argument("--config-dir", action="store_true", help="show Jupyter config dir")
|
||||
group.add_argument("--data-dir", action="store_true", help="show Jupyter data dir")
|
||||
group.add_argument("--runtime-dir", action="store_true", help="show Jupyter runtime dir")
|
||||
group.add_argument(
|
||||
"--paths",
|
||||
action="store_true",
|
||||
help="show all Jupyter paths. Add --json for machine-readable format.",
|
||||
)
|
||||
parser.add_argument("--json", action="store_true", help="output paths as machine-readable json")
|
||||
parser.add_argument("--debug", action="store_true", help="output debug information about paths")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def list_subcommands() -> list[str]:
|
||||
"""List all jupyter subcommands
|
||||
|
||||
searches PATH for `jupyter-name`
|
||||
|
||||
Returns a list of jupyter's subcommand names, without the `jupyter-` prefix.
|
||||
Nested children (e.g. jupyter-sub-subsub) are not included.
|
||||
"""
|
||||
subcommand_tuples = set()
|
||||
# construct a set of `('foo', 'bar') from `jupyter-foo-bar`
|
||||
for d in _path_with_self():
|
||||
try:
|
||||
names = os.listdir(d)
|
||||
except OSError:
|
||||
continue
|
||||
for name in names:
|
||||
if name.startswith("jupyter-"):
|
||||
if sys.platform.startswith("win"):
|
||||
# remove file-extension on Windows
|
||||
name = os.path.splitext(name)[0] # noqa: PTH122, PLW2901
|
||||
subcommand_tuples.add(tuple(name.split("-")[1:]))
|
||||
# build a set of subcommand strings, excluding subcommands whose parents are defined
|
||||
subcommands = set()
|
||||
# Only include `jupyter-foo-bar` if `jupyter-foo` is not already present
|
||||
for sub_tup in subcommand_tuples:
|
||||
if not any(sub_tup[:i] in subcommand_tuples for i in range(1, len(sub_tup))):
|
||||
subcommands.add("-".join(sub_tup))
|
||||
return sorted(subcommands)
|
||||
|
||||
|
||||
def _execvp(cmd: str, argv: list[str]) -> None:
|
||||
"""execvp, except on Windows where it uses Popen
|
||||
|
||||
Python provides execvp on Windows, but its behavior is problematic (Python bug#9148).
|
||||
"""
|
||||
if sys.platform.startswith("win"):
|
||||
# PATH is ignored when shell=False,
|
||||
# so rely on shutil.which
|
||||
cmd_path = which(cmd)
|
||||
if cmd_path is None:
|
||||
raise OSError("%r not found" % cmd, errno.ENOENT)
|
||||
p = Popen([cmd_path] + argv[1:]) # noqa: S603
|
||||
# Don't raise KeyboardInterrupt in the parent process.
|
||||
# Set this after spawning, to avoid subprocess inheriting handler.
|
||||
import signal
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
p.wait()
|
||||
sys.exit(p.returncode)
|
||||
else:
|
||||
os.execvp(cmd, argv) # noqa: S606
|
||||
|
||||
|
||||
def _jupyter_abspath(subcommand: str) -> str:
|
||||
"""This method get the abspath of a specified jupyter-subcommand with no
|
||||
changes on ENV.
|
||||
"""
|
||||
# get env PATH with self
|
||||
search_path = os.pathsep.join(_path_with_self())
|
||||
# get the abs path for the jupyter-<subcommand>
|
||||
jupyter_subcommand = f"jupyter-{subcommand}"
|
||||
abs_path = which(jupyter_subcommand, path=search_path)
|
||||
if abs_path is None:
|
||||
msg = f"\nJupyter command `{jupyter_subcommand}` not found."
|
||||
raise Exception(msg)
|
||||
|
||||
if not os.access(abs_path, os.X_OK):
|
||||
msg = f"\nJupyter command `{jupyter_subcommand}` is not executable."
|
||||
raise Exception(msg)
|
||||
|
||||
return abs_path
|
||||
|
||||
|
||||
def _path_with_self() -> list[str]:
|
||||
"""Put `jupyter`'s dir at the front of PATH
|
||||
|
||||
Ensures that /path/to/jupyter subcommand
|
||||
will do /path/to/jupyter-subcommand
|
||||
even if /other/jupyter-subcommand is ahead of it on PATH
|
||||
"""
|
||||
path_list = (os.environ.get("PATH") or os.defpath).split(os.pathsep)
|
||||
|
||||
# Insert the "scripts" directory for this Python installation
|
||||
# This allows the "jupyter" command to be relocated, while still
|
||||
# finding subcommands that have been installed in the default
|
||||
# location.
|
||||
# We put the scripts directory at the *end* of PATH, so that
|
||||
# if the user explicitly overrides a subcommand, that override
|
||||
# still takes effect.
|
||||
try:
|
||||
bindir = sysconfig.get_path("scripts")
|
||||
except KeyError:
|
||||
# The Python environment does not specify a "scripts" location
|
||||
pass
|
||||
else:
|
||||
path_list.append(bindir)
|
||||
|
||||
scripts = [sys.argv[0]]
|
||||
if Path(scripts[0]).is_symlink():
|
||||
# include realpath, if `jupyter` is a symlink
|
||||
scripts.append(os.path.realpath(scripts[0]))
|
||||
|
||||
for script in scripts:
|
||||
bindir = str(Path(script).parent)
|
||||
if Path(bindir).is_dir() and os.access(script, os.X_OK): # only if it's a script
|
||||
# ensure executable's dir is on PATH
|
||||
# avoids missing subcommands when jupyter is run via absolute path
|
||||
path_list.insert(0, bindir)
|
||||
return path_list
|
||||
|
||||
|
||||
def _evaluate_argcomplete(parser: JupyterParser) -> list[str]:
|
||||
"""If argcomplete is enabled, trigger autocomplete or return current words
|
||||
|
||||
If the first word looks like a subcommand, return the current command
|
||||
that is attempting to be completed so that the subcommand can evaluate it;
|
||||
otherwise auto-complete using the main parser.
|
||||
"""
|
||||
try:
|
||||
# traitlets >= 5.8 provides some argcomplete support,
|
||||
# use helper methods to jump to argcomplete
|
||||
from traitlets.config.argcomplete_config import (
|
||||
get_argcomplete_cwords,
|
||||
increment_argcomplete_index,
|
||||
)
|
||||
|
||||
cwords = get_argcomplete_cwords()
|
||||
if cwords and len(cwords) > 1 and not cwords[1].startswith("-"):
|
||||
# If first completion word looks like a subcommand,
|
||||
# increment word from which to start handling arguments
|
||||
increment_argcomplete_index()
|
||||
return cwords
|
||||
# Otherwise no subcommand, directly autocomplete and exit
|
||||
parser.argcomplete()
|
||||
except ImportError:
|
||||
# traitlets >= 5.8 not available, just try to complete this without
|
||||
# worrying about subcommands
|
||||
parser.argcomplete()
|
||||
msg = "Control flow should not reach end of autocomplete()"
|
||||
raise AssertionError(msg)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""The command entry point."""
|
||||
parser = jupyter_parser()
|
||||
argv = sys.argv
|
||||
subcommand = None
|
||||
if "_ARGCOMPLETE" in os.environ:
|
||||
argv = _evaluate_argcomplete(parser)
|
||||
subcommand = argv[1]
|
||||
elif len(argv) > 1 and not argv[1].startswith("-"):
|
||||
# Don't parse if a subcommand is given
|
||||
# Avoids argparse gobbling up args passed to subcommand, such as `-h`.
|
||||
subcommand = argv[1]
|
||||
else:
|
||||
args, opts = parser.parse_known_args()
|
||||
subcommand = args.subcommand
|
||||
if args.version:
|
||||
print("Selected Jupyter core packages...")
|
||||
for package in [
|
||||
"IPython",
|
||||
"ipykernel",
|
||||
"ipywidgets",
|
||||
"jupyter_client",
|
||||
"jupyter_core",
|
||||
"jupyter_server",
|
||||
"jupyterlab",
|
||||
"nbclient",
|
||||
"nbconvert",
|
||||
"nbformat",
|
||||
"notebook",
|
||||
"qtconsole",
|
||||
"traitlets",
|
||||
]:
|
||||
try:
|
||||
if package == "jupyter_core": # We're already here
|
||||
version = __version__
|
||||
else:
|
||||
mod = __import__(package)
|
||||
version = mod.__version__
|
||||
except ImportError:
|
||||
version = "not installed"
|
||||
print(f"{package:<17}:", version)
|
||||
return
|
||||
if args.json and not args.paths:
|
||||
sys.exit("--json is only used with --paths")
|
||||
if args.debug and not args.paths:
|
||||
sys.exit("--debug is only used with --paths")
|
||||
if args.debug and args.json:
|
||||
sys.exit("--debug cannot be used with --json")
|
||||
if args.config_dir:
|
||||
print(paths.jupyter_config_dir())
|
||||
return
|
||||
if args.data_dir:
|
||||
print(paths.jupyter_data_dir())
|
||||
return
|
||||
if args.runtime_dir:
|
||||
print(paths.jupyter_runtime_dir())
|
||||
return
|
||||
if args.paths:
|
||||
data = {}
|
||||
data["runtime"] = [paths.jupyter_runtime_dir()]
|
||||
data["config"] = paths.jupyter_config_path()
|
||||
data["data"] = paths.jupyter_path()
|
||||
if args.json:
|
||||
print(json.dumps(data))
|
||||
else:
|
||||
if args.debug:
|
||||
env = os.environ
|
||||
|
||||
if paths.use_platform_dirs():
|
||||
print(
|
||||
"JUPYTER_PLATFORM_DIRS is set to a true value, so we use platformdirs to find platform-specific directories"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"JUPYTER_PLATFORM_DIRS is set to a false value, or is not set, so we use hardcoded legacy paths for platform-specific directories"
|
||||
)
|
||||
|
||||
if paths.prefer_environment_over_user():
|
||||
print(
|
||||
"JUPYTER_PREFER_ENV_PATH is set to a true value, or JUPYTER_PREFER_ENV_PATH is not set and we detected a virtual environment, making the environment-level path preferred over the user-level path for data and config"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"JUPYTER_PREFER_ENV_PATH is set to a false value, or JUPYTER_PREFER_ENV_PATH is not set and we did not detect a virtual environment, making the user-level path preferred over the environment-level path for data and config"
|
||||
)
|
||||
|
||||
# config path list
|
||||
if env.get("JUPYTER_NO_CONFIG"):
|
||||
print(
|
||||
"JUPYTER_NO_CONFIG is set, making the config path list only a single temporary directory"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"JUPYTER_NO_CONFIG is not set, so we use the full path list for config"
|
||||
)
|
||||
|
||||
if env.get("JUPYTER_CONFIG_PATH"):
|
||||
print(
|
||||
f"JUPYTER_CONFIG_PATH is set to '{env.get('JUPYTER_CONFIG_PATH')}', which is prepended to the config path list (unless JUPYTER_NO_CONFIG is set)"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"JUPYTER_CONFIG_PATH is not set, so we do not prepend anything to the config paths"
|
||||
)
|
||||
|
||||
if env.get("JUPYTER_CONFIG_DIR"):
|
||||
print(
|
||||
f"JUPYTER_CONFIG_DIR is set to '{env.get('JUPYTER_CONFIG_DIR')}', overriding the default user-level config directory"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"JUPYTER_CONFIG_DIR is not set, so we use the default user-level config directory"
|
||||
)
|
||||
|
||||
if site.ENABLE_USER_SITE:
|
||||
print(
|
||||
f"Python's site.ENABLE_USER_SITE is True, so we add the user site directory '{site.getuserbase()}'"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"Python's site.ENABLE_USER_SITE is not True, so we do not add the Python site user directory '{site.getuserbase()}'"
|
||||
)
|
||||
|
||||
# data path list
|
||||
if env.get("JUPYTER_PATH"):
|
||||
print(
|
||||
f"JUPYTER_PATH is set to '{env.get('JUPYTER_PATH')}', which is prepended to the data paths"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"JUPYTER_PATH is not set, so we do not prepend anything to the data paths"
|
||||
)
|
||||
|
||||
if env.get("JUPYTER_DATA_DIR"):
|
||||
print(
|
||||
f"JUPYTER_DATA_DIR is set to '{env.get('JUPYTER_DATA_DIR')}', overriding the default user-level data directory"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"JUPYTER_DATA_DIR is not set, so we use the default user-level data directory"
|
||||
)
|
||||
|
||||
# runtime directory
|
||||
if env.get("JUPYTER_RUNTIME_DIR"):
|
||||
print(
|
||||
f"JUPYTER_RUNTIME_DIR is set to '{env.get('JUPYTER_RUNTIME_DIR')}', overriding the default runtime directory"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"JUPYTER_RUNTIME_DIR is not set, so we use the default runtime directory"
|
||||
)
|
||||
|
||||
print()
|
||||
|
||||
for name in sorted(data):
|
||||
path = data[name]
|
||||
print("%s:" % name)
|
||||
for p in path:
|
||||
print(" " + p)
|
||||
return
|
||||
|
||||
if not subcommand:
|
||||
parser.print_help(file=sys.stderr)
|
||||
sys.exit("\nPlease specify a subcommand or one of the optional arguments.")
|
||||
|
||||
try:
|
||||
command = _jupyter_abspath(subcommand)
|
||||
except Exception as e:
|
||||
parser.print_help(file=sys.stderr)
|
||||
# special-case alias of "jupyter help" to "jupyter --help"
|
||||
if subcommand == "help":
|
||||
return
|
||||
sys.exit(str(e))
|
||||
|
||||
try:
|
||||
_execvp(command, [command] + argv[2:])
|
||||
except OSError as e:
|
||||
sys.exit(f"Error executing Jupyter command {subcommand!r}: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
279
venv/lib/python3.10/site-packages/jupyter_core/migrate.py
Normal file
279
venv/lib/python3.10/site-packages/jupyter_core/migrate.py
Normal file
@@ -0,0 +1,279 @@
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
"""Migrating IPython < 4.0 to Jupyter
|
||||
|
||||
This *copies* configuration and resources to their new locations in Jupyter
|
||||
|
||||
Migrations:
|
||||
|
||||
- .ipython/
|
||||
- nbextensions -> JUPYTER_DATA_DIR/nbextensions
|
||||
- kernels -> JUPYTER_DATA_DIR/kernels
|
||||
|
||||
- .ipython/profile_default/
|
||||
- static/custom -> .jupyter/custom
|
||||
- nbconfig -> .jupyter/nbconfig
|
||||
- security/
|
||||
|
||||
- notebook_secret, notebook_cookie_secret, nbsignatures.db -> JUPYTER_DATA_DIR
|
||||
|
||||
- ipython_{notebook,nbconvert,qtconsole}_config.py -> .jupyter/jupyter_{name}_config.py
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from traitlets.config.loader import JSONFileConfigLoader, PyFileConfigLoader
|
||||
from traitlets.log import get_logger
|
||||
|
||||
from .application import JupyterApp
|
||||
from .paths import jupyter_config_dir, jupyter_data_dir
|
||||
from .utils import ensure_dir_exists
|
||||
|
||||
# mypy: disable-error-code="no-untyped-call"
|
||||
|
||||
|
||||
migrations = {
|
||||
str(Path("{ipython_dir}", "nbextensions")): str(Path("{jupyter_data}", "nbextensions")),
|
||||
str(Path("{ipython_dir}", "kernels")): str(Path("{jupyter_data}", "kernels")),
|
||||
str(Path("{profile}", "nbconfig")): str(Path("{jupyter_config}", "nbconfig")),
|
||||
}
|
||||
|
||||
custom_src_t = str(Path("{profile}", "static", "custom"))
|
||||
custom_dst_t = str(Path("{jupyter_config}", "custom"))
|
||||
|
||||
for security_file in ("notebook_secret", "notebook_cookie_secret", "nbsignatures.db"):
|
||||
src = str(Path("{profile}", "security", security_file))
|
||||
dst = str(Path("{jupyter_data}", security_file))
|
||||
migrations[src] = dst
|
||||
|
||||
config_migrations = ["notebook", "nbconvert", "qtconsole"]
|
||||
|
||||
regex = re.compile
|
||||
|
||||
config_substitutions = {
|
||||
regex(r"\bIPythonQtConsoleApp\b"): "JupyterQtConsoleApp",
|
||||
regex(r"\bIPythonWidget\b"): "JupyterWidget",
|
||||
regex(r"\bRichIPythonWidget\b"): "RichJupyterWidget",
|
||||
regex(r"\bIPython\.html\b"): "notebook",
|
||||
regex(r"\bIPython\.nbconvert\b"): "nbconvert",
|
||||
}
|
||||
|
||||
|
||||
def get_ipython_dir() -> str:
|
||||
"""Return the IPython directory location.
|
||||
|
||||
Not imported from IPython because the IPython implementation
|
||||
ensures that a writable directory exists,
|
||||
creating a temporary directory if not.
|
||||
We don't want to trigger that when checking if migration should happen.
|
||||
|
||||
We only need to support the IPython < 4 behavior for migration,
|
||||
so importing for forward-compatibility and edge cases is not important.
|
||||
"""
|
||||
return os.environ.get("IPYTHONDIR", str(Path("~/.ipython").expanduser()))
|
||||
|
||||
|
||||
def migrate_dir(src: str, dst: str) -> bool:
|
||||
"""Migrate a directory from src to dst"""
|
||||
log = get_logger()
|
||||
if not os.listdir(src):
|
||||
log.debug("No files in %s", src)
|
||||
return False
|
||||
if Path(dst).exists():
|
||||
if os.listdir(dst):
|
||||
# already exists, non-empty
|
||||
log.debug("%s already exists", dst)
|
||||
return False
|
||||
Path(dst).rmdir()
|
||||
log.info("Copying %s -> %s", src, dst)
|
||||
ensure_dir_exists(Path(dst).parent)
|
||||
shutil.copytree(src, dst, symlinks=True)
|
||||
return True
|
||||
|
||||
|
||||
def migrate_file(src: str | Path, dst: str | Path, substitutions: Any = None) -> bool:
|
||||
"""Migrate a single file from src to dst
|
||||
|
||||
substitutions is an optional dict of {regex: replacement} for performing replacements on the file.
|
||||
"""
|
||||
log = get_logger()
|
||||
if Path(dst).exists():
|
||||
# already exists
|
||||
log.debug("%s already exists", dst)
|
||||
return False
|
||||
log.info("Copying %s -> %s", src, dst)
|
||||
ensure_dir_exists(Path(dst).parent)
|
||||
shutil.copy(src, dst)
|
||||
if substitutions:
|
||||
with Path.open(Path(dst), encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
for pat, replacement in substitutions.items():
|
||||
text = pat.sub(replacement, text)
|
||||
with Path.open(Path(dst), "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
return True
|
||||
|
||||
|
||||
def migrate_one(src: str, dst: str) -> bool:
|
||||
"""Migrate one item
|
||||
|
||||
dispatches to migrate_dir/_file
|
||||
"""
|
||||
log = get_logger()
|
||||
if Path(src).is_file():
|
||||
return migrate_file(src, dst)
|
||||
if Path(src).is_dir():
|
||||
return migrate_dir(src, dst)
|
||||
log.debug("Nothing to migrate for %s", src)
|
||||
return False
|
||||
|
||||
|
||||
def migrate_static_custom(src: str, dst: str) -> bool:
|
||||
"""Migrate non-empty custom.js,css from src to dst
|
||||
|
||||
src, dst are 'custom' directories containing custom.{js,css}
|
||||
"""
|
||||
log = get_logger()
|
||||
migrated = False
|
||||
|
||||
custom_js = Path(src, "custom.js")
|
||||
custom_css = Path(src, "custom.css")
|
||||
# check if custom_js is empty:
|
||||
custom_js_empty = True
|
||||
if Path(custom_js).is_file():
|
||||
with Path.open(custom_js, encoding="utf-8") as f:
|
||||
js = f.read().strip()
|
||||
for line in js.splitlines():
|
||||
if not (line.isspace() or line.strip().startswith(("/*", "*", "//"))):
|
||||
custom_js_empty = False
|
||||
break
|
||||
|
||||
# check if custom_css is empty:
|
||||
custom_css_empty = True
|
||||
if Path(custom_css).is_file():
|
||||
with Path.open(custom_css, encoding="utf-8") as f:
|
||||
css = f.read().strip()
|
||||
custom_css_empty = css.startswith("/*") and css.endswith("*/")
|
||||
|
||||
if custom_js_empty:
|
||||
log.debug("Ignoring empty %s", custom_js)
|
||||
if custom_css_empty:
|
||||
log.debug("Ignoring empty %s", custom_css)
|
||||
|
||||
if custom_js_empty and custom_css_empty:
|
||||
# nothing to migrate
|
||||
return False
|
||||
ensure_dir_exists(dst)
|
||||
|
||||
if not custom_js_empty or not custom_css_empty:
|
||||
ensure_dir_exists(dst)
|
||||
|
||||
if not custom_js_empty and migrate_file(custom_js, Path(dst, "custom.js")):
|
||||
migrated = True
|
||||
if not custom_css_empty and migrate_file(custom_css, Path(dst, "custom.css")):
|
||||
migrated = True
|
||||
|
||||
return migrated
|
||||
|
||||
|
||||
def migrate_config(name: str, env: Any) -> list[Any]:
|
||||
"""Migrate a config file.
|
||||
|
||||
Includes substitutions for updated configurable names.
|
||||
"""
|
||||
log = get_logger()
|
||||
src_base = str(Path(f"{env['profile']}", f"ipython_{name}_config"))
|
||||
dst_base = str(Path(f"{env['jupyter_config']}", f"jupyter_{name}_config"))
|
||||
loaders = {
|
||||
".py": PyFileConfigLoader,
|
||||
".json": JSONFileConfigLoader,
|
||||
}
|
||||
migrated = []
|
||||
for ext in (".py", ".json"):
|
||||
src = src_base + ext
|
||||
dst = dst_base + ext
|
||||
if Path(src).exists():
|
||||
cfg = loaders[ext](src).load_config()
|
||||
if cfg:
|
||||
if migrate_file(src, dst, substitutions=config_substitutions):
|
||||
migrated.append(src)
|
||||
else:
|
||||
# don't migrate empty config files
|
||||
log.debug("Not migrating empty config file: %s", src)
|
||||
return migrated
|
||||
|
||||
|
||||
def migrate() -> bool:
|
||||
"""Migrate IPython configuration to Jupyter"""
|
||||
env = {
|
||||
"jupyter_data": jupyter_data_dir(),
|
||||
"jupyter_config": jupyter_config_dir(),
|
||||
"ipython_dir": get_ipython_dir(),
|
||||
"profile": str(Path(get_ipython_dir(), "profile_default")),
|
||||
}
|
||||
migrated = False
|
||||
for src_t, dst_t in migrations.items():
|
||||
src = src_t.format(**env)
|
||||
dst = dst_t.format(**env)
|
||||
if Path(src).exists() and migrate_one(src, dst):
|
||||
migrated = True
|
||||
|
||||
for name in config_migrations:
|
||||
if migrate_config(name, env):
|
||||
migrated = True
|
||||
|
||||
custom_src = custom_src_t.format(**env)
|
||||
custom_dst = custom_dst_t.format(**env)
|
||||
|
||||
if Path(custom_src).exists() and migrate_static_custom(custom_src, custom_dst):
|
||||
migrated = True
|
||||
|
||||
# write a marker to avoid re-running migration checks
|
||||
ensure_dir_exists(env["jupyter_config"])
|
||||
with Path.open(Path(env["jupyter_config"], "migrated"), "w", encoding="utf-8") as f:
|
||||
f.write(datetime.now(tz=timezone.utc).isoformat())
|
||||
|
||||
return migrated
|
||||
|
||||
|
||||
class JupyterMigrate(JupyterApp):
|
||||
"""A Jupyter Migration App."""
|
||||
|
||||
name = "jupyter-migrate"
|
||||
description = """
|
||||
Migrate configuration and data from .ipython prior to 4.0 to Jupyter locations.
|
||||
|
||||
This migrates:
|
||||
|
||||
- config files in the default profile
|
||||
- kernels in ~/.ipython/kernels
|
||||
- notebook javascript extensions in ~/.ipython/extensions
|
||||
- custom.js/css to .jupyter/custom
|
||||
|
||||
to their new Jupyter locations.
|
||||
|
||||
All files are copied, not moved.
|
||||
If the destinations already exist, nothing will be done.
|
||||
"""
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the application."""
|
||||
if not migrate():
|
||||
self.log.info("Found nothing to migrate.")
|
||||
|
||||
|
||||
main = JupyterMigrate.launch_instance
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1023
venv/lib/python3.10/site-packages/jupyter_core/paths.py
Normal file
1023
venv/lib/python3.10/site-packages/jupyter_core/paths.py
Normal file
File diff suppressed because it is too large
Load Diff
110
venv/lib/python3.10/site-packages/jupyter_core/troubleshoot.py
Executable file
110
venv/lib/python3.10/site-packages/jupyter_core/troubleshoot.py
Executable file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
display environment information that is frequently
|
||||
used to troubleshoot installations of Jupyter or IPython
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
|
||||
def subs(cmd: Union[list[str], str]) -> Optional[str]:
|
||||
"""
|
||||
get data from commands that we need to run outside of python
|
||||
"""
|
||||
try:
|
||||
stdout = subprocess.check_output(cmd) # noqa: S603
|
||||
return stdout.decode("utf-8", "replace").strip()
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
return None
|
||||
|
||||
|
||||
def get_data() -> dict[str, Any]:
|
||||
"""
|
||||
returns a dict of various user environment data
|
||||
"""
|
||||
env: dict[str, Any] = {}
|
||||
env["path"] = os.environ.get("PATH")
|
||||
env["sys_path"] = sys.path
|
||||
env["sys_exe"] = sys.executable
|
||||
env["sys_version"] = sys.version
|
||||
env["platform"] = platform.platform()
|
||||
# FIXME: which on Windows?
|
||||
if sys.platform == "win32":
|
||||
env["where"] = subs(["where", "jupyter"])
|
||||
env["which"] = None
|
||||
else:
|
||||
env["which"] = subs(["which", "-a", "jupyter"])
|
||||
env["where"] = None
|
||||
env["pip"] = subs([sys.executable, "-m", "pip", "list"])
|
||||
env["conda"] = subs(["conda", "list"])
|
||||
env["conda-env"] = subs(["conda", "env", "export"])
|
||||
return env
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
print out useful info
|
||||
"""
|
||||
# pylint: disable=superfluous-parens
|
||||
# args = get_args()
|
||||
if "_ARGCOMPLETE" in os.environ:
|
||||
# No arguments to complete, the script can be slow to run to completion,
|
||||
# so in case someone tries to complete jupyter troubleshoot just exit early
|
||||
return
|
||||
|
||||
environment_data = get_data()
|
||||
|
||||
print("$PATH:")
|
||||
for directory in environment_data["path"].split(os.pathsep):
|
||||
print(f"\t{directory}")
|
||||
|
||||
print("\nsys.path:")
|
||||
for directory in environment_data["sys_path"]:
|
||||
print(f"\t{directory}")
|
||||
|
||||
print("\nsys.executable:")
|
||||
print(f'\t{environment_data["sys_exe"]}')
|
||||
|
||||
print("\nsys.version:")
|
||||
if "\n" in environment_data["sys_version"]:
|
||||
for data in environment_data["sys_version"].split("\n"):
|
||||
print(f"\t{data}")
|
||||
else:
|
||||
print(f'\t{environment_data["sys_version"]}')
|
||||
|
||||
print("\nplatform.platform():")
|
||||
print(f'\t{environment_data["platform"]}')
|
||||
|
||||
if environment_data["which"]:
|
||||
print("\nwhich -a jupyter:")
|
||||
for line in environment_data["which"].split("\n"):
|
||||
print(f"\t{line}")
|
||||
|
||||
if environment_data["where"]:
|
||||
print("\nwhere jupyter:")
|
||||
for line in environment_data["where"].split("\n"):
|
||||
print(f"\t{line}")
|
||||
|
||||
if environment_data["pip"]:
|
||||
print("\npip list:")
|
||||
for package in environment_data["pip"].split("\n"):
|
||||
print(f"\t{package}")
|
||||
|
||||
if environment_data["conda"]:
|
||||
print("\nconda list:")
|
||||
for package in environment_data["conda"].split("\n"):
|
||||
print(f"\t{package}")
|
||||
|
||||
if environment_data["conda-env"]:
|
||||
print("\nconda env:")
|
||||
for package in environment_data["conda-env"].split("\n"):
|
||||
print(f"\t{package}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
206
venv/lib/python3.10/site-packages/jupyter_core/utils/__init__.py
Normal file
206
venv/lib/python3.10/site-packages/jupyter_core/utils/__init__.py
Normal file
@@ -0,0 +1,206 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import atexit
|
||||
import errno
|
||||
import inspect
|
||||
import sys
|
||||
import threading
|
||||
import warnings
|
||||
from contextvars import ContextVar
|
||||
from pathlib import Path
|
||||
from types import FrameType
|
||||
from typing import Any, Awaitable, Callable, TypeVar, cast
|
||||
|
||||
|
||||
def ensure_dir_exists(path: str | Path, mode: int = 0o777) -> None:
|
||||
"""Ensure that a directory exists
|
||||
|
||||
If it doesn't exist, try to create it, protecting against a race condition
|
||||
if another process is doing the same.
|
||||
The default permissions are determined by the current umask.
|
||||
"""
|
||||
try:
|
||||
Path(path).mkdir(parents=True, mode=mode)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
if not Path(path).is_dir():
|
||||
raise OSError("%r exists but is not a directory" % path)
|
||||
|
||||
|
||||
def _get_frame(level: int) -> FrameType | None:
|
||||
"""Get the frame at the given stack level."""
|
||||
# sys._getframe is much faster than inspect.stack, but isn't guaranteed to
|
||||
# exist in all python implementations, so we fall back to inspect.stack()
|
||||
|
||||
# We need to add one to level to account for this get_frame call.
|
||||
if hasattr(sys, "_getframe"):
|
||||
frame = sys._getframe(level + 1)
|
||||
else:
|
||||
frame = inspect.stack(context=0)[level + 1].frame
|
||||
return frame
|
||||
|
||||
|
||||
# This function is from https://github.com/python/cpython/issues/67998
|
||||
# (https://bugs.python.org/file39550/deprecated_module_stacklevel.diff) and
|
||||
# calculates the appropriate stacklevel for deprecations to target the
|
||||
# deprecation for the caller, no matter how many internal stack frames we have
|
||||
# added in the process. For example, with the deprecation warning in the
|
||||
# __init__ below, the appropriate stacklevel will change depending on how deep
|
||||
# the inheritance hierarchy is.
|
||||
def _external_stacklevel(internal: list[str]) -> int:
|
||||
"""Find the stacklevel of the first frame that doesn't contain any of the given internal strings
|
||||
|
||||
The depth will be 1 at minimum in order to start checking at the caller of
|
||||
the function that called this utility method.
|
||||
"""
|
||||
# Get the level of my caller's caller
|
||||
level = 2
|
||||
frame = _get_frame(level)
|
||||
|
||||
# Normalize the path separators:
|
||||
normalized_internal = [str(Path(s)) for s in internal]
|
||||
|
||||
# climb the stack frames while we see internal frames
|
||||
while frame and any(s in str(Path(frame.f_code.co_filename)) for s in normalized_internal):
|
||||
level += 1
|
||||
frame = frame.f_back
|
||||
|
||||
# Return the stack level from the perspective of whoever called us (i.e., one level up)
|
||||
return level - 1
|
||||
|
||||
|
||||
def deprecation(message: str, internal: str | list[str] = "jupyter_core/") -> None:
|
||||
"""Generate a deprecation warning targeting the first frame that is not 'internal'
|
||||
|
||||
internal is a string or list of strings, which if they appear in filenames in the
|
||||
frames, the frames will be considered internal. Changing this can be useful if, for example,
|
||||
we know that our internal code is calling out to another library.
|
||||
"""
|
||||
_internal: list[str]
|
||||
_internal = [internal] if isinstance(internal, str) else internal
|
||||
|
||||
# stack level of the first external frame from here
|
||||
stacklevel = _external_stacklevel(_internal)
|
||||
|
||||
# The call to .warn adds one frame, so bump the stacklevel up by one
|
||||
warnings.warn(message, DeprecationWarning, stacklevel=stacklevel + 1)
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class _TaskRunner:
|
||||
"""A task runner that runs an asyncio event loop on a background thread."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__io_loop: asyncio.AbstractEventLoop | None = None
|
||||
self.__runner_thread: threading.Thread | None = None
|
||||
self.__lock = threading.Lock()
|
||||
atexit.register(self._close)
|
||||
|
||||
def _close(self) -> None:
|
||||
if self.__io_loop:
|
||||
self.__io_loop.stop()
|
||||
|
||||
def _runner(self) -> None:
|
||||
loop = self.__io_loop
|
||||
assert loop is not None
|
||||
try:
|
||||
loop.run_forever()
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
def run(self, coro: Any) -> Any:
|
||||
"""Synchronously run a coroutine on a background thread."""
|
||||
with self.__lock:
|
||||
name = f"{threading.current_thread().name} - runner"
|
||||
if self.__io_loop is None:
|
||||
self.__io_loop = asyncio.new_event_loop()
|
||||
self.__runner_thread = threading.Thread(target=self._runner, daemon=True, name=name)
|
||||
self.__runner_thread.start()
|
||||
fut = asyncio.run_coroutine_threadsafe(coro, self.__io_loop)
|
||||
return fut.result(None)
|
||||
|
||||
|
||||
_runner_map: dict[str, _TaskRunner] = {}
|
||||
_loop: ContextVar[asyncio.AbstractEventLoop | None] = ContextVar("_loop", default=None)
|
||||
|
||||
|
||||
def run_sync(coro: Callable[..., Awaitable[T]]) -> Callable[..., T]:
|
||||
"""Wraps coroutine in a function that blocks until it has executed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
coro : coroutine-function
|
||||
The coroutine-function to be executed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result :
|
||||
Whatever the coroutine-function returns.
|
||||
"""
|
||||
|
||||
if not inspect.iscoroutinefunction(coro):
|
||||
raise AssertionError
|
||||
|
||||
def wrapped(*args: Any, **kwargs: Any) -> Any:
|
||||
name = threading.current_thread().name
|
||||
inner = coro(*args, **kwargs)
|
||||
try:
|
||||
# If a loop is currently running in this thread,
|
||||
# use a task runner.
|
||||
asyncio.get_running_loop()
|
||||
if name not in _runner_map:
|
||||
_runner_map[name] = _TaskRunner()
|
||||
return _runner_map[name].run(inner)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
# Run the loop for this thread.
|
||||
loop = ensure_event_loop()
|
||||
return loop.run_until_complete(inner)
|
||||
|
||||
wrapped.__doc__ = coro.__doc__
|
||||
return wrapped
|
||||
|
||||
|
||||
def ensure_event_loop(prefer_selector_loop: bool = False) -> asyncio.AbstractEventLoop:
|
||||
# Get the loop for this thread, or create a new one.
|
||||
loop = _loop.get()
|
||||
if loop is not None and not loop.is_closed():
|
||||
return loop
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
if sys.platform == "win32" and prefer_selector_loop:
|
||||
loop = asyncio.WindowsSelectorEventLoopPolicy().new_event_loop()
|
||||
else:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
_loop.set(loop)
|
||||
return loop
|
||||
|
||||
|
||||
async def ensure_async(obj: Awaitable[T] | T) -> T:
|
||||
"""Convert a non-awaitable object to a coroutine if needed,
|
||||
and await it if it was not already awaited.
|
||||
|
||||
This function is meant to be called on the result of calling a function,
|
||||
when that function could either be asynchronous or not.
|
||||
"""
|
||||
if inspect.isawaitable(obj):
|
||||
obj = cast(Awaitable[T], obj)
|
||||
try:
|
||||
result = await obj
|
||||
except RuntimeError as e:
|
||||
if str(e) == "cannot reuse already awaited coroutine":
|
||||
# obj is already the coroutine's result
|
||||
return cast(T, obj)
|
||||
raise
|
||||
return result
|
||||
# obj doesn't need to be awaited
|
||||
return cast(T, obj)
|
||||
Binary file not shown.
18
venv/lib/python3.10/site-packages/jupyter_core/version.py
Normal file
18
venv/lib/python3.10/site-packages/jupyter_core/version.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
store the current version info of the jupyter_core.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
# Version string must appear intact for hatch versioning
|
||||
__version__ = "5.7.1"
|
||||
|
||||
# Build up version_info tuple for backwards compatibility
|
||||
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
|
||||
match = re.match(pattern, __version__)
|
||||
assert match is not None
|
||||
parts: list[object] = [int(match[part]) for part in ["major", "minor", "patch"]]
|
||||
if match["rest"]:
|
||||
parts.append(match["rest"])
|
||||
version_info = tuple(parts)
|
||||
Reference in New Issue
Block a user