diff --git a/lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb b/lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb index 2928fa0a..329e8644 100644 --- a/lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb +++ b/lessons/3-NeuralNetworks/03-Perceptron/Perceptron.ipynb @@ -24,7 +24,7 @@ "metadata": {}, "outputs": [], "source": [ - "import pylab\n", + "import matplotlib.pyplot as plt\n", "from matplotlib import gridspec\n", "from sklearn.datasets import make_classification\n", "import numpy as np\n", @@ -131,8 +131,8 @@ "source": [ "def plot_dataset(suptitle, features, labels):\n", " # prepare the plot\n", - " fig, ax = pylab.subplots(1, 1)\n", - " #pylab.subplots_adjust(bottom=0.2, wspace=0.4)\n", + " fig, ax = plt.subplots(1, 1)\n", + " #plt.subplots_adjust(bottom=0.2, wspace=0.4)\n", " fig.suptitle(suptitle, fontsize = 16)\n", " ax.set_xlabel('$x_i[0]$ -- (feature 1)')\n", " ax.set_ylabel('$x_i[1]$ -- (feature 2)')\n", @@ -337,12 +337,12 @@ " x = np.array([-6, 6], dtype='float32')\n", " y = -(weights[0] * x + weights[2])/weights[1]\n", "\n", - " pylab.xlim(-6, 6)\n", - " pylab.ylim(-6, 6) \n", - " pylab.plot(positive_examples[:,0], positive_examples[:,1], 'bo')\n", - " pylab.plot(negative_examples[:,0], negative_examples[:,1], 'ro')\n", - " pylab.plot(x, y, 'g', linewidth=2.0)\n", - " pylab.show()" + " plt.xlim(-6, 6)\n", + " plt.ylim(-6, 6) \n", + " plt.plot(positive_examples[:,0], positive_examples[:,1], 'bo')\n", + " plt.plot(negative_examples[:,0], negative_examples[:,1], 'ro')\n", + " plt.plot(x, y, 'g', linewidth=2.0)\n", + " plt.show()" ] }, { @@ -470,15 +470,15 @@ "snapshots = train_graph(pos_examples,neg_examples)\n", "\n", "def plotit(pos_examples,neg_examples,snapshots,step):\n", - " fig = pylab.figure(figsize=(10,4))\n", + " fig = plt.figure(figsize=(10,4))\n", " fig.add_subplot(1, 2, 1)\n", " plot_boundary(pos_examples, neg_examples, snapshots[step][0])\n", " fig.add_subplot(1, 2, 2)\n", - " pylab.plot(np.arange(len(snapshots[:,1])), snapshots[:,1])\n", - " pylab.ylabel('Accuracy')\n", - " pylab.xlabel('Iteration')\n", - " pylab.plot(step, snapshots[step,1][0], \"bo\")\n", - " pylab.show()\n", + " plt.plot(np.arange(len(snapshots[:,1])), snapshots[:,1])\n", + " plt.ylabel('Accuracy')\n", + " plt.xlabel('Iteration')\n", + " plt.plot(step, snapshots[step,1][0], \"bo\")\n", + " plt.show()\n", "def pl1(step): plotit(pos_examples,neg_examples,snapshots,step)" ] }, @@ -685,11 +685,11 @@ "print(MNIST['Train']['Labels'][0])\n", "features = MNIST['Train']['Features'].astype(np.float32) / 256.0\n", "labels = MNIST['Train']['Labels']\n", - "fig = pylab.figure(figsize=(10,5))\n", + "fig = plt.figure(figsize=(10,5))\n", "for i in range(10):\n", " ax = fig.add_subplot(1,10,i+1)\n", - " pylab.imshow(features[i].reshape(28,28))\n", - "pylab.show()" + " plt.imshow(features[i].reshape(28,28))\n", + "plt.show()" ] }, { @@ -718,16 +718,16 @@ " positive_images = MNIST['Train']['Features'][positive_indices]\n", " negative_images = MNIST['Train']['Features'][negative_indices]\n", "\n", - " fig = pylab.figure()\n", + " fig = plt.figure()\n", " ax = fig.add_subplot(1, 2, 1)\n", - " pylab.imshow(positive_images[0].reshape(28,28), cmap='gray', interpolation='nearest')\n", + " plt.imshow(positive_images[0].reshape(28,28), cmap='gray', interpolation='nearest')\n", " ax.set_xticks([])\n", " ax.set_yticks([])\n", " ax = fig.add_subplot(1, 2, 2)\n", - " pylab.imshow(negative_images[0].reshape(28,28), cmap='gray', interpolation='nearest')\n", + " plt.imshow(negative_images[0].reshape(28,28), cmap='gray', interpolation='nearest')\n", " ax.set_xticks([])\n", " ax.set_yticks([])\n", - " pylab.show()\n", + " plt.show()\n", " \n", " return positive_images, negative_images" ] @@ -774,17 +774,17 @@ "outputs": [], "source": [ "def plotit2(snapshots_mn,step):\n", - " fig = pylab.figure(figsize=(10,4))\n", + " fig = plt.figure(figsize=(10,4))\n", " ax = fig.add_subplot(1, 2, 1)\n", - " pylab.imshow(snapshots_mn[step][0].reshape(28, 28), interpolation='nearest')\n", + " plt.imshow(snapshots_mn[step][0].reshape(28, 28), interpolation='nearest')\n", " ax.set_xticks([])\n", " ax.set_yticks([])\n", - " pylab.colorbar()\n", + " plt.colorbar()\n", " ax = fig.add_subplot(1, 2, 2)\n", " ax.set_ylim([0,1])\n", - " pylab.plot(np.arange(len(snapshots_mn[:,1])), snapshots_mn[:,1])\n", - " pylab.plot(step, snapshots_mn[step,1], \"bo\")\n", - " pylab.show()\n", + " plt.plot(np.arange(len(snapshots_mn[:,1])), snapshots_mn[:,1])\n", + " plt.plot(step, snapshots_mn[step,1], \"bo\")\n", + " plt.show()\n", "def pl3(step): plotit2(snapshots_mn,step)\n", "def pl4(step): plotit2(snapshots_mn2,step) " ] @@ -943,8 +943,8 @@ " pos_points = mypca.transform(positive_images[:200])\n", " neg_points = mypca.transform(negative_images[:200])\n", "\n", - " pylab.plot(pos_points[:,0], pos_points[:,1], 'bo')\n", - " pylab.plot(neg_points[:,0], neg_points[:,1], 'ro')" + " plt.plot(pos_points[:,0], pos_points[:,1], 'bo')\n", + " plt.plot(neg_points[:,0], neg_points[:,1], 'ro')" ] }, { @@ -1053,11 +1053,9 @@ ], "metadata": { "celltoolbar": "Slideshow", - "interpreter": { - "hash": "16aeaa504b544176258e5caf576fc030dfd6fff62d0c15825e7863ff13e121ff" - }, "kernelspec": { - "display_name": "Python 3.8.0 64-bit (conda)", + "display_name": "venv", + "language": "python", "name": "python3" }, "language_info": { @@ -1070,7 +1068,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.12.3" }, "livereveal": { "start_slideshow_at": "selected" diff --git a/myenv/lib/python3.12/site-packages/IPython/__init__.py b/myenv/lib/python3.12/site-packages/IPython/__init__.py new file mode 100644 index 00000000..b7235481 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/__init__.py @@ -0,0 +1,163 @@ +# PYTHON_ARGCOMPLETE_OK +""" +IPython: tools for interactive and parallel computing in Python. + +https://ipython.org +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2008-2011, IPython Development Team. +# Copyright (c) 2001-2007, Fernando Perez +# Copyright (c) 2001, Janko Hauser +# Copyright (c) 2001, Nathaniel Gray +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys + +#----------------------------------------------------------------------------- +# Setup everything +#----------------------------------------------------------------------------- + +# Don't forget to also update setup.py when this changes! +if sys.version_info < (3, 10): + raise ImportError( + """ +IPython 8.19+ supports Python 3.10 and above, following SPEC0. +IPython 8.13+ supports Python 3.9 and above, following NEP 29. +IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29. +When using Python 2.7, please install IPython 5.x LTS Long Term Support version. +Python 3.3 and 3.4 were supported up to IPython 6.x. +Python 3.5 was supported with IPython 7.0 to 7.9. +Python 3.6 was supported with IPython up to 7.16. +Python 3.7 was still supported with the 7.x branch. + +See IPython `README.rst` file for more information: + + https://github.com/ipython/ipython/blob/main/README.rst + +""" + ) + +#----------------------------------------------------------------------------- +# Setup the top level names +#----------------------------------------------------------------------------- + +from .core.getipython import get_ipython +from .core import release +from .core.application import Application +from .terminal.embed import embed + +from .core.interactiveshell import InteractiveShell +from .utils.sysinfo import sys_info +from .utils.frame import extract_module_locals + +__all__ = ["start_ipython", "embed", "start_kernel", "embed_kernel"] + +# Release data +__author__ = '%s <%s>' % (release.author, release.author_email) +__license__ = release.license +__version__ = release.version +version_info = release.version_info +# list of CVEs that should have been patched in this release. +# this is informational and should not be relied upon. +__patched_cves__ = {"CVE-2022-21699", "CVE-2023-24816"} + + +def embed_kernel(module=None, local_ns=None, **kwargs): + """Embed and start an IPython kernel in a given scope. + + If you don't want the kernel to initialize the namespace + from the scope of the surrounding function, + and/or you want to load full IPython configuration, + you probably want `IPython.start_kernel()` instead. + + Parameters + ---------- + module : types.ModuleType, optional + The module to load into IPython globals (default: caller) + local_ns : dict, optional + The namespace to load into IPython user namespace (default: caller) + **kwargs : various, optional + Further keyword args are relayed to the IPKernelApp constructor, + such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`), + allowing configuration of the kernel (see :ref:`kernel_options`). Will only have an effect + on the first embed_kernel call for a given process. + """ + + (caller_module, caller_locals) = extract_module_locals(1) + if module is None: + module = caller_module + if local_ns is None: + local_ns = caller_locals + + # Only import .zmq when we really need it + from ipykernel.embed import embed_kernel as real_embed_kernel + real_embed_kernel(module=module, local_ns=local_ns, **kwargs) + +def start_ipython(argv=None, **kwargs): + """Launch a normal IPython instance (as opposed to embedded) + + `IPython.embed()` puts a shell in a particular calling scope, + such as a function or method for debugging purposes, + which is often not desirable. + + `start_ipython()` does full, regular IPython initialization, + including loading startup files, configuration, etc. + much of which is skipped by `embed()`. + + This is a public API method, and will survive implementation changes. + + Parameters + ---------- + argv : list or None, optional + If unspecified or None, IPython will parse command-line options from sys.argv. + To prevent any command-line parsing, pass an empty list: `argv=[]`. + user_ns : dict, optional + specify this dictionary to initialize the IPython user namespace with particular values. + **kwargs : various, optional + Any other kwargs will be passed to the Application constructor, + such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`), + allowing configuration of the instance (see :ref:`terminal_options`). + """ + from IPython.terminal.ipapp import launch_new_instance + return launch_new_instance(argv=argv, **kwargs) + +def start_kernel(argv=None, **kwargs): + """Launch a normal IPython kernel instance (as opposed to embedded) + + `IPython.embed_kernel()` puts a shell in a particular calling scope, + such as a function or method for debugging purposes, + which is often not desirable. + + `start_kernel()` does full, regular IPython initialization, + including loading startup files, configuration, etc. + much of which is skipped by `embed_kernel()`. + + Parameters + ---------- + argv : list or None, optional + If unspecified or None, IPython will parse command-line options from sys.argv. + To prevent any command-line parsing, pass an empty list: `argv=[]`. + user_ns : dict, optional + specify this dictionary to initialize the IPython user namespace with particular values. + **kwargs : various, optional + Any other kwargs will be passed to the Application constructor, + such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`), + allowing configuration of the kernel (see :ref:`kernel_options`). + """ + import warnings + + warnings.warn( + "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`", + DeprecationWarning, + stacklevel=2, + ) + from ipykernel.kernelapp import launch_new_instance + return launch_new_instance(argv=argv, **kwargs) diff --git a/myenv/lib/python3.12/site-packages/IPython/__main__.py b/myenv/lib/python3.12/site-packages/IPython/__main__.py new file mode 100644 index 00000000..3b460560 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/__main__.py @@ -0,0 +1,15 @@ +# PYTHON_ARGCOMPLETE_OK +# encoding: utf-8 +"""Terminal-based IPython entry point. +""" +# ----------------------------------------------------------------------------- +# Copyright (c) 2012, IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +# ----------------------------------------------------------------------------- + +from IPython import start_ipython + +start_ipython() diff --git a/myenv/lib/python3.12/site-packages/IPython/conftest.py b/myenv/lib/python3.12/site-packages/IPython/conftest.py new file mode 100644 index 00000000..abf61314 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/conftest.py @@ -0,0 +1,87 @@ +import builtins +import inspect +import os +import pathlib +import shutil +import sys +import types + +import pytest + +# Must register before it gets imported +pytest.register_assert_rewrite("IPython.testing.tools") + +from .testing import tools + + +def pytest_collection_modifyitems(items): + """This function is automatically run by pytest passing all collected test + functions. + + We use it to add asyncio marker to all async tests and assert we don't use + test functions that are async generators which wouldn't make sense. + """ + for item in items: + if inspect.iscoroutinefunction(item.obj): + item.add_marker("asyncio") + assert not inspect.isasyncgenfunction(item.obj) + + +def get_ipython(): + from .terminal.interactiveshell import TerminalInteractiveShell + if TerminalInteractiveShell._instance: + return TerminalInteractiveShell.instance() + + config = tools.default_config() + config.TerminalInteractiveShell.simple_prompt = True + + # Create and initialize our test-friendly IPython instance. + shell = TerminalInteractiveShell.instance(config=config) + return shell + + +@pytest.fixture(scope='session', autouse=True) +def work_path(): + path = pathlib.Path("./tmp-ipython-pytest-profiledir") + os.environ["IPYTHONDIR"] = str(path.absolute()) + if path.exists(): + raise ValueError('IPython dir temporary path already exists ! Did previous test run exit successfully ?') + path.mkdir() + yield + shutil.rmtree(str(path.resolve())) + + +def nopage(strng, start=0, screen_lines=0, pager_cmd=None): + if isinstance(strng, dict): + strng = strng.get("text/plain", "") + print(strng) + + +def xsys(self, cmd): + """Replace the default system call with a capturing one for doctest. + """ + # We use getoutput, but we need to strip it because pexpect captures + # the trailing newline differently from commands.getoutput + print(self.getoutput(cmd, split=False, depth=1).rstrip(), end="", file=sys.stdout) + sys.stdout.flush() + + +# for things to work correctly we would need this as a session fixture; +# unfortunately this will fail on some test that get executed as _collection_ +# time (before the fixture run), in particular parametrized test that contain +# yields. so for now execute at import time. +#@pytest.fixture(autouse=True, scope='session') +def inject(): + + builtins.get_ipython = get_ipython + builtins._ip = get_ipython() + builtins.ip = get_ipython() + builtins.ip.system = types.MethodType(xsys, ip) + builtins.ip.builtin_trap.activate() + from .core import page + + page.pager_page = nopage + # yield + + +inject() diff --git a/myenv/lib/python3.12/site-packages/IPython/consoleapp.py b/myenv/lib/python3.12/site-packages/IPython/consoleapp.py new file mode 100644 index 00000000..c2bbe188 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/consoleapp.py @@ -0,0 +1,12 @@ +""" +Shim to maintain backwards compatibility with old IPython.consoleapp imports. +""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from warnings import warn + +warn("The `IPython.consoleapp` package has been deprecated since IPython 4.0." + "You should import from jupyter_client.consoleapp instead.", stacklevel=2) + +from jupyter_client.consoleapp import * diff --git a/myenv/lib/python3.12/site-packages/IPython/core/__init__.py b/myenv/lib/python3.12/site-packages/IPython/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/myenv/lib/python3.12/site-packages/IPython/core/alias.py b/myenv/lib/python3.12/site-packages/IPython/core/alias.py new file mode 100644 index 00000000..845e6b7d --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/alias.py @@ -0,0 +1,267 @@ +# encoding: utf-8 +""" +System command aliases. + +Authors: + +* Fernando Perez +* Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import re +import sys + +from traitlets.config.configurable import Configurable +from .error import UsageError + +from traitlets import List, Instance +from logging import error + +import typing as t + + +#----------------------------------------------------------------------------- +# Utilities +#----------------------------------------------------------------------------- + +# This is used as the pattern for calls to split_user_input. +shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)') + +def default_aliases() -> t.List[t.Tuple[str, str]]: + """Return list of shell aliases to auto-define. + """ + # Note: the aliases defined here should be safe to use on a kernel + # regardless of what frontend it is attached to. Frontends that use a + # kernel in-process can define additional aliases that will only work in + # their case. For example, things like 'less' or 'clear' that manipulate + # the terminal should NOT be declared here, as they will only work if the + # kernel is running inside a true terminal, and not over the network. + + if os.name == 'posix': + default_aliases = [('mkdir', 'mkdir'), ('rmdir', 'rmdir'), + ('mv', 'mv'), ('rm', 'rm'), ('cp', 'cp'), + ('cat', 'cat'), + ] + # Useful set of ls aliases. The GNU and BSD options are a little + # different, so we make aliases that provide as similar as possible + # behavior in ipython, by passing the right flags for each platform + if sys.platform.startswith('linux'): + ls_aliases = [('ls', 'ls -F --color'), + # long ls + ('ll', 'ls -F -o --color'), + # ls normal files only + ('lf', 'ls -F -o --color %l | grep ^-'), + # ls symbolic links + ('lk', 'ls -F -o --color %l | grep ^l'), + # directories or links to directories, + ('ldir', 'ls -F -o --color %l | grep /$'), + # things which are executable + ('lx', 'ls -F -o --color %l | grep ^-..x'), + ] + elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'): + # OpenBSD, NetBSD. The ls implementation on these platforms do not support + # the -G switch and lack the ability to use colorized output. + ls_aliases = [('ls', 'ls -F'), + # long ls + ('ll', 'ls -F -l'), + # ls normal files only + ('lf', 'ls -F -l %l | grep ^-'), + # ls symbolic links + ('lk', 'ls -F -l %l | grep ^l'), + # directories or links to directories, + ('ldir', 'ls -F -l %l | grep /$'), + # things which are executable + ('lx', 'ls -F -l %l | grep ^-..x'), + ] + else: + # BSD, OSX, etc. + ls_aliases = [('ls', 'ls -F -G'), + # long ls + ('ll', 'ls -F -l -G'), + # ls normal files only + ('lf', 'ls -F -l -G %l | grep ^-'), + # ls symbolic links + ('lk', 'ls -F -l -G %l | grep ^l'), + # directories or links to directories, + ('ldir', 'ls -F -G -l %l | grep /$'), + # things which are executable + ('lx', 'ls -F -l -G %l | grep ^-..x'), + ] + default_aliases = default_aliases + ls_aliases + elif os.name in ['nt', 'dos']: + default_aliases = [('ls', 'dir /on'), + ('ddir', 'dir /ad /on'), ('ldir', 'dir /ad /on'), + ('mkdir', 'mkdir'), ('rmdir', 'rmdir'), + ('echo', 'echo'), ('ren', 'ren'), ('copy', 'copy'), + ] + else: + default_aliases = [] + + return default_aliases + + +class AliasError(Exception): + pass + + +class InvalidAliasError(AliasError): + pass + +class Alias(object): + """Callable object storing the details of one alias. + + Instances are registered as magic functions to allow use of aliases. + """ + + # Prepare blacklist + blacklist = {'cd','popd','pushd','dhist','alias','unalias'} + + def __init__(self, shell, name, cmd): + self.shell = shell + self.name = name + self.cmd = cmd + self.__doc__ = "Alias for `!{}`".format(cmd) + self.nargs = self.validate() + + def validate(self): + """Validate the alias, and return the number of arguments.""" + if self.name in self.blacklist: + raise InvalidAliasError("The name %s can't be aliased " + "because it is a keyword or builtin." % self.name) + try: + caller = self.shell.magics_manager.magics['line'][self.name] + except KeyError: + pass + else: + if not isinstance(caller, Alias): + raise InvalidAliasError("The name %s can't be aliased " + "because it is another magic command." % self.name) + + if not (isinstance(self.cmd, str)): + raise InvalidAliasError("An alias command must be a string, " + "got: %r" % self.cmd) + + nargs = self.cmd.count('%s') - self.cmd.count('%%s') + + if (nargs > 0) and (self.cmd.find('%l') >= 0): + raise InvalidAliasError('The %s and %l specifiers are mutually ' + 'exclusive in alias definitions.') + + return nargs + + def __repr__(self): + return "".format(self.name, self.cmd) + + def __call__(self, rest=''): + cmd = self.cmd + nargs = self.nargs + # Expand the %l special to be the user's input line + if cmd.find('%l') >= 0: + cmd = cmd.replace('%l', rest) + rest = '' + + if nargs==0: + if cmd.find('%%s') >= 1: + cmd = cmd.replace('%%s', '%s') + # Simple, argument-less aliases + cmd = '%s %s' % (cmd, rest) + else: + # Handle aliases with positional arguments + args = rest.split(None, nargs) + if len(args) < nargs: + raise UsageError('Alias <%s> requires %s arguments, %s given.' % + (self.name, nargs, len(args))) + cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:])) + + self.shell.system(cmd) + +#----------------------------------------------------------------------------- +# Main AliasManager class +#----------------------------------------------------------------------------- + +class AliasManager(Configurable): + default_aliases: List = List(default_aliases()).tag(config=True) + user_aliases: List = List(default_value=[]).tag(config=True) + shell = Instance( + "IPython.core.interactiveshell.InteractiveShellABC", allow_none=True + ) + + def __init__(self, shell=None, **kwargs): + super(AliasManager, self).__init__(shell=shell, **kwargs) + # For convenient access + if self.shell is not None: + self.linemagics = self.shell.magics_manager.magics["line"] + self.init_aliases() + + def init_aliases(self): + # Load default & user aliases + for name, cmd in self.default_aliases + self.user_aliases: + if ( + cmd.startswith("ls ") + and self.shell is not None + and self.shell.colors == "NoColor" + ): + cmd = cmd.replace(" --color", "") + self.soft_define_alias(name, cmd) + + @property + def aliases(self): + return [(n, func.cmd) for (n, func) in self.linemagics.items() + if isinstance(func, Alias)] + + def soft_define_alias(self, name, cmd): + """Define an alias, but don't raise on an AliasError.""" + try: + self.define_alias(name, cmd) + except AliasError as e: + error("Invalid alias: %s" % e) + + def define_alias(self, name, cmd): + """Define a new alias after validating it. + + This will raise an :exc:`AliasError` if there are validation + problems. + """ + caller = Alias(shell=self.shell, name=name, cmd=cmd) + self.shell.magics_manager.register_function(caller, magic_kind='line', + magic_name=name) + + def get_alias(self, name): + """Return an alias, or None if no alias by that name exists.""" + aname = self.linemagics.get(name, None) + return aname if isinstance(aname, Alias) else None + + def is_alias(self, name): + """Return whether or not a given name has been defined as an alias""" + return self.get_alias(name) is not None + + def undefine_alias(self, name): + if self.is_alias(name): + del self.linemagics[name] + else: + raise ValueError('%s is not an alias' % name) + + def clear_aliases(self): + for name, _ in self.aliases: + self.undefine_alias(name) + + def retrieve_alias(self, name): + """Retrieve the command to which an alias expands.""" + caller = self.get_alias(name) + if caller: + return caller.cmd + else: + raise ValueError('%s is not an alias' % name) diff --git a/myenv/lib/python3.12/site-packages/IPython/core/application.py b/myenv/lib/python3.12/site-packages/IPython/core/application.py new file mode 100644 index 00000000..841e867a --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/application.py @@ -0,0 +1,492 @@ +# encoding: utf-8 +""" +An application for IPython. + +All top-level applications should use the classes in this module for +handling configuration and creating configurables. + +The job of an :class:`Application` is to create the master configuration +object and then create the configurable objects, passing the config to them. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import atexit +from copy import deepcopy +import logging +import os +import shutil +import sys + +from pathlib import Path + +from traitlets.config.application import Application, catch_config_error +from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader +from IPython.core import release, crashhandler +from IPython.core.profiledir import ProfileDir, ProfileDirError +from IPython.paths import get_ipython_dir, get_ipython_package_dir +from IPython.utils.path import ensure_dir_exists +from traitlets import ( + List, Unicode, Type, Bool, Set, Instance, Undefined, + default, observe, +) + +if os.name == "nt": + programdata = os.environ.get("PROGRAMDATA", None) + if programdata is not None: + SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")] + else: # PROGRAMDATA is not defined by default on XP. + SYSTEM_CONFIG_DIRS = [] +else: + SYSTEM_CONFIG_DIRS = [ + "/usr/local/etc/ipython", + "/etc/ipython", + ] + + +ENV_CONFIG_DIRS = [] +_env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython') +if _env_config_dir not in SYSTEM_CONFIG_DIRS: + # only add ENV_CONFIG if sys.prefix is not already included + ENV_CONFIG_DIRS.append(_env_config_dir) + + +_envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS') +if _envvar in {None, ''}: + IPYTHON_SUPPRESS_CONFIG_ERRORS = None +else: + if _envvar.lower() in {'1','true'}: + IPYTHON_SUPPRESS_CONFIG_ERRORS = True + elif _envvar.lower() in {'0','false'} : + IPYTHON_SUPPRESS_CONFIG_ERRORS = False + else: + sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar ) + +# aliases and flags + +base_aliases = {} +if isinstance(Application.aliases, dict): + # traitlets 5 + base_aliases.update(Application.aliases) +base_aliases.update( + { + "profile-dir": "ProfileDir.location", + "profile": "BaseIPythonApplication.profile", + "ipython-dir": "BaseIPythonApplication.ipython_dir", + "log-level": "Application.log_level", + "config": "BaseIPythonApplication.extra_config_file", + } +) + +base_flags = dict() +if isinstance(Application.flags, dict): + # traitlets 5 + base_flags.update(Application.flags) +base_flags.update( + dict( + debug=( + {"Application": {"log_level": logging.DEBUG}}, + "set log level to logging.DEBUG (maximize logging output)", + ), + quiet=( + {"Application": {"log_level": logging.CRITICAL}}, + "set log level to logging.CRITICAL (minimize logging output)", + ), + init=( + { + "BaseIPythonApplication": { + "copy_config_files": True, + "auto_create": True, + } + }, + """Initialize profile with default config files. This is equivalent + to running `ipython profile create ` prior to startup. + """, + ), + ) +) + + +class ProfileAwareConfigLoader(PyFileConfigLoader): + """A Python file config loader that is aware of IPython profiles.""" + def load_subconfig(self, fname, path=None, profile=None): + if profile is not None: + try: + profile_dir = ProfileDir.find_profile_dir_by_name( + get_ipython_dir(), + profile, + ) + except ProfileDirError: + return + path = profile_dir.location + return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path) + +class BaseIPythonApplication(Application): + name = "ipython" + description = "IPython: an enhanced interactive Python shell." + version = Unicode(release.version) + + aliases = base_aliases + flags = base_flags + classes = List([ProfileDir]) + + # enable `load_subconfig('cfg.py', profile='name')` + python_config_loader_class = ProfileAwareConfigLoader + + # Track whether the config_file has changed, + # because some logic happens only if we aren't using the default. + config_file_specified = Set() + + config_file_name = Unicode() + @default('config_file_name') + def _config_file_name_default(self): + return self.name.replace('-','_') + u'_config.py' + @observe('config_file_name') + def _config_file_name_changed(self, change): + if change['new'] != change['old']: + self.config_file_specified.add(change['new']) + + # The directory that contains IPython's builtin profiles. + builtin_profile_dir = Unicode( + os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') + ) + + config_file_paths = List(Unicode()) + @default('config_file_paths') + def _config_file_paths_default(self): + return [] + + extra_config_file = Unicode( + help="""Path to an extra config file to load. + + If specified, load this config file in addition to any other IPython config. + """).tag(config=True) + @observe('extra_config_file') + def _extra_config_file_changed(self, change): + old = change['old'] + new = change['new'] + try: + self.config_files.remove(old) + except ValueError: + pass + self.config_file_specified.add(new) + self.config_files.append(new) + + profile = Unicode(u'default', + help="""The IPython profile to use.""" + ).tag(config=True) + + @observe('profile') + def _profile_changed(self, change): + self.builtin_profile_dir = os.path.join( + get_ipython_package_dir(), u'config', u'profile', change['new'] + ) + + add_ipython_dir_to_sys_path = Bool( + False, + """Should the IPython profile directory be added to sys path ? + + This option was non-existing before IPython 8.0, and ipython_dir was added to + sys path to allow import of extensions present there. This was historical + baggage from when pip did not exist. This now default to false, + but can be set to true for legacy reasons. + """, + ).tag(config=True) + + ipython_dir = Unicode( + help=""" + The name of the IPython directory. This directory is used for logging + configuration (through profiles), history storage, etc. The default + is usually $HOME/.ipython. This option can also be specified through + the environment variable IPYTHONDIR. + """ + ).tag(config=True) + @default('ipython_dir') + def _ipython_dir_default(self): + d = get_ipython_dir() + self._ipython_dir_changed({ + 'name': 'ipython_dir', + 'old': d, + 'new': d, + }) + return d + + _in_init_profile_dir = False + + profile_dir = Instance(ProfileDir, allow_none=True) + + @default('profile_dir') + def _profile_dir_default(self): + # avoid recursion + if self._in_init_profile_dir: + return + # profile_dir requested early, force initialization + self.init_profile_dir() + return self.profile_dir + + overwrite = Bool(False, + help="""Whether to overwrite existing config files when copying""" + ).tag(config=True) + + auto_create = Bool(False, + help="""Whether to create profile dir if it doesn't exist""" + ).tag(config=True) + + config_files = List(Unicode()) + + @default('config_files') + def _config_files_default(self): + return [self.config_file_name] + + copy_config_files = Bool(False, + help="""Whether to install the default config files into the profile dir. + If a new profile is being created, and IPython contains config files for that + profile, then they will be staged into the new directory. Otherwise, + default config files will be automatically generated. + """).tag(config=True) + + verbose_crash = Bool(False, + help="""Create a massive crash report when IPython encounters what may be an + internal error. The default is to append a short message to the + usual traceback""").tag(config=True) + + # The class to use as the crash handler. + crash_handler_class = Type(crashhandler.CrashHandler) + + @catch_config_error + def __init__(self, **kwargs): + super(BaseIPythonApplication, self).__init__(**kwargs) + # ensure current working directory exists + try: + os.getcwd() + except: + # exit if cwd doesn't exist + self.log.error("Current working directory doesn't exist.") + self.exit(1) + + #------------------------------------------------------------------------- + # Various stages of Application creation + #------------------------------------------------------------------------- + + def init_crash_handler(self): + """Create a crash handler, typically setting sys.excepthook to it.""" + self.crash_handler = self.crash_handler_class(self) + sys.excepthook = self.excepthook + def unset_crashhandler(): + sys.excepthook = sys.__excepthook__ + atexit.register(unset_crashhandler) + + def excepthook(self, etype, evalue, tb): + """this is sys.excepthook after init_crashhandler + + set self.verbose_crash=True to use our full crashhandler, instead of + a regular traceback with a short message (crash_handler_lite) + """ + + if self.verbose_crash: + return self.crash_handler(etype, evalue, tb) + else: + return crashhandler.crash_handler_lite(etype, evalue, tb) + + @observe('ipython_dir') + def _ipython_dir_changed(self, change): + old = change['old'] + new = change['new'] + if old is not Undefined: + str_old = os.path.abspath(old) + if str_old in sys.path: + sys.path.remove(str_old) + if self.add_ipython_dir_to_sys_path: + str_path = os.path.abspath(new) + sys.path.append(str_path) + ensure_dir_exists(new) + readme = os.path.join(new, "README") + readme_src = os.path.join( + get_ipython_package_dir(), "config", "profile", "README" + ) + if not os.path.exists(readme) and os.path.exists(readme_src): + shutil.copy(readme_src, readme) + for d in ("extensions", "nbextensions"): + path = os.path.join(new, d) + try: + ensure_dir_exists(path) + except OSError as e: + # this will not be EEXIST + self.log.error("couldn't create path %s: %s", path, e) + self.log.debug("IPYTHONDIR set to: %s", new) + + def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): + """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. + + `suppress_errors` default value is to be `None` in which case the + behavior default to the one of `traitlets.Application`. + + The default value can be set : + - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive). + - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive). + - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset. + + Any other value are invalid, and will make IPython exit with a non-zero return code. + """ + + + self.log.debug("Searching path %s for config files", self.config_file_paths) + base_config = 'ipython_config.py' + self.log.debug("Attempting to load config file: %s" % + base_config) + try: + if suppress_errors is not None: + old_value = Application.raise_config_file_errors + Application.raise_config_file_errors = not suppress_errors; + Application.load_config_file( + self, + base_config, + path=self.config_file_paths + ) + except ConfigFileNotFound: + # ignore errors loading parent + self.log.debug("Config file %s not found", base_config) + pass + if suppress_errors is not None: + Application.raise_config_file_errors = old_value + + for config_file_name in self.config_files: + if not config_file_name or config_file_name == base_config: + continue + self.log.debug("Attempting to load config file: %s" % + self.config_file_name) + try: + Application.load_config_file( + self, + config_file_name, + path=self.config_file_paths + ) + except ConfigFileNotFound: + # Only warn if the default config file was NOT being used. + if config_file_name in self.config_file_specified: + msg = self.log.warning + else: + msg = self.log.debug + msg("Config file not found, skipping: %s", config_file_name) + except Exception: + # For testing purposes. + if not suppress_errors: + raise + self.log.warning("Error loading config file: %s" % + self.config_file_name, exc_info=True) + + def init_profile_dir(self): + """initialize the profile dir""" + self._in_init_profile_dir = True + if self.profile_dir is not None: + # already ran + return + if 'ProfileDir.location' not in self.config: + # location not specified, find by profile name + try: + p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) + except ProfileDirError: + # not found, maybe create it (always create default profile) + if self.auto_create or self.profile == 'default': + try: + p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) + except ProfileDirError: + self.log.fatal("Could not create profile: %r"%self.profile) + self.exit(1) + else: + self.log.info("Created profile dir: %r"%p.location) + else: + self.log.fatal("Profile %r not found."%self.profile) + self.exit(1) + else: + self.log.debug("Using existing profile dir: %r", p.location) + else: + location = self.config.ProfileDir.location + # location is fully specified + try: + p = ProfileDir.find_profile_dir(location, self.config) + except ProfileDirError: + # not found, maybe create it + if self.auto_create: + try: + p = ProfileDir.create_profile_dir(location, self.config) + except ProfileDirError: + self.log.fatal("Could not create profile directory: %r"%location) + self.exit(1) + else: + self.log.debug("Creating new profile dir: %r"%location) + else: + self.log.fatal("Profile directory %r not found."%location) + self.exit(1) + else: + self.log.debug("Using existing profile dir: %r", p.location) + # if profile_dir is specified explicitly, set profile name + dir_name = os.path.basename(p.location) + if dir_name.startswith('profile_'): + self.profile = dir_name[8:] + + self.profile_dir = p + self.config_file_paths.append(p.location) + self._in_init_profile_dir = False + + def init_config_files(self): + """[optionally] copy default config files into profile dir.""" + self.config_file_paths.extend(ENV_CONFIG_DIRS) + self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) + # copy config files + path = Path(self.builtin_profile_dir) + if self.copy_config_files: + src = self.profile + + cfg = self.config_file_name + if path and (path / cfg).exists(): + self.log.warning( + "Staging %r from %s into %r [overwrite=%s]" + % (cfg, src, self.profile_dir.location, self.overwrite) + ) + self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) + else: + self.stage_default_config_file() + else: + # Still stage *bundled* config files, but not generated ones + # This is necessary for `ipython profile=sympy` to load the profile + # on the first go + files = path.glob("*.py") + for fullpath in files: + cfg = fullpath.name + if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): + # file was copied + self.log.warning("Staging bundled %s from %s into %r"%( + cfg, self.profile, self.profile_dir.location) + ) + + + def stage_default_config_file(self): + """auto generate default config file, and stage it into the profile.""" + s = self.generate_config_file() + config_file = Path(self.profile_dir.location) / self.config_file_name + if self.overwrite or not config_file.exists(): + self.log.warning("Generating default config file: %r", (config_file)) + config_file.write_text(s, encoding="utf-8") + + @catch_config_error + def initialize(self, argv=None): + # don't hook up crash handler before parsing command-line + self.parse_command_line(argv) + self.init_crash_handler() + if self.subapp is not None: + # stop here if subapp is taking over + return + # save a copy of CLI config to re-load after config files + # so that it has highest priority + cl_config = deepcopy(self.config) + self.init_profile_dir() + self.init_config_files() + self.load_config_file() + # enforce cl-opts override configfile opts: + self.update_config(cl_config) diff --git a/myenv/lib/python3.12/site-packages/IPython/core/async_helpers.py b/myenv/lib/python3.12/site-packages/IPython/core/async_helpers.py new file mode 100644 index 00000000..4dfac541 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/async_helpers.py @@ -0,0 +1,155 @@ +""" +Async helper function that are invalid syntax on Python 3.5 and below. + +This code is best effort, and may have edge cases not behaving as expected. In +particular it contain a number of heuristics to detect whether code is +effectively async and need to run in an event loop or not. + +Some constructs (like top-level `return`, or `yield`) are taken care of +explicitly to actually raise a SyntaxError and stay as close as possible to +Python semantics. +""" + +import ast +import asyncio +import inspect +from functools import wraps + +_asyncio_event_loop = None + + +def get_asyncio_loop(): + """asyncio has deprecated get_event_loop + + Replicate it here, with our desired semantics: + + - always returns a valid, not-closed loop + - not thread-local like asyncio's, + because we only want one loop for IPython + - if called from inside a coroutine (e.g. in ipykernel), + return the running loop + + .. versionadded:: 8.0 + """ + try: + return asyncio.get_running_loop() + except RuntimeError: + # not inside a coroutine, + # track our own global + pass + + # not thread-local like asyncio's, + # because we only track one event loop to run for IPython itself, + # always in the main thread. + global _asyncio_event_loop + if _asyncio_event_loop is None or _asyncio_event_loop.is_closed(): + _asyncio_event_loop = asyncio.new_event_loop() + return _asyncio_event_loop + + +class _AsyncIORunner: + def __call__(self, coro): + """ + Handler for asyncio autoawait + """ + return get_asyncio_loop().run_until_complete(coro) + + def __str__(self): + return "asyncio" + + +_asyncio_runner = _AsyncIORunner() + + +class _AsyncIOProxy: + """Proxy-object for an asyncio + + Any coroutine methods will be wrapped in event_loop.run_ + """ + + def __init__(self, obj, event_loop): + self._obj = obj + self._event_loop = event_loop + + def __repr__(self): + return f"<_AsyncIOProxy({self._obj!r})>" + + def __getattr__(self, key): + attr = getattr(self._obj, key) + if inspect.iscoroutinefunction(attr): + # if it's a coroutine method, + # return a threadsafe wrapper onto the _current_ asyncio loop + @wraps(attr) + def _wrapped(*args, **kwargs): + concurrent_future = asyncio.run_coroutine_threadsafe( + attr(*args, **kwargs), self._event_loop + ) + return asyncio.wrap_future(concurrent_future) + + return _wrapped + else: + return attr + + def __dir__(self): + return dir(self._obj) + + +def _curio_runner(coroutine): + """ + handler for curio autoawait + """ + import curio + + return curio.run(coroutine) + + +def _trio_runner(async_fn): + import trio + + async def loc(coro): + """ + We need the dummy no-op async def to protect from + trio's internal. See https://github.com/python-trio/trio/issues/89 + """ + return await coro + + return trio.run(loc, async_fn) + + +def _pseudo_sync_runner(coro): + """ + A runner that does not really allow async execution, and just advance the coroutine. + + See discussion in https://github.com/python-trio/trio/issues/608, + + Credit to Nathaniel Smith + """ + try: + coro.send(None) + except StopIteration as exc: + return exc.value + else: + # TODO: do not raise but return an execution result with the right info. + raise RuntimeError( + "{coro_name!r} needs a real async loop".format(coro_name=coro.__name__) + ) + + +def _should_be_async(cell: str) -> bool: + """Detect if a block of code need to be wrapped in an `async def` + + Attempt to parse the block of code, it it compile we're fine. + Otherwise we wrap if and try to compile. + + If it works, assume it should be async. Otherwise Return False. + + Not handled yet: If the block of code has a return statement as the top + level, it will be seen as async. This is a know limitation. + """ + try: + code = compile( + cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) + ) + return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE + except (SyntaxError, MemoryError): + return False diff --git a/myenv/lib/python3.12/site-packages/IPython/core/autocall.py b/myenv/lib/python3.12/site-packages/IPython/core/autocall.py new file mode 100644 index 00000000..54beec3f --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/autocall.py @@ -0,0 +1,70 @@ +# encoding: utf-8 +""" +Autocall capabilities for IPython.core. + +Authors: + +* Brian Granger +* Fernando Perez +* Thomas Kluyver + +Notes +----- +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +class IPyAutocall(object): + """ Instances of this class are always autocalled + + This happens regardless of 'autocall' variable state. Use this to + develop macro-like mechanisms. + """ + _ip = None + rewrite = True + def __init__(self, ip=None): + self._ip = ip + + def set_ip(self, ip): + """Will be used to set _ip point to current ipython instance b/f call + + Override this method if you don't want this to happen. + + """ + self._ip = ip + + +class ExitAutocall(IPyAutocall): + """An autocallable object which will be added to the user namespace so that + exit, exit(), quit or quit() are all valid ways to close the shell.""" + rewrite = False + + def __call__(self): + self._ip.ask_exit() + +class ZMQExitAutocall(ExitAutocall): + """Exit IPython. Autocallable, so it needn't be explicitly called. + + Parameters + ---------- + keep_kernel : bool + If True, leave the kernel alive. Otherwise, tell the kernel to exit too + (default). + """ + def __call__(self, keep_kernel=False): + self._ip.keepkernel_on_exit = keep_kernel + self._ip.ask_exit() diff --git a/myenv/lib/python3.12/site-packages/IPython/core/builtin_trap.py b/myenv/lib/python3.12/site-packages/IPython/core/builtin_trap.py new file mode 100644 index 00000000..a8ea4abc --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/builtin_trap.py @@ -0,0 +1,86 @@ +""" +A context manager for managing things injected into :mod:`builtins`. +""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +import builtins as builtin_mod + +from traitlets.config.configurable import Configurable + +from traitlets import Instance + + +class __BuiltinUndefined(object): pass +BuiltinUndefined = __BuiltinUndefined() + +class __HideBuiltin(object): pass +HideBuiltin = __HideBuiltin() + + +class BuiltinTrap(Configurable): + + shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', + allow_none=True) + + def __init__(self, shell=None): + super(BuiltinTrap, self).__init__(shell=shell, config=None) + self._orig_builtins = {} + # We define this to track if a single BuiltinTrap is nested. + # Only turn off the trap when the outermost call to __exit__ is made. + self._nested_level = 0 + self.shell = shell + # builtins we always add - if set to HideBuiltin, they will just + # be removed instead of being replaced by something else + self.auto_builtins = {'exit': HideBuiltin, + 'quit': HideBuiltin, + 'get_ipython': self.shell.get_ipython, + } + + def __enter__(self): + if self._nested_level == 0: + self.activate() + self._nested_level += 1 + # I return self, so callers can use add_builtin in a with clause. + return self + + def __exit__(self, type, value, traceback): + if self._nested_level == 1: + self.deactivate() + self._nested_level -= 1 + # Returning False will cause exceptions to propagate + return False + + def add_builtin(self, key, value): + """Add a builtin and save the original.""" + bdict = builtin_mod.__dict__ + orig = bdict.get(key, BuiltinUndefined) + if value is HideBuiltin: + if orig is not BuiltinUndefined: #same as 'key in bdict' + self._orig_builtins[key] = orig + del bdict[key] + else: + self._orig_builtins[key] = orig + bdict[key] = value + + def remove_builtin(self, key, orig): + """Remove an added builtin and re-set the original.""" + if orig is BuiltinUndefined: + del builtin_mod.__dict__[key] + else: + builtin_mod.__dict__[key] = orig + + def activate(self): + """Store ipython references in the __builtin__ namespace.""" + + add_builtin = self.add_builtin + for name, func in self.auto_builtins.items(): + add_builtin(name, func) + + def deactivate(self): + """Remove any builtins which might have been added by add_builtins, or + restore overwritten ones to their previous values.""" + remove_builtin = self.remove_builtin + for key, val in self._orig_builtins.items(): + remove_builtin(key, val) + self._orig_builtins.clear() + self._builtins_added = False diff --git a/myenv/lib/python3.12/site-packages/IPython/core/compilerop.py b/myenv/lib/python3.12/site-packages/IPython/core/compilerop.py new file mode 100644 index 00000000..7799a4fc --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/compilerop.py @@ -0,0 +1,214 @@ +"""Compiler tools with improved interactive support. + +Provides compilation machinery similar to codeop, but with caching support so +we can provide interactive tracebacks. + +Authors +------- +* Robert Kern +* Fernando Perez +* Thomas Kluyver +""" + +# Note: though it might be more natural to name this module 'compiler', that +# name is in the stdlib and name collisions with the stdlib tend to produce +# weird problems (often with third-party tools). + +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team. +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib imports +import __future__ +from ast import PyCF_ONLY_AST +import codeop +import functools +import hashlib +import linecache +import operator +import time +from contextlib import contextmanager + +#----------------------------------------------------------------------------- +# Constants +#----------------------------------------------------------------------------- + +# Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h, +# this is used as a bitmask to extract future-related code flags. +PyCF_MASK = functools.reduce(operator.or_, + (getattr(__future__, fname).compiler_flag + for fname in __future__.all_feature_names)) + +#----------------------------------------------------------------------------- +# Local utilities +#----------------------------------------------------------------------------- + +def code_name(code, number=0): + """ Compute a (probably) unique name for code for caching. + + This now expects code to be unicode. + """ + hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest() + # Include the number and 12 characters of the hash in the name. It's + # pretty much impossible that in a single session we'll have collisions + # even with truncated hashes, and the full one makes tracebacks too long + return ''.format(number, hash_digest[:12]) + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +class CachingCompiler(codeop.Compile): + """A compiler that caches code compiled from interactive statements. + """ + + def __init__(self): + codeop.Compile.__init__(self) + + # Caching a dictionary { filename: execution_count } for nicely + # rendered tracebacks. The filename corresponds to the filename + # argument used for the builtins.compile function. + self._filename_map = {} + + def ast_parse(self, source, filename='', symbol='exec'): + """Parse code to an AST with the current compiler flags active. + + Arguments are exactly the same as ast.parse (in the standard library), + and are passed to the built-in compile function.""" + return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1) + + def reset_compiler_flags(self): + """Reset compiler flags to default state.""" + # This value is copied from codeop.Compile.__init__, so if that ever + # changes, it will need to be updated. + self.flags = codeop.PyCF_DONT_IMPLY_DEDENT + + @property + def compiler_flags(self): + """Flags currently active in the compilation process. + """ + return self.flags + + def get_code_name(self, raw_code, transformed_code, number): + """Compute filename given the code, and the cell number. + + Parameters + ---------- + raw_code : str + The raw cell code. + transformed_code : str + The executable Python source code to cache and compile. + number : int + A number which forms part of the code's name. Used for the execution + counter. + + Returns + ------- + The computed filename. + """ + return code_name(transformed_code, number) + + def format_code_name(self, name): + """Return a user-friendly label and name for a code block. + + Parameters + ---------- + name : str + The name for the code block returned from get_code_name + + Returns + ------- + A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used. + """ + if name in self._filename_map: + return "Cell", "In[%s]" % self._filename_map[name] + + def cache(self, transformed_code, number=0, raw_code=None): + """Make a name for a block of code, and cache the code. + + Parameters + ---------- + transformed_code : str + The executable Python source code to cache and compile. + number : int + A number which forms part of the code's name. Used for the execution + counter. + raw_code : str + The raw code before transformation, if None, set to `transformed_code`. + + Returns + ------- + The name of the cached code (as a string). Pass this as the filename + argument to compilation, so that tracebacks are correctly hooked up. + """ + if raw_code is None: + raw_code = transformed_code + + name = self.get_code_name(raw_code, transformed_code, number) + + # Save the execution count + self._filename_map[name] = number + + # Since Python 2.5, setting mtime to `None` means the lines will + # never be removed by `linecache.checkcache`. This means all the + # monkeypatching has *never* been necessary, since this code was + # only added in 2010, at which point IPython had already stopped + # supporting Python 2.4. + # + # Note that `linecache.clearcache` and `linecache.updatecache` may + # still remove our code from the cache, but those show explicit + # intent, and we should not try to interfere. Normally the former + # is never called except when out of memory, and the latter is only + # called for lines *not* in the cache. + entry = ( + len(transformed_code), + None, + [line + "\n" for line in transformed_code.splitlines()], + name, + ) + linecache.cache[name] = entry + return name + + @contextmanager + def extra_flags(self, flags): + ## bits that we'll set to 1 + turn_on_bits = ~self.flags & flags + + + self.flags = self.flags | flags + try: + yield + finally: + # turn off only the bits we turned on so that something like + # __future__ that set flags stays. + self.flags &= ~turn_on_bits + + +def check_linecache_ipython(*args): + """Deprecated since IPython 8.6. Call linecache.checkcache() directly. + + It was already not necessary to call this function directly. If no + CachingCompiler had been created, this function would fail badly. If + an instance had been created, this function would've been monkeypatched + into place. + + As of IPython 8.6, the monkeypatching has gone away entirely. But there + were still internal callers of this function, so maybe external callers + also existed? + """ + import warnings + + warnings.warn( + "Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.", + DeprecationWarning, + stacklevel=2, + ) + linecache.checkcache() diff --git a/myenv/lib/python3.12/site-packages/IPython/core/completer.py b/myenv/lib/python3.12/site-packages/IPython/core/completer.py new file mode 100644 index 00000000..4873be6b --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/completer.py @@ -0,0 +1,3389 @@ +"""Completion for IPython. + +This module started as fork of the rlcompleter module in the Python standard +library. The original enhancements made to rlcompleter have been sent +upstream and were accepted as of Python 2.3, + +This module now support a wide variety of completion mechanism both available +for normal classic Python code, as well as completer for IPython specific +Syntax like magics. + +Latex and Unicode completion +============================ + +IPython and compatible frontends not only can complete your code, but can help +you to input a wide range of characters. In particular we allow you to insert +a unicode character using the tab completion mechanism. + +Forward latex/unicode completion +-------------------------------- + +Forward completion allows you to easily type a unicode character using its latex +name, or unicode long description. To do so type a backslash follow by the +relevant name and press tab: + + +Using latex completion: + +.. code:: + + \\alpha + α + +or using unicode completion: + + +.. code:: + + \\GREEK SMALL LETTER ALPHA + α + + +Only valid Python identifiers will complete. Combining characters (like arrow or +dots) are also available, unlike latex they need to be put after the their +counterpart that is to say, ``F\\\\vec`` is correct, not ``\\\\vecF``. + +Some browsers are known to display combining characters incorrectly. + +Backward latex completion +------------------------- + +It is sometime challenging to know how to type a character, if you are using +IPython, or any compatible frontend you can prepend backslash to the character +and press :kbd:`Tab` to expand it to its latex form. + +.. code:: + + \\α + \\alpha + + +Both forward and backward completions can be deactivated by setting the +:std:configtrait:`Completer.backslash_combining_completions` option to +``False``. + + +Experimental +============ + +Starting with IPython 6.0, this module can make use of the Jedi library to +generate completions both using static analysis of the code, and dynamically +inspecting multiple namespaces. Jedi is an autocompletion and static analysis +for Python. The APIs attached to this new mechanism is unstable and will +raise unless use in an :any:`provisionalcompleter` context manager. + +You will find that the following are experimental: + + - :any:`provisionalcompleter` + - :any:`IPCompleter.completions` + - :any:`Completion` + - :any:`rectify_completions` + +.. note:: + + better name for :any:`rectify_completions` ? + +We welcome any feedback on these new API, and we also encourage you to try this +module in debug mode (start IPython with ``--Completer.debug=True``) in order +to have extra logging information if :any:`jedi` is crashing, or if current +IPython completer pending deprecations are returning results not yet handled +by :any:`jedi` + +Using Jedi for tab completion allow snippets like the following to work without +having to execute any code: + + >>> myvar = ['hello', 42] + ... myvar[1].bi + +Tab completion will be able to infer that ``myvar[1]`` is a real number without +executing almost any code unlike the deprecated :any:`IPCompleter.greedy` +option. + +Be sure to update :any:`jedi` to the latest stable version or to try the +current development version to get better completions. + +Matchers +======== + +All completions routines are implemented using unified *Matchers* API. +The matchers API is provisional and subject to change without notice. + +The built-in matchers include: + +- :any:`IPCompleter.dict_key_matcher`: dictionary key completions, +- :any:`IPCompleter.magic_matcher`: completions for magics, +- :any:`IPCompleter.unicode_name_matcher`, + :any:`IPCompleter.fwd_unicode_matcher` + and :any:`IPCompleter.latex_name_matcher`: see `Forward latex/unicode completion`_, +- :any:`back_unicode_name_matcher` and :any:`back_latex_name_matcher`: see `Backward latex completion`_, +- :any:`IPCompleter.file_matcher`: paths to files and directories, +- :any:`IPCompleter.python_func_kw_matcher` - function keywords, +- :any:`IPCompleter.python_matches` - globals and attributes (v1 API), +- ``IPCompleter.jedi_matcher`` - static analysis with Jedi, +- :any:`IPCompleter.custom_completer_matcher` - pluggable completer with a default + implementation in :any:`InteractiveShell` which uses IPython hooks system + (`complete_command`) with string dispatch (including regular expressions). + Differently to other matchers, ``custom_completer_matcher`` will not suppress + Jedi results to match behaviour in earlier IPython versions. + +Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list. + +Matcher API +----------- + +Simplifying some details, the ``Matcher`` interface can described as + +.. code-block:: + + MatcherAPIv1 = Callable[[str], list[str]] + MatcherAPIv2 = Callable[[CompletionContext], SimpleMatcherResult] + + Matcher = MatcherAPIv1 | MatcherAPIv2 + +The ``MatcherAPIv1`` reflects the matcher API as available prior to IPython 8.6.0 +and remains supported as a simplest way for generating completions. This is also +currently the only API supported by the IPython hooks system `complete_command`. + +To distinguish between matcher versions ``matcher_api_version`` attribute is used. +More precisely, the API allows to omit ``matcher_api_version`` for v1 Matchers, +and requires a literal ``2`` for v2 Matchers. + +Once the API stabilises future versions may relax the requirement for specifying +``matcher_api_version`` by switching to :any:`functools.singledispatch`, therefore +please do not rely on the presence of ``matcher_api_version`` for any purposes. + +Suppression of competing matchers +--------------------------------- + +By default results from all matchers are combined, in the order determined by +their priority. Matchers can request to suppress results from subsequent +matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``. + +When multiple matchers simultaneously request suppression, the results from of +the matcher with higher priority will be returned. + +Sometimes it is desirable to suppress most but not all other matchers; +this can be achieved by adding a set of identifiers of matchers which +should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key. + +The suppression behaviour can is user-configurable via +:std:configtrait:`IPCompleter.suppress_competing_matchers`. +""" + + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +# +# Some of this code originated from rlcompleter in the Python standard library +# Copyright (C) 2001 Python Software Foundation, www.python.org + +from __future__ import annotations +import builtins as builtin_mod +import enum +import glob +import inspect +import itertools +import keyword +import os +import re +import string +import sys +import tokenize +import time +import unicodedata +import uuid +import warnings +from ast import literal_eval +from collections import defaultdict +from contextlib import contextmanager +from dataclasses import dataclass +from functools import cached_property, partial +from types import SimpleNamespace +from typing import ( + Iterable, + Iterator, + List, + Tuple, + Union, + Any, + Sequence, + Dict, + Optional, + TYPE_CHECKING, + Set, + Sized, + TypeVar, + Literal, +) + +from IPython.core.guarded_eval import guarded_eval, EvaluationContext +from IPython.core.error import TryNext +from IPython.core.inputtransformer2 import ESC_MAGIC +from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol +from IPython.core.oinspect import InspectColors +from IPython.testing.skipdoctest import skip_doctest +from IPython.utils import generics +from IPython.utils.decorators import sphinx_options +from IPython.utils.dir2 import dir2, get_real_method +from IPython.utils.docs import GENERATING_DOCUMENTATION +from IPython.utils.path import ensure_dir_exists +from IPython.utils.process import arg_split +from traitlets import ( + Bool, + Enum, + Int, + List as ListTrait, + Unicode, + Dict as DictTrait, + Union as UnionTrait, + observe, +) +from traitlets.config.configurable import Configurable + +import __main__ + +# skip module docstests +__skip_doctest__ = True + + +try: + import jedi + jedi.settings.case_insensitive_completion = False + import jedi.api.helpers + import jedi.api.classes + JEDI_INSTALLED = True +except ImportError: + JEDI_INSTALLED = False + + +if TYPE_CHECKING or GENERATING_DOCUMENTATION and sys.version_info >= (3, 11): + from typing import cast + from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard +else: + from typing import Generic + + def cast(type_, obj): + """Workaround for `TypeError: MatcherAPIv2() takes no arguments`""" + return obj + + # do not require on runtime + NotRequired = Tuple # requires Python >=3.11 + TypedDict = Dict # by extension of `NotRequired` requires 3.11 too + Protocol = object # requires Python >=3.8 + TypeAlias = Any # requires Python >=3.10 + TypeGuard = Generic # requires Python >=3.10 +if GENERATING_DOCUMENTATION: + from typing import TypedDict + +# ----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + +# ranges where we have most of the valid unicode names. We could be more finer +# grained but is it worth it for performance While unicode have character in the +# range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I +# write this). With below range we cover them all, with a density of ~67% +# biggest next gap we consider only adds up about 1% density and there are 600 +# gaps that would need hard coding. +_UNICODE_RANGES = [(32, 0x323B0), (0xE0001, 0xE01F0)] + +# Public API +__all__ = ["Completer", "IPCompleter"] + +if sys.platform == 'win32': + PROTECTABLES = ' ' +else: + PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&' + +# Protect against returning an enormous number of completions which the frontend +# may have trouble processing. +MATCHES_LIMIT = 500 + +# Completion type reported when no type can be inferred. +_UNKNOWN_TYPE = "" + +# sentinel value to signal lack of a match +not_found = object() + +class ProvisionalCompleterWarning(FutureWarning): + """ + Exception raise by an experimental feature in this module. + + Wrap code in :any:`provisionalcompleter` context manager if you + are certain you want to use an unstable feature. + """ + pass + +warnings.filterwarnings('error', category=ProvisionalCompleterWarning) + + +@skip_doctest +@contextmanager +def provisionalcompleter(action='ignore'): + """ + This context manager has to be used in any place where unstable completer + behavior and API may be called. + + >>> with provisionalcompleter(): + ... completer.do_experimental_things() # works + + >>> completer.do_experimental_things() # raises. + + .. note:: + + Unstable + + By using this context manager you agree that the API in use may change + without warning, and that you won't complain if they do so. + + You also understand that, if the API is not to your liking, you should report + a bug to explain your use case upstream. + + We'll be happy to get your feedback, feature requests, and improvements on + any of the unstable APIs! + """ + with warnings.catch_warnings(): + warnings.filterwarnings(action, category=ProvisionalCompleterWarning) + yield + + +def has_open_quotes(s): + """Return whether a string has open quotes. + + This simply counts whether the number of quote characters of either type in + the string is odd. + + Returns + ------- + If there is an open quote, the quote character is returned. Else, return + False. + """ + # We check " first, then ', so complex cases with nested quotes will get + # the " to take precedence. + if s.count('"') % 2: + return '"' + elif s.count("'") % 2: + return "'" + else: + return False + + +def protect_filename(s, protectables=PROTECTABLES): + """Escape a string to protect certain characters.""" + if set(s) & set(protectables): + if sys.platform == "win32": + return '"' + s + '"' + else: + return "".join(("\\" + c if c in protectables else c) for c in s) + else: + return s + + +def expand_user(path:str) -> Tuple[str, bool, str]: + """Expand ``~``-style usernames in strings. + + This is similar to :func:`os.path.expanduser`, but it computes and returns + extra information that will be useful if the input was being used in + computing completions, and you wish to return the completions with the + original '~' instead of its expanded value. + + Parameters + ---------- + path : str + String to be expanded. If no ~ is present, the output is the same as the + input. + + Returns + ------- + newpath : str + Result of ~ expansion in the input path. + tilde_expand : bool + Whether any expansion was performed or not. + tilde_val : str + The value that ~ was replaced with. + """ + # Default values + tilde_expand = False + tilde_val = '' + newpath = path + + if path.startswith('~'): + tilde_expand = True + rest = len(path)-1 + newpath = os.path.expanduser(path) + if rest: + tilde_val = newpath[:-rest] + else: + tilde_val = newpath + + return newpath, tilde_expand, tilde_val + + +def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str: + """Does the opposite of expand_user, with its outputs. + """ + if tilde_expand: + return path.replace(tilde_val, '~') + else: + return path + + +def completions_sorting_key(word): + """key for sorting completions + + This does several things: + + - Demote any completions starting with underscores to the end + - Insert any %magic and %%cellmagic completions in the alphabetical order + by their name + """ + prio1, prio2 = 0, 0 + + if word.startswith('__'): + prio1 = 2 + elif word.startswith('_'): + prio1 = 1 + + if word.endswith('='): + prio1 = -1 + + if word.startswith('%%'): + # If there's another % in there, this is something else, so leave it alone + if not "%" in word[2:]: + word = word[2:] + prio2 = 2 + elif word.startswith('%'): + if not "%" in word[1:]: + word = word[1:] + prio2 = 1 + + return prio1, word, prio2 + + +class _FakeJediCompletion: + """ + This is a workaround to communicate to the UI that Jedi has crashed and to + report a bug. Will be used only id :any:`IPCompleter.debug` is set to true. + + Added in IPython 6.0 so should likely be removed for 7.0 + + """ + + def __init__(self, name): + + self.name = name + self.complete = name + self.type = 'crashed' + self.name_with_symbols = name + self.signature = "" + self._origin = "fake" + self.text = "crashed" + + def __repr__(self): + return '' + + +_JediCompletionLike = Union["jedi.api.Completion", _FakeJediCompletion] + + +class Completion: + """ + Completion object used and returned by IPython completers. + + .. warning:: + + Unstable + + This function is unstable, API may change without warning. + It will also raise unless use in proper context manager. + + This act as a middle ground :any:`Completion` object between the + :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion + object. While Jedi need a lot of information about evaluator and how the + code should be ran/inspected, PromptToolkit (and other frontend) mostly + need user facing information. + + - Which range should be replaced replaced by what. + - Some metadata (like completion type), or meta information to displayed to + the use user. + + For debugging purpose we can also store the origin of the completion (``jedi``, + ``IPython.python_matches``, ``IPython.magics_matches``...). + """ + + __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin'] + + def __init__( + self, + start: int, + end: int, + text: str, + *, + type: Optional[str] = None, + _origin="", + signature="", + ) -> None: + warnings.warn( + "``Completion`` is a provisional API (as of IPython 6.0). " + "It may change without warnings. " + "Use in corresponding context manager.", + category=ProvisionalCompleterWarning, + stacklevel=2, + ) + + self.start = start + self.end = end + self.text = text + self.type = type + self.signature = signature + self._origin = _origin + + def __repr__(self): + return '' % \ + (self.start, self.end, self.text, self.type or '?', self.signature or '?') + + def __eq__(self, other) -> bool: + """ + Equality and hash do not hash the type (as some completer may not be + able to infer the type), but are use to (partially) de-duplicate + completion. + + Completely de-duplicating completion is a bit tricker that just + comparing as it depends on surrounding text, which Completions are not + aware of. + """ + return self.start == other.start and \ + self.end == other.end and \ + self.text == other.text + + def __hash__(self): + return hash((self.start, self.end, self.text)) + + +class SimpleCompletion: + """Completion item to be included in the dictionary returned by new-style Matcher (API v2). + + .. warning:: + + Provisional + + This class is used to describe the currently supported attributes of + simple completion items, and any additional implementation details + should not be relied on. Additional attributes may be included in + future versions, and meaning of text disambiguated from the current + dual meaning of "text to insert" and "text to used as a label". + """ + + __slots__ = ["text", "type"] + + def __init__(self, text: str, *, type: Optional[str] = None): + self.text = text + self.type = type + + def __repr__(self): + return f"" + + +class _MatcherResultBase(TypedDict): + """Definition of dictionary to be returned by new-style Matcher (API v2).""" + + #: Suffix of the provided ``CompletionContext.token``, if not given defaults to full token. + matched_fragment: NotRequired[str] + + #: Whether to suppress results from all other matchers (True), some + #: matchers (set of identifiers) or none (False); default is False. + suppress: NotRequired[Union[bool, Set[str]]] + + #: Identifiers of matchers which should NOT be suppressed when this matcher + #: requests to suppress all other matchers; defaults to an empty set. + do_not_suppress: NotRequired[Set[str]] + + #: Are completions already ordered and should be left as-is? default is False. + ordered: NotRequired[bool] + + +@sphinx_options(show_inherited_members=True, exclude_inherited_from=["dict"]) +class SimpleMatcherResult(_MatcherResultBase, TypedDict): + """Result of new-style completion matcher.""" + + # note: TypedDict is added again to the inheritance chain + # in order to get __orig_bases__ for documentation + + #: List of candidate completions + completions: Sequence[SimpleCompletion] | Iterator[SimpleCompletion] + + +class _JediMatcherResult(_MatcherResultBase): + """Matching result returned by Jedi (will be processed differently)""" + + #: list of candidate completions + completions: Iterator[_JediCompletionLike] + + +AnyMatcherCompletion = Union[_JediCompletionLike, SimpleCompletion] +AnyCompletion = TypeVar("AnyCompletion", AnyMatcherCompletion, Completion) + + +@dataclass +class CompletionContext: + """Completion context provided as an argument to matchers in the Matcher API v2.""" + + # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`) + # which was not explicitly visible as an argument of the matcher, making any refactor + # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers + # from the completer, and make substituting them in sub-classes easier. + + #: Relevant fragment of code directly preceding the cursor. + #: The extraction of token is implemented via splitter heuristic + #: (following readline behaviour for legacy reasons), which is user configurable + #: (by switching the greedy mode). + token: str + + #: The full available content of the editor or buffer + full_text: str + + #: Cursor position in the line (the same for ``full_text`` and ``text``). + cursor_position: int + + #: Cursor line in ``full_text``. + cursor_line: int + + #: The maximum number of completions that will be used downstream. + #: Matchers can use this information to abort early. + #: The built-in Jedi matcher is currently excepted from this limit. + # If not given, return all possible completions. + limit: Optional[int] + + @cached_property + def text_until_cursor(self) -> str: + return self.line_with_cursor[: self.cursor_position] + + @cached_property + def line_with_cursor(self) -> str: + return self.full_text.split("\n")[self.cursor_line] + + +#: Matcher results for API v2. +MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult] + + +class _MatcherAPIv1Base(Protocol): + def __call__(self, text: str) -> List[str]: + """Call signature.""" + ... + + #: Used to construct the default matcher identifier + __qualname__: str + + +class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol): + #: API version + matcher_api_version: Optional[Literal[1]] + + def __call__(self, text: str) -> List[str]: + """Call signature.""" + ... + + +#: Protocol describing Matcher API v1. +MatcherAPIv1: TypeAlias = Union[_MatcherAPIv1Base, _MatcherAPIv1Total] + + +class MatcherAPIv2(Protocol): + """Protocol describing Matcher API v2.""" + + #: API version + matcher_api_version: Literal[2] = 2 + + def __call__(self, context: CompletionContext) -> MatcherResult: + """Call signature.""" + ... + + #: Used to construct the default matcher identifier + __qualname__: str + + +Matcher: TypeAlias = Union[MatcherAPIv1, MatcherAPIv2] + + +def _is_matcher_v1(matcher: Matcher) -> TypeGuard[MatcherAPIv1]: + api_version = _get_matcher_api_version(matcher) + return api_version == 1 + + +def _is_matcher_v2(matcher: Matcher) -> TypeGuard[MatcherAPIv2]: + api_version = _get_matcher_api_version(matcher) + return api_version == 2 + + +def _is_sizable(value: Any) -> TypeGuard[Sized]: + """Determines whether objects is sizable""" + return hasattr(value, "__len__") + + +def _is_iterator(value: Any) -> TypeGuard[Iterator]: + """Determines whether objects is sizable""" + return hasattr(value, "__next__") + + +def has_any_completions(result: MatcherResult) -> bool: + """Check if any result includes any completions.""" + completions = result["completions"] + if _is_sizable(completions): + return len(completions) != 0 + if _is_iterator(completions): + try: + old_iterator = completions + first = next(old_iterator) + result["completions"] = cast( + Iterator[SimpleCompletion], + itertools.chain([first], old_iterator), + ) + return True + except StopIteration: + return False + raise ValueError( + "Completions returned by matcher need to be an Iterator or a Sizable" + ) + + +def completion_matcher( + *, + priority: Optional[float] = None, + identifier: Optional[str] = None, + api_version: int = 1, +): + """Adds attributes describing the matcher. + + Parameters + ---------- + priority : Optional[float] + The priority of the matcher, determines the order of execution of matchers. + Higher priority means that the matcher will be executed first. Defaults to 0. + identifier : Optional[str] + identifier of the matcher allowing users to modify the behaviour via traitlets, + and also used to for debugging (will be passed as ``origin`` with the completions). + + Defaults to matcher function's ``__qualname__`` (for example, + ``IPCompleter.file_matcher`` for the built-in matched defined + as a ``file_matcher`` method of the ``IPCompleter`` class). + api_version: Optional[int] + version of the Matcher API used by this matcher. + Currently supported values are 1 and 2. + Defaults to 1. + """ + + def wrapper(func: Matcher): + func.matcher_priority = priority or 0 # type: ignore + func.matcher_identifier = identifier or func.__qualname__ # type: ignore + func.matcher_api_version = api_version # type: ignore + if TYPE_CHECKING: + if api_version == 1: + func = cast(MatcherAPIv1, func) + elif api_version == 2: + func = cast(MatcherAPIv2, func) + return func + + return wrapper + + +def _get_matcher_priority(matcher: Matcher): + return getattr(matcher, "matcher_priority", 0) + + +def _get_matcher_id(matcher: Matcher): + return getattr(matcher, "matcher_identifier", matcher.__qualname__) + + +def _get_matcher_api_version(matcher): + return getattr(matcher, "matcher_api_version", 1) + + +context_matcher = partial(completion_matcher, api_version=2) + + +_IC = Iterable[Completion] + + +def _deduplicate_completions(text: str, completions: _IC)-> _IC: + """ + Deduplicate a set of completions. + + .. warning:: + + Unstable + + This function is unstable, API may change without warning. + + Parameters + ---------- + text : str + text that should be completed. + completions : Iterator[Completion] + iterator over the completions to deduplicate + + Yields + ------ + `Completions` objects + Completions coming from multiple sources, may be different but end up having + the same effect when applied to ``text``. If this is the case, this will + consider completions as equal and only emit the first encountered. + Not folded in `completions()` yet for debugging purpose, and to detect when + the IPython completer does return things that Jedi does not, but should be + at some point. + """ + completions = list(completions) + if not completions: + return + + new_start = min(c.start for c in completions) + new_end = max(c.end for c in completions) + + seen = set() + for c in completions: + new_text = text[new_start:c.start] + c.text + text[c.end:new_end] + if new_text not in seen: + yield c + seen.add(new_text) + + +def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC: + """ + Rectify a set of completions to all have the same ``start`` and ``end`` + + .. warning:: + + Unstable + + This function is unstable, API may change without warning. + It will also raise unless use in proper context manager. + + Parameters + ---------- + text : str + text that should be completed. + completions : Iterator[Completion] + iterator over the completions to rectify + _debug : bool + Log failed completion + + Notes + ----- + :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though + the Jupyter Protocol requires them to behave like so. This will readjust + the completion to have the same ``start`` and ``end`` by padding both + extremities with surrounding text. + + During stabilisation should support a ``_debug`` option to log which + completion are return by the IPython completer and not found in Jedi in + order to make upstream bug report. + """ + warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). " + "It may change without warnings. " + "Use in corresponding context manager.", + category=ProvisionalCompleterWarning, stacklevel=2) + + completions = list(completions) + if not completions: + return + starts = (c.start for c in completions) + ends = (c.end for c in completions) + + new_start = min(starts) + new_end = max(ends) + + seen_jedi = set() + seen_python_matches = set() + for c in completions: + new_text = text[new_start:c.start] + c.text + text[c.end:new_end] + if c._origin == 'jedi': + seen_jedi.add(new_text) + elif c._origin == "IPCompleter.python_matcher": + seen_python_matches.add(new_text) + yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature) + diff = seen_python_matches.difference(seen_jedi) + if diff and _debug: + print('IPython.python matches have extras:', diff) + + +if sys.platform == 'win32': + DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?' +else: + DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' + +GREEDY_DELIMS = ' =\r\n' + + +class CompletionSplitter(object): + """An object to split an input line in a manner similar to readline. + + By having our own implementation, we can expose readline-like completion in + a uniform manner to all frontends. This object only needs to be given the + line of text to be split and the cursor position on said line, and it + returns the 'word' to be completed on at the cursor after splitting the + entire line. + + What characters are used as splitting delimiters can be controlled by + setting the ``delims`` attribute (this is a property that internally + automatically builds the necessary regular expression)""" + + # Private interface + + # A string of delimiter characters. The default value makes sense for + # IPython's most typical usage patterns. + _delims = DELIMS + + # The expression (a normal string) to be compiled into a regular expression + # for actual splitting. We store it as an attribute mostly for ease of + # debugging, since this type of code can be so tricky to debug. + _delim_expr = None + + # The regular expression that does the actual splitting + _delim_re = None + + def __init__(self, delims=None): + delims = CompletionSplitter._delims if delims is None else delims + self.delims = delims + + @property + def delims(self): + """Return the string of delimiter characters.""" + return self._delims + + @delims.setter + def delims(self, delims): + """Set the delimiters for line splitting.""" + expr = '[' + ''.join('\\'+ c for c in delims) + ']' + self._delim_re = re.compile(expr) + self._delims = delims + self._delim_expr = expr + + def split_line(self, line, cursor_pos=None): + """Split a line of text with a cursor at the given position. + """ + l = line if cursor_pos is None else line[:cursor_pos] + return self._delim_re.split(l)[-1] + + + +class Completer(Configurable): + + greedy = Bool( + False, + help="""Activate greedy completion. + + .. deprecated:: 8.8 + Use :std:configtrait:`Completer.evaluation` and :std:configtrait:`Completer.auto_close_dict_keys` instead. + + When enabled in IPython 8.8 or newer, changes configuration as follows: + + - ``Completer.evaluation = 'unsafe'`` + - ``Completer.auto_close_dict_keys = True`` + """, + ).tag(config=True) + + evaluation = Enum( + ("forbidden", "minimal", "limited", "unsafe", "dangerous"), + default_value="limited", + help="""Policy for code evaluation under completion. + + Successive options allow to enable more eager evaluation for better + completion suggestions, including for nested dictionaries, nested lists, + or even results of function calls. + Setting ``unsafe`` or higher can lead to evaluation of arbitrary user + code on :kbd:`Tab` with potentially unwanted or dangerous side effects. + + Allowed values are: + + - ``forbidden``: no evaluation of code is permitted, + - ``minimal``: evaluation of literals and access to built-in namespace; + no item/attribute evaluationm no access to locals/globals, + no evaluation of any operations or comparisons. + - ``limited``: access to all namespaces, evaluation of hard-coded methods + (for example: :any:`dict.keys`, :any:`object.__getattr__`, + :any:`object.__getitem__`) on allow-listed objects (for example: + :any:`dict`, :any:`list`, :any:`tuple`, ``pandas.Series``), + - ``unsafe``: evaluation of all methods and function calls but not of + syntax with side-effects like `del x`, + - ``dangerous``: completely arbitrary evaluation. + """, + ).tag(config=True) + + use_jedi = Bool(default_value=JEDI_INSTALLED, + help="Experimental: Use Jedi to generate autocompletions. " + "Default to True if jedi is installed.").tag(config=True) + + jedi_compute_type_timeout = Int(default_value=400, + help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types. + Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt + performance by preventing jedi to build its cache. + """).tag(config=True) + + debug = Bool(default_value=False, + help='Enable debug for the Completer. Mostly print extra ' + 'information for experimental jedi integration.')\ + .tag(config=True) + + backslash_combining_completions = Bool(True, + help="Enable unicode completions, e.g. \\alpha . " + "Includes completion of latex commands, unicode names, and expanding " + "unicode characters back to latex commands.").tag(config=True) + + auto_close_dict_keys = Bool( + False, + help=""" + Enable auto-closing dictionary keys. + + When enabled string keys will be suffixed with a final quote + (matching the opening quote), tuple keys will also receive a + separating comma if needed, and keys which are final will + receive a closing bracket (``]``). + """, + ).tag(config=True) + + def __init__(self, namespace=None, global_namespace=None, **kwargs): + """Create a new completer for the command line. + + Completer(namespace=ns, global_namespace=ns2) -> completer instance. + + If unspecified, the default namespace where completions are performed + is __main__ (technically, __main__.__dict__). Namespaces should be + given as dictionaries. + + An optional second namespace can be given. This allows the completer + to handle cases where both the local and global scopes need to be + distinguished. + """ + + # Don't bind to namespace quite yet, but flag whether the user wants a + # specific namespace or to use __main__.__dict__. This will allow us + # to bind to __main__.__dict__ at completion time, not now. + if namespace is None: + self.use_main_ns = True + else: + self.use_main_ns = False + self.namespace = namespace + + # The global namespace, if given, can be bound directly + if global_namespace is None: + self.global_namespace = {} + else: + self.global_namespace = global_namespace + + self.custom_matchers = [] + + super(Completer, self).__init__(**kwargs) + + def complete(self, text, state): + """Return the next possible completion for 'text'. + + This is called successively with state == 0, 1, 2, ... until it + returns None. The completion should begin with 'text'. + + """ + if self.use_main_ns: + self.namespace = __main__.__dict__ + + if state == 0: + if "." in text: + self.matches = self.attr_matches(text) + else: + self.matches = self.global_matches(text) + try: + return self.matches[state] + except IndexError: + return None + + def global_matches(self, text): + """Compute matches when text is a simple name. + + Return a list of all keywords, built-in functions and names currently + defined in self.namespace or self.global_namespace that match. + + """ + matches = [] + match_append = matches.append + n = len(text) + for lst in [ + keyword.kwlist, + builtin_mod.__dict__.keys(), + list(self.namespace.keys()), + list(self.global_namespace.keys()), + ]: + for word in lst: + if word[:n] == text and word != "__builtins__": + match_append(word) + + snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z") + for lst in [list(self.namespace.keys()), list(self.global_namespace.keys())]: + shortened = { + "_".join([sub[0] for sub in word.split("_")]): word + for word in lst + if snake_case_re.match(word) + } + for word in shortened.keys(): + if word[:n] == text and word != "__builtins__": + match_append(shortened[word]) + return matches + + def attr_matches(self, text): + """Compute matches when text contains a dot. + + Assuming the text is of the form NAME.NAME....[NAME], and is + evaluatable in self.namespace or self.global_namespace, it will be + evaluated and its attributes (as revealed by dir()) are used as + possible completions. (For class instances, class members are + also considered.) + + WARNING: this can still invoke arbitrary C code, if an object + with a __getattr__ hook is evaluated. + + """ + return self._attr_matches(text)[0] + + def _attr_matches(self, text, include_prefix=True) -> Tuple[Sequence[str], str]: + m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) + if not m2: + return [], "" + expr, attr = m2.group(1, 2) + + obj = self._evaluate_expr(expr) + + if obj is not_found: + return [], "" + + if self.limit_to__all__ and hasattr(obj, '__all__'): + words = get__all__entries(obj) + else: + words = dir2(obj) + + try: + words = generics.complete_object(obj, words) + except TryNext: + pass + except AssertionError: + raise + except Exception: + # Silence errors from completion function + pass + # Build match list to return + n = len(attr) + + # Note: ideally we would just return words here and the prefix + # reconciliator would know that we intend to append to rather than + # replace the input text; this requires refactoring to return range + # which ought to be replaced (as does jedi). + if include_prefix: + tokens = _parse_tokens(expr) + rev_tokens = reversed(tokens) + skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE} + name_turn = True + + parts = [] + for token in rev_tokens: + if token.type in skip_over: + continue + if token.type == tokenize.NAME and name_turn: + parts.append(token.string) + name_turn = False + elif ( + token.type == tokenize.OP and token.string == "." and not name_turn + ): + parts.append(token.string) + name_turn = True + else: + # short-circuit if not empty nor name token + break + + prefix_after_space = "".join(reversed(parts)) + else: + prefix_after_space = "" + + return ( + ["%s.%s" % (prefix_after_space, w) for w in words if w[:n] == attr], + "." + attr, + ) + + def _evaluate_expr(self, expr): + obj = not_found + done = False + while not done and expr: + try: + obj = guarded_eval( + expr, + EvaluationContext( + globals=self.global_namespace, + locals=self.namespace, + evaluation=self.evaluation, + ), + ) + done = True + except Exception as e: + if self.debug: + print("Evaluation exception", e) + # trim the expression to remove any invalid prefix + # e.g. user starts `(d[`, so we get `expr = '(d'`, + # where parenthesis is not closed. + # TODO: make this faster by reusing parts of the computation? + expr = expr[1:] + return obj + +def get__all__entries(obj): + """returns the strings in the __all__ attribute""" + try: + words = getattr(obj, '__all__') + except: + return [] + + return [w for w in words if isinstance(w, str)] + + +class _DictKeyState(enum.Flag): + """Represent state of the key match in context of other possible matches. + + - given `d1 = {'a': 1}` completion on `d1['` will yield `{'a': END_OF_ITEM}` as there is no tuple. + - given `d2 = {('a', 'b'): 1}`: `d2['a', '` will yield `{'b': END_OF_TUPLE}` as there is no tuple members to add beyond `'b'`. + - given `d3 = {('a', 'b'): 1}`: `d3['` will yield `{'a': IN_TUPLE}` as `'a'` can be added. + - given `d4 = {'a': 1, ('a', 'b'): 2}`: `d4['` will yield `{'a': END_OF_ITEM & END_OF_TUPLE}` + """ + + BASELINE = 0 + END_OF_ITEM = enum.auto() + END_OF_TUPLE = enum.auto() + IN_TUPLE = enum.auto() + + +def _parse_tokens(c): + """Parse tokens even if there is an error.""" + tokens = [] + token_generator = tokenize.generate_tokens(iter(c.splitlines()).__next__) + while True: + try: + tokens.append(next(token_generator)) + except tokenize.TokenError: + return tokens + except StopIteration: + return tokens + + +def _match_number_in_dict_key_prefix(prefix: str) -> Union[str, None]: + """Match any valid Python numeric literal in a prefix of dictionary keys. + + References: + - https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals + - https://docs.python.org/3/library/tokenize.html + """ + if prefix[-1].isspace(): + # if user typed a space we do not have anything to complete + # even if there was a valid number token before + return None + tokens = _parse_tokens(prefix) + rev_tokens = reversed(tokens) + skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE} + number = None + for token in rev_tokens: + if token.type in skip_over: + continue + if number is None: + if token.type == tokenize.NUMBER: + number = token.string + continue + else: + # we did not match a number + return None + if token.type == tokenize.OP: + if token.string == ",": + break + if token.string in {"+", "-"}: + number = token.string + number + else: + return None + return number + + +_INT_FORMATS = { + "0b": bin, + "0o": oct, + "0x": hex, +} + + +def match_dict_keys( + keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]], + prefix: str, + delims: str, + extra_prefix: Optional[Tuple[Union[str, bytes], ...]] = None, +) -> Tuple[str, int, Dict[str, _DictKeyState]]: + """Used by dict_key_matches, matching the prefix to a list of keys + + Parameters + ---------- + keys + list of keys in dictionary currently being completed. + prefix + Part of the text already typed by the user. E.g. `mydict[b'fo` + delims + String of delimiters to consider when finding the current key. + extra_prefix : optional + Part of the text already typed in multi-key index cases. E.g. for + `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`. + + Returns + ------- + A tuple of three elements: ``quote``, ``token_start``, ``matched``, with + ``quote`` being the quote that need to be used to close current string. + ``token_start`` the position where the replacement should start occurring, + ``matches`` a dictionary of replacement/completion keys on keys and values + indicating whether the state. + """ + prefix_tuple = extra_prefix if extra_prefix else () + + prefix_tuple_size = sum( + [ + # for pandas, do not count slices as taking space + not isinstance(k, slice) + for k in prefix_tuple + ] + ) + text_serializable_types = (str, bytes, int, float, slice) + + def filter_prefix_tuple(key): + # Reject too short keys + if len(key) <= prefix_tuple_size: + return False + # Reject keys which cannot be serialised to text + for k in key: + if not isinstance(k, text_serializable_types): + return False + # Reject keys that do not match the prefix + for k, pt in zip(key, prefix_tuple): + if k != pt and not isinstance(pt, slice): + return False + # All checks passed! + return True + + filtered_key_is_final: Dict[Union[str, bytes, int, float], _DictKeyState] = ( + defaultdict(lambda: _DictKeyState.BASELINE) + ) + + for k in keys: + # If at least one of the matches is not final, mark as undetermined. + # This can happen with `d = {111: 'b', (111, 222): 'a'}` where + # `111` appears final on first match but is not final on the second. + + if isinstance(k, tuple): + if filter_prefix_tuple(k): + key_fragment = k[prefix_tuple_size] + filtered_key_is_final[key_fragment] |= ( + _DictKeyState.END_OF_TUPLE + if len(k) == prefix_tuple_size + 1 + else _DictKeyState.IN_TUPLE + ) + elif prefix_tuple_size > 0: + # we are completing a tuple but this key is not a tuple, + # so we should ignore it + pass + else: + if isinstance(k, text_serializable_types): + filtered_key_is_final[k] |= _DictKeyState.END_OF_ITEM + + filtered_keys = filtered_key_is_final.keys() + + if not prefix: + return "", 0, {repr(k): v for k, v in filtered_key_is_final.items()} + + quote_match = re.search("(?:\"|')", prefix) + is_user_prefix_numeric = False + + if quote_match: + quote = quote_match.group() + valid_prefix = prefix + quote + try: + prefix_str = literal_eval(valid_prefix) + except Exception: + return "", 0, {} + else: + # If it does not look like a string, let's assume + # we are dealing with a number or variable. + number_match = _match_number_in_dict_key_prefix(prefix) + + # We do not want the key matcher to suggest variable names so we yield: + if number_match is None: + # The alternative would be to assume that user forgort the quote + # and if the substring matches, suggest adding it at the start. + return "", 0, {} + + prefix_str = number_match + is_user_prefix_numeric = True + quote = "" + + pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$' + token_match = re.search(pattern, prefix, re.UNICODE) + assert token_match is not None # silence mypy + token_start = token_match.start() + token_prefix = token_match.group() + + matched: Dict[str, _DictKeyState] = {} + + str_key: Union[str, bytes] + + for key in filtered_keys: + if isinstance(key, (int, float)): + # User typed a number but this key is not a number. + if not is_user_prefix_numeric: + continue + str_key = str(key) + if isinstance(key, int): + int_base = prefix_str[:2].lower() + # if user typed integer using binary/oct/hex notation: + if int_base in _INT_FORMATS: + int_format = _INT_FORMATS[int_base] + str_key = int_format(key) + else: + # User typed a string but this key is a number. + if is_user_prefix_numeric: + continue + str_key = key + try: + if not str_key.startswith(prefix_str): + continue + except (AttributeError, TypeError, UnicodeError) as e: + # Python 3+ TypeError on b'a'.startswith('a') or vice-versa + continue + + # reformat remainder of key to begin with prefix + rem = str_key[len(prefix_str) :] + # force repr wrapped in ' + rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"') + rem_repr = rem_repr[1 + rem_repr.index("'"):-2] + if quote == '"': + # The entered prefix is quoted with ", + # but the match is quoted with '. + # A contained " hence needs escaping for comparison: + rem_repr = rem_repr.replace('"', '\\"') + + # then reinsert prefix from start of token + match = "%s%s" % (token_prefix, rem_repr) + + matched[match] = filtered_key_is_final[key] + return quote, token_start, matched + + +def cursor_to_position(text:str, line:int, column:int)->int: + """ + Convert the (line,column) position of the cursor in text to an offset in a + string. + + Parameters + ---------- + text : str + The text in which to calculate the cursor offset + line : int + Line of the cursor; 0-indexed + column : int + Column of the cursor 0-indexed + + Returns + ------- + Position of the cursor in ``text``, 0-indexed. + + See Also + -------- + position_to_cursor : reciprocal of this function + + """ + lines = text.split('\n') + assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines))) + + return sum(len(l) + 1 for l in lines[:line]) + column + +def position_to_cursor(text:str, offset:int)->Tuple[int, int]: + """ + Convert the position of the cursor in text (0 indexed) to a line + number(0-indexed) and a column number (0-indexed) pair + + Position should be a valid position in ``text``. + + Parameters + ---------- + text : str + The text in which to calculate the cursor offset + offset : int + Position of the cursor in ``text``, 0-indexed. + + Returns + ------- + (line, column) : (int, int) + Line of the cursor; 0-indexed, column of the cursor 0-indexed + + See Also + -------- + cursor_to_position : reciprocal of this function + + """ + + assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text)) + + before = text[:offset] + blines = before.split('\n') # ! splitnes trim trailing \n + line = before.count('\n') + col = len(blines[-1]) + return line, col + + +def _safe_isinstance(obj, module, class_name, *attrs): + """Checks if obj is an instance of module.class_name if loaded + """ + if module in sys.modules: + m = sys.modules[module] + for attr in [class_name, *attrs]: + m = getattr(m, attr) + return isinstance(obj, m) + + +@context_matcher() +def back_unicode_name_matcher(context: CompletionContext): + """Match Unicode characters back to Unicode name + + Same as :any:`back_unicode_name_matches`, but adopted to new Matcher API. + """ + fragment, matches = back_unicode_name_matches(context.text_until_cursor) + return _convert_matcher_v1_result_to_v2( + matches, type="unicode", fragment=fragment, suppress_if_matches=True + ) + + +def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]: + """Match Unicode characters back to Unicode name + + This does ``☃`` -> ``\\snowman`` + + Note that snowman is not a valid python3 combining character but will be expanded. + Though it will not recombine back to the snowman character by the completion machinery. + + This will not either back-complete standard sequences like \\n, \\b ... + + .. deprecated:: 8.6 + You can use :meth:`back_unicode_name_matcher` instead. + + Returns + ======= + + Return a tuple with two elements: + + - The Unicode character that was matched (preceded with a backslash), or + empty string, + - a sequence (of 1), name for the match Unicode character, preceded by + backslash, or empty if no match. + """ + if len(text)<2: + return '', () + maybe_slash = text[-2] + if maybe_slash != '\\': + return '', () + + char = text[-1] + # no expand on quote for completion in strings. + # nor backcomplete standard ascii keys + if char in string.ascii_letters or char in ('"',"'"): + return '', () + try : + unic = unicodedata.name(char) + return '\\'+char,('\\'+unic,) + except KeyError: + pass + return '', () + + +@context_matcher() +def back_latex_name_matcher(context: CompletionContext): + """Match latex characters back to unicode name + + Same as :any:`back_latex_name_matches`, but adopted to new Matcher API. + """ + fragment, matches = back_latex_name_matches(context.text_until_cursor) + return _convert_matcher_v1_result_to_v2( + matches, type="latex", fragment=fragment, suppress_if_matches=True + ) + + +def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]: + """Match latex characters back to unicode name + + This does ``\\ℵ`` -> ``\\aleph`` + + .. deprecated:: 8.6 + You can use :meth:`back_latex_name_matcher` instead. + """ + if len(text)<2: + return '', () + maybe_slash = text[-2] + if maybe_slash != '\\': + return '', () + + + char = text[-1] + # no expand on quote for completion in strings. + # nor backcomplete standard ascii keys + if char in string.ascii_letters or char in ('"',"'"): + return '', () + try : + latex = reverse_latex_symbol[char] + # '\\' replace the \ as well + return '\\'+char,[latex] + except KeyError: + pass + return '', () + + +def _formatparamchildren(parameter) -> str: + """ + Get parameter name and value from Jedi Private API + + Jedi does not expose a simple way to get `param=value` from its API. + + Parameters + ---------- + parameter + Jedi's function `Param` + + Returns + ------- + A string like 'a', 'b=1', '*args', '**kwargs' + + """ + description = parameter.description + if not description.startswith('param '): + raise ValueError('Jedi function parameter description have change format.' + 'Expected "param ...", found %r".' % description) + return description[6:] + +def _make_signature(completion)-> str: + """ + Make the signature from a jedi completion + + Parameters + ---------- + completion : jedi.Completion + object does not complete a function type + + Returns + ------- + a string consisting of the function signature, with the parenthesis but + without the function name. example: + `(a, *args, b=1, **kwargs)` + + """ + + # it looks like this might work on jedi 0.17 + if hasattr(completion, 'get_signatures'): + signatures = completion.get_signatures() + if not signatures: + return '(?)' + + c0 = completion.get_signatures()[0] + return '('+c0.to_string().split('(', maxsplit=1)[1] + + return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures() + for p in signature.defined_names()) if f]) + + +_CompleteResult = Dict[str, MatcherResult] + + +DICT_MATCHER_REGEX = re.compile( + r"""(?x) +( # match dict-referring - or any get item object - expression + .+ +) +\[ # open bracket +\s* # and optional whitespace +# Capture any number of serializable objects (e.g. "a", "b", 'c') +# and slices +((?:(?: + (?: # closed string + [uUbB]? # string prefix (r not handled) + (?: + '(?:[^']|(? SimpleMatcherResult: + """Utility to help with transition""" + result = { + "completions": [SimpleCompletion(text=match, type=type) for match in matches], + "suppress": (True if matches else False) if suppress_if_matches else False, + } + if fragment is not None: + result["matched_fragment"] = fragment + return cast(SimpleMatcherResult, result) + + +class IPCompleter(Completer): + """Extension of the completer class with IPython-specific features""" + + @observe('greedy') + def _greedy_changed(self, change): + """update the splitter and readline delims when greedy is changed""" + if change["new"]: + self.evaluation = "unsafe" + self.auto_close_dict_keys = True + self.splitter.delims = GREEDY_DELIMS + else: + self.evaluation = "limited" + self.auto_close_dict_keys = False + self.splitter.delims = DELIMS + + dict_keys_only = Bool( + False, + help=""" + Whether to show dict key matches only. + + (disables all matchers except for `IPCompleter.dict_key_matcher`). + """, + ) + + suppress_competing_matchers = UnionTrait( + [Bool(allow_none=True), DictTrait(Bool(None, allow_none=True))], + default_value=None, + help=""" + Whether to suppress completions from other *Matchers*. + + When set to ``None`` (default) the matchers will attempt to auto-detect + whether suppression of other matchers is desirable. For example, at + the beginning of a line followed by `%` we expect a magic completion + to be the only applicable option, and after ``my_dict['`` we usually + expect a completion with an existing dictionary key. + + If you want to disable this heuristic and see completions from all matchers, + set ``IPCompleter.suppress_competing_matchers = False``. + To disable the heuristic for specific matchers provide a dictionary mapping: + ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``. + + Set ``IPCompleter.suppress_competing_matchers = True`` to limit + completions to the set of matchers with the highest priority; + this is equivalent to ``IPCompleter.merge_completions`` and + can be beneficial for performance, but will sometimes omit relevant + candidates from matchers further down the priority list. + """, + ).tag(config=True) + + merge_completions = Bool( + True, + help="""Whether to merge completion results into a single list + + If False, only the completion results from the first non-empty + completer will be returned. + + As of version 8.6.0, setting the value to ``False`` is an alias for: + ``IPCompleter.suppress_competing_matchers = True.``. + """, + ).tag(config=True) + + disable_matchers = ListTrait( + Unicode(), + help="""List of matchers to disable. + + The list should contain matcher identifiers (see :any:`completion_matcher`). + """, + ).tag(config=True) + + omit__names = Enum( + (0, 1, 2), + default_value=2, + help="""Instruct the completer to omit private method names + + Specifically, when completing on ``object.``. + + When 2 [default]: all names that start with '_' will be excluded. + + When 1: all 'magic' names (``__foo__``) will be excluded. + + When 0: nothing will be excluded. + """ + ).tag(config=True) + limit_to__all__ = Bool(False, + help=""" + DEPRECATED as of version 5.0. + + Instruct the completer to use __all__ for the completion + + Specifically, when completing on ``object.``. + + When True: only those names in obj.__all__ will be included. + + When False [default]: the __all__ attribute is ignored + """, + ).tag(config=True) + + profile_completions = Bool( + default_value=False, + help="If True, emit profiling data for completion subsystem using cProfile." + ).tag(config=True) + + profiler_output_dir = Unicode( + default_value=".completion_profiles", + help="Template for path at which to output profile data for completions." + ).tag(config=True) + + @observe('limit_to__all__') + def _limit_to_all_changed(self, change): + warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration ' + 'value has been deprecated since IPython 5.0, will be made to have ' + 'no effects and then removed in future version of IPython.', + UserWarning) + + def __init__( + self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs + ): + """IPCompleter() -> completer + + Return a completer object. + + Parameters + ---------- + shell + a pointer to the ipython shell itself. This is needed + because this completer knows about magic functions, and those can + only be accessed via the ipython instance. + namespace : dict, optional + an optional dict where completions are performed. + global_namespace : dict, optional + secondary optional dict for completions, to + handle cases (such as IPython embedded inside functions) where + both Python scopes are visible. + config : Config + traitlet's config object + **kwargs + passed to super class unmodified. + """ + + self.magic_escape = ESC_MAGIC + self.splitter = CompletionSplitter() + + # _greedy_changed() depends on splitter and readline being defined: + super().__init__( + namespace=namespace, + global_namespace=global_namespace, + config=config, + **kwargs, + ) + + # List where completion matches will be stored + self.matches = [] + self.shell = shell + # Regexp to split filenames with spaces in them + self.space_name_re = re.compile(r'([^\\] )') + # Hold a local ref. to glob.glob for speed + self.glob = glob.glob + + # Determine if we are running on 'dumb' terminals, like (X)Emacs + # buffers, to avoid completion problems. + term = os.environ.get('TERM','xterm') + self.dumb_terminal = term in ['dumb','emacs'] + + # Special handling of backslashes needed in win32 platforms + if sys.platform == "win32": + self.clean_glob = self._clean_glob_win32 + else: + self.clean_glob = self._clean_glob + + #regexp to parse docstring for function signature + self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') + self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') + #use this if positional argument name is also needed + #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)') + + self.magic_arg_matchers = [ + self.magic_config_matcher, + self.magic_color_matcher, + ] + + # This is set externally by InteractiveShell + self.custom_completers = None + + # This is a list of names of unicode characters that can be completed + # into their corresponding unicode value. The list is large, so we + # lazily initialize it on first use. Consuming code should access this + # attribute through the `@unicode_names` property. + self._unicode_names = None + + self._backslash_combining_matchers = [ + self.latex_name_matcher, + self.unicode_name_matcher, + back_latex_name_matcher, + back_unicode_name_matcher, + self.fwd_unicode_matcher, + ] + + if not self.backslash_combining_completions: + for matcher in self._backslash_combining_matchers: + self.disable_matchers.append(_get_matcher_id(matcher)) + + if not self.merge_completions: + self.suppress_competing_matchers = True + + @property + def matchers(self) -> List[Matcher]: + """All active matcher routines for completion""" + if self.dict_keys_only: + return [self.dict_key_matcher] + + if self.use_jedi: + return [ + *self.custom_matchers, + *self._backslash_combining_matchers, + *self.magic_arg_matchers, + self.custom_completer_matcher, + self.magic_matcher, + self._jedi_matcher, + self.dict_key_matcher, + self.file_matcher, + ] + else: + return [ + *self.custom_matchers, + *self._backslash_combining_matchers, + *self.magic_arg_matchers, + self.custom_completer_matcher, + self.dict_key_matcher, + self.magic_matcher, + self.python_matcher, + self.file_matcher, + self.python_func_kw_matcher, + ] + + def all_completions(self, text:str) -> List[str]: + """ + Wrapper around the completion methods for the benefit of emacs. + """ + prefix = text.rpartition('.')[0] + with provisionalcompleter(): + return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text + for c in self.completions(text, len(text))] + + return self.complete(text)[1] + + def _clean_glob(self, text:str): + return self.glob("%s*" % text) + + def _clean_glob_win32(self, text:str): + return [f.replace("\\","/") + for f in self.glob("%s*" % text)] + + @context_matcher() + def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Same as :any:`file_matches`, but adopted to new Matcher API.""" + matches = self.file_matches(context.token) + # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter, + # starts with `/home/`, `C:\`, etc) + return _convert_matcher_v1_result_to_v2(matches, type="path") + + def file_matches(self, text: str) -> List[str]: + """Match filenames, expanding ~USER type strings. + + Most of the seemingly convoluted logic in this completer is an + attempt to handle filenames with spaces in them. And yet it's not + quite perfect, because Python's readline doesn't expose all of the + GNU readline details needed for this to be done correctly. + + For a filename with a space in it, the printed completions will be + only the parts after what's already been typed (instead of the + full completions, as is normally done). I don't think with the + current (as of Python 2.3) Python readline it's possible to do + better. + + .. deprecated:: 8.6 + You can use :meth:`file_matcher` instead. + """ + + # chars that require escaping with backslash - i.e. chars + # that readline treats incorrectly as delimiters, but we + # don't want to treat as delimiters in filename matching + # when escaped with backslash + if text.startswith('!'): + text = text[1:] + text_prefix = u'!' + else: + text_prefix = u'' + + text_until_cursor = self.text_until_cursor + # track strings with open quotes + open_quotes = has_open_quotes(text_until_cursor) + + if '(' in text_until_cursor or '[' in text_until_cursor: + lsplit = text + else: + try: + # arg_split ~ shlex.split, but with unicode bugs fixed by us + lsplit = arg_split(text_until_cursor)[-1] + except ValueError: + # typically an unmatched ", or backslash without escaped char. + if open_quotes: + lsplit = text_until_cursor.split(open_quotes)[-1] + else: + return [] + except IndexError: + # tab pressed on empty line + lsplit = "" + + if not open_quotes and lsplit != protect_filename(lsplit): + # if protectables are found, do matching on the whole escaped name + has_protectables = True + text0,text = text,lsplit + else: + has_protectables = False + text = os.path.expanduser(text) + + if text == "": + return [text_prefix + protect_filename(f) for f in self.glob("*")] + + # Compute the matches from the filesystem + if sys.platform == 'win32': + m0 = self.clean_glob(text) + else: + m0 = self.clean_glob(text.replace('\\', '')) + + if has_protectables: + # If we had protectables, we need to revert our changes to the + # beginning of filename so that we don't double-write the part + # of the filename we have so far + len_lsplit = len(lsplit) + matches = [text_prefix + text0 + + protect_filename(f[len_lsplit:]) for f in m0] + else: + if open_quotes: + # if we have a string with an open quote, we don't need to + # protect the names beyond the quote (and we _shouldn't_, as + # it would cause bugs when the filesystem call is made). + matches = m0 if sys.platform == "win32" else\ + [protect_filename(f, open_quotes) for f in m0] + else: + matches = [text_prefix + + protect_filename(f) for f in m0] + + # Mark directories in input list by appending '/' to their names. + return [x+'/' if os.path.isdir(x) else x for x in matches] + + @context_matcher() + def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match magics.""" + text = context.token + matches = self.magic_matches(text) + result = _convert_matcher_v1_result_to_v2(matches, type="magic") + is_magic_prefix = len(text) > 0 and text[0] == "%" + result["suppress"] = is_magic_prefix and bool(result["completions"]) + return result + + def magic_matches(self, text: str): + """Match magics. + + .. deprecated:: 8.6 + You can use :meth:`magic_matcher` instead. + """ + # Get all shell magics now rather than statically, so magics loaded at + # runtime show up too. + lsm = self.shell.magics_manager.lsmagic() + line_magics = lsm['line'] + cell_magics = lsm['cell'] + pre = self.magic_escape + pre2 = pre+pre + + explicit_magic = text.startswith(pre) + + # Completion logic: + # - user gives %%: only do cell magics + # - user gives %: do both line and cell magics + # - no prefix: do both + # In other words, line magics are skipped if the user gives %% explicitly + # + # We also exclude magics that match any currently visible names: + # https://github.com/ipython/ipython/issues/4877, unless the user has + # typed a %: + # https://github.com/ipython/ipython/issues/10754 + bare_text = text.lstrip(pre) + global_matches = self.global_matches(bare_text) + if not explicit_magic: + def matches(magic): + """ + Filter magics, in particular remove magics that match + a name present in global namespace. + """ + return ( magic.startswith(bare_text) and + magic not in global_matches ) + else: + def matches(magic): + return magic.startswith(bare_text) + + comp = [ pre2+m for m in cell_magics if matches(m)] + if not text.startswith(pre2): + comp += [ pre+m for m in line_magics if matches(m)] + + return comp + + @context_matcher() + def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match class names and attributes for %config magic.""" + # NOTE: uses `line_buffer` equivalent for compatibility + matches = self.magic_config_matches(context.line_with_cursor) + return _convert_matcher_v1_result_to_v2(matches, type="param") + + def magic_config_matches(self, text: str) -> List[str]: + """Match class names and attributes for %config magic. + + .. deprecated:: 8.6 + You can use :meth:`magic_config_matcher` instead. + """ + texts = text.strip().split() + + if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): + # get all configuration classes + classes = sorted(set([ c for c in self.shell.configurables + if c.__class__.class_traits(config=True) + ]), key=lambda x: x.__class__.__name__) + classnames = [ c.__class__.__name__ for c in classes ] + + # return all classnames if config or %config is given + if len(texts) == 1: + return classnames + + # match classname + classname_texts = texts[1].split('.') + classname = classname_texts[0] + classname_matches = [ c for c in classnames + if c.startswith(classname) ] + + # return matched classes or the matched class with attributes + if texts[1].find('.') < 0: + return classname_matches + elif len(classname_matches) == 1 and \ + classname_matches[0] == classname: + cls = classes[classnames.index(classname)].__class__ + help = cls.class_get_help() + # strip leading '--' from cl-args: + help = re.sub(re.compile(r'^--', re.MULTILINE), '', help) + return [ attr.split('=')[0] + for attr in help.strip().splitlines() + if attr.startswith(texts[1]) ] + return [] + + @context_matcher() + def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match color schemes for %colors magic.""" + # NOTE: uses `line_buffer` equivalent for compatibility + matches = self.magic_color_matches(context.line_with_cursor) + return _convert_matcher_v1_result_to_v2(matches, type="param") + + def magic_color_matches(self, text: str) -> List[str]: + """Match color schemes for %colors magic. + + .. deprecated:: 8.6 + You can use :meth:`magic_color_matcher` instead. + """ + texts = text.split() + if text.endswith(' '): + # .split() strips off the trailing whitespace. Add '' back + # so that: '%colors ' -> ['%colors', ''] + texts.append('') + + if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'): + prefix = texts[1] + return [ color for color in InspectColors.keys() + if color.startswith(prefix) ] + return [] + + @context_matcher(identifier="IPCompleter.jedi_matcher") + def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult: + matches = self._jedi_matches( + cursor_column=context.cursor_position, + cursor_line=context.cursor_line, + text=context.full_text, + ) + return { + "completions": matches, + # static analysis should not suppress other matchers + "suppress": False, + } + + def _jedi_matches( + self, cursor_column: int, cursor_line: int, text: str + ) -> Iterator[_JediCompletionLike]: + """ + Return a list of :any:`jedi.api.Completion`\\s object from a ``text`` and + cursor position. + + Parameters + ---------- + cursor_column : int + column position of the cursor in ``text``, 0-indexed. + cursor_line : int + line position of the cursor in ``text``, 0-indexed + text : str + text to complete + + Notes + ----- + If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` + object containing a string with the Jedi debug information attached. + + .. deprecated:: 8.6 + You can use :meth:`_jedi_matcher` instead. + """ + namespaces = [self.namespace] + if self.global_namespace is not None: + namespaces.append(self.global_namespace) + + completion_filter = lambda x:x + offset = cursor_to_position(text, cursor_line, cursor_column) + # filter output if we are completing for object members + if offset: + pre = text[offset-1] + if pre == '.': + if self.omit__names == 2: + completion_filter = lambda c:not c.name.startswith('_') + elif self.omit__names == 1: + completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__')) + elif self.omit__names == 0: + completion_filter = lambda x:x + else: + raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names)) + + interpreter = jedi.Interpreter(text[:offset], namespaces) + try_jedi = True + + try: + # find the first token in the current tree -- if it is a ' or " then we are in a string + completing_string = False + try: + first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value')) + except StopIteration: + pass + else: + # note the value may be ', ", or it may also be ''' or """, or + # in some cases, """what/you/typed..., but all of these are + # strings. + completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'} + + # if we are in a string jedi is likely not the right candidate for + # now. Skip it. + try_jedi = not completing_string + except Exception as e: + # many of things can go wrong, we are using private API just don't crash. + if self.debug: + print("Error detecting if completing a non-finished string :", e, '|') + + if not try_jedi: + return iter([]) + try: + return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1)) + except Exception as e: + if self.debug: + return iter( + [ + _FakeJediCompletion( + 'Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' + % (e) + ) + ] + ) + else: + return iter([]) + + @context_matcher() + def python_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match attributes or global python names""" + text = context.line_with_cursor + if "." in text: + try: + matches, fragment = self._attr_matches(text, include_prefix=False) + if text.endswith(".") and self.omit__names: + if self.omit__names == 1: + # true if txt is _not_ a __ name, false otherwise: + no__name = lambda txt: re.match(r".*\.__.*?__", txt) is None + else: + # true if txt is _not_ a _ name, false otherwise: + no__name = ( + lambda txt: re.match(r"\._.*?", txt[txt.rindex(".") :]) + is None + ) + matches = filter(no__name, matches) + return _convert_matcher_v1_result_to_v2( + matches, type="attribute", fragment=fragment + ) + except NameError: + # catches . + matches = [] + return _convert_matcher_v1_result_to_v2(matches, type="attribute") + else: + matches = self.global_matches(context.token) + # TODO: maybe distinguish between functions, modules and just "variables" + return _convert_matcher_v1_result_to_v2(matches, type="variable") + + @completion_matcher(api_version=1) + def python_matches(self, text: str) -> Iterable[str]: + """Match attributes or global python names. + + .. deprecated:: 8.27 + You can use :meth:`python_matcher` instead.""" + if "." in text: + try: + matches = self.attr_matches(text) + if text.endswith('.') and self.omit__names: + if self.omit__names == 1: + # true if txt is _not_ a __ name, false otherwise: + no__name = (lambda txt: + re.match(r'.*\.__.*?__',txt) is None) + else: + # true if txt is _not_ a _ name, false otherwise: + no__name = (lambda txt: + re.match(r'\._.*?',txt[txt.rindex('.'):]) is None) + matches = filter(no__name, matches) + except NameError: + # catches . + matches = [] + else: + matches = self.global_matches(text) + return matches + + def _default_arguments_from_docstring(self, doc): + """Parse the first line of docstring for call signature. + + Docstring should be of the form 'min(iterable[, key=func])\n'. + It can also parse cython docstring of the form + 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'. + """ + if doc is None: + return [] + + #care only the firstline + line = doc.lstrip().splitlines()[0] + + #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') + #'min(iterable[, key=func])\n' -> 'iterable[, key=func]' + sig = self.docstring_sig_re.search(line) + if sig is None: + return [] + # iterable[, key=func]' -> ['iterable[' ,' key=func]'] + sig = sig.groups()[0].split(',') + ret = [] + for s in sig: + #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') + ret += self.docstring_kwd_re.findall(s) + return ret + + def _default_arguments(self, obj): + """Return the list of default arguments of obj if it is callable, + or empty list otherwise.""" + call_obj = obj + ret = [] + if inspect.isbuiltin(obj): + pass + elif not (inspect.isfunction(obj) or inspect.ismethod(obj)): + if inspect.isclass(obj): + #for cython embedsignature=True the constructor docstring + #belongs to the object itself not __init__ + ret += self._default_arguments_from_docstring( + getattr(obj, '__doc__', '')) + # for classes, check for __init__,__new__ + call_obj = (getattr(obj, '__init__', None) or + getattr(obj, '__new__', None)) + # for all others, check if they are __call__able + elif hasattr(obj, '__call__'): + call_obj = obj.__call__ + ret += self._default_arguments_from_docstring( + getattr(call_obj, '__doc__', '')) + + _keeps = (inspect.Parameter.KEYWORD_ONLY, + inspect.Parameter.POSITIONAL_OR_KEYWORD) + + try: + sig = inspect.signature(obj) + ret.extend(k for k, v in sig.parameters.items() if + v.kind in _keeps) + except ValueError: + pass + + return list(set(ret)) + + @context_matcher() + def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match named parameters (kwargs) of the last open function.""" + matches = self.python_func_kw_matches(context.token) + return _convert_matcher_v1_result_to_v2(matches, type="param") + + def python_func_kw_matches(self, text): + """Match named parameters (kwargs) of the last open function. + + .. deprecated:: 8.6 + You can use :meth:`python_func_kw_matcher` instead. + """ + + if "." in text: # a parameter cannot be dotted + return [] + try: regexp = self.__funcParamsRegex + except AttributeError: + regexp = self.__funcParamsRegex = re.compile(r''' + '.*?(?,a=1)", the candidate is "foo" + tokens = regexp.findall(self.text_until_cursor) + iterTokens = reversed(tokens); openPar = 0 + + for token in iterTokens: + if token == ')': + openPar -= 1 + elif token == '(': + openPar += 1 + if openPar > 0: + # found the last unclosed parenthesis + break + else: + return [] + # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" ) + ids = [] + isId = re.compile(r'\w+$').match + + while True: + try: + ids.append(next(iterTokens)) + if not isId(ids[-1]): + ids.pop(); break + if not next(iterTokens) == '.': + break + except StopIteration: + break + + # Find all named arguments already assigned to, as to avoid suggesting + # them again + usedNamedArgs = set() + par_level = -1 + for token, next_token in zip(tokens, tokens[1:]): + if token == '(': + par_level += 1 + elif token == ')': + par_level -= 1 + + if par_level != 0: + continue + + if next_token != '=': + continue + + usedNamedArgs.add(token) + + argMatches = [] + try: + callableObj = '.'.join(ids[::-1]) + namedArgs = self._default_arguments(eval(callableObj, + self.namespace)) + + # Remove used named arguments from the list, no need to show twice + for namedArg in set(namedArgs) - usedNamedArgs: + if namedArg.startswith(text): + argMatches.append("%s=" %namedArg) + except: + pass + + return argMatches + + @staticmethod + def _get_keys(obj: Any) -> List[Any]: + # Objects can define their own completions by defining an + # _ipy_key_completions_() method. + method = get_real_method(obj, '_ipython_key_completions_') + if method is not None: + return method() + + # Special case some common in-memory dict-like types + if isinstance(obj, dict) or _safe_isinstance(obj, "pandas", "DataFrame"): + try: + return list(obj.keys()) + except Exception: + return [] + elif _safe_isinstance(obj, "pandas", "core", "indexing", "_LocIndexer"): + try: + return list(obj.obj.keys()) + except Exception: + return [] + elif _safe_isinstance(obj, 'numpy', 'ndarray') or\ + _safe_isinstance(obj, 'numpy', 'void'): + return obj.dtype.names or [] + return [] + + @context_matcher() + def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult: + """Match string keys in a dictionary, after e.g. ``foo[``.""" + matches = self.dict_key_matches(context.token) + return _convert_matcher_v1_result_to_v2( + matches, type="dict key", suppress_if_matches=True + ) + + def dict_key_matches(self, text: str) -> List[str]: + """Match string keys in a dictionary, after e.g. ``foo[``. + + .. deprecated:: 8.6 + You can use :meth:`dict_key_matcher` instead. + """ + + # Short-circuit on closed dictionary (regular expression would + # not match anyway, but would take quite a while). + if self.text_until_cursor.strip().endswith("]"): + return [] + + match = DICT_MATCHER_REGEX.search(self.text_until_cursor) + + if match is None: + return [] + + expr, prior_tuple_keys, key_prefix = match.groups() + + obj = self._evaluate_expr(expr) + + if obj is not_found: + return [] + + keys = self._get_keys(obj) + if not keys: + return keys + + tuple_prefix = guarded_eval( + prior_tuple_keys, + EvaluationContext( + globals=self.global_namespace, + locals=self.namespace, + evaluation=self.evaluation, # type: ignore + in_subscript=True, + ), + ) + + closing_quote, token_offset, matches = match_dict_keys( + keys, key_prefix, self.splitter.delims, extra_prefix=tuple_prefix + ) + if not matches: + return [] + + # get the cursor position of + # - the text being completed + # - the start of the key text + # - the start of the completion + text_start = len(self.text_until_cursor) - len(text) + if key_prefix: + key_start = match.start(3) + completion_start = key_start + token_offset + else: + key_start = completion_start = match.end() + + # grab the leading prefix, to make sure all completions start with `text` + if text_start > key_start: + leading = '' + else: + leading = text[text_start:completion_start] + + # append closing quote and bracket as appropriate + # this is *not* appropriate if the opening quote or bracket is outside + # the text given to this method, e.g. `d["""a\nt + can_close_quote = False + can_close_bracket = False + + continuation = self.line_buffer[len(self.text_until_cursor) :].strip() + + if continuation.startswith(closing_quote): + # do not close if already closed, e.g. `d['a'` + continuation = continuation[len(closing_quote) :] + else: + can_close_quote = True + + continuation = continuation.strip() + + # e.g. `pandas.DataFrame` has different tuple indexer behaviour, + # handling it is out of scope, so let's avoid appending suffixes. + has_known_tuple_handling = isinstance(obj, dict) + + can_close_bracket = ( + not continuation.startswith("]") and self.auto_close_dict_keys + ) + can_close_tuple_item = ( + not continuation.startswith(",") + and has_known_tuple_handling + and self.auto_close_dict_keys + ) + can_close_quote = can_close_quote and self.auto_close_dict_keys + + # fast path if closing quote should be appended but not suffix is allowed + if not can_close_quote and not can_close_bracket and closing_quote: + return [leading + k for k in matches] + + results = [] + + end_of_tuple_or_item = _DictKeyState.END_OF_TUPLE | _DictKeyState.END_OF_ITEM + + for k, state_flag in matches.items(): + result = leading + k + if can_close_quote and closing_quote: + result += closing_quote + + if state_flag == end_of_tuple_or_item: + # We do not know which suffix to add, + # e.g. both tuple item and string + # match this item. + pass + + if state_flag in end_of_tuple_or_item and can_close_bracket: + result += "]" + if state_flag == _DictKeyState.IN_TUPLE and can_close_tuple_item: + result += ", " + results.append(result) + return results + + @context_matcher() + def unicode_name_matcher(self, context: CompletionContext): + """Same as :any:`unicode_name_matches`, but adopted to new Matcher API.""" + fragment, matches = self.unicode_name_matches(context.text_until_cursor) + return _convert_matcher_v1_result_to_v2( + matches, type="unicode", fragment=fragment, suppress_if_matches=True + ) + + @staticmethod + def unicode_name_matches(text: str) -> Tuple[str, List[str]]: + """Match Latex-like syntax for unicode characters base + on the name of the character. + + This does ``\\GREEK SMALL LETTER ETA`` -> ``η`` + + Works only on valid python 3 identifier, or on combining characters that + will combine to form a valid identifier. + """ + slashpos = text.rfind('\\') + if slashpos > -1: + s = text[slashpos+1:] + try : + unic = unicodedata.lookup(s) + # allow combining chars + if ('a'+unic).isidentifier(): + return '\\'+s,[unic] + except KeyError: + pass + return '', [] + + @context_matcher() + def latex_name_matcher(self, context: CompletionContext): + """Match Latex syntax for unicode characters. + + This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` + """ + fragment, matches = self.latex_matches(context.text_until_cursor) + return _convert_matcher_v1_result_to_v2( + matches, type="latex", fragment=fragment, suppress_if_matches=True + ) + + def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]: + """Match Latex syntax for unicode characters. + + This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` + + .. deprecated:: 8.6 + You can use :meth:`latex_name_matcher` instead. + """ + slashpos = text.rfind('\\') + if slashpos > -1: + s = text[slashpos:] + if s in latex_symbols: + # Try to complete a full latex symbol to unicode + # \\alpha -> α + return s, [latex_symbols[s]] + else: + # If a user has partially typed a latex symbol, give them + # a full list of options \al -> [\aleph, \alpha] + matches = [k for k in latex_symbols if k.startswith(s)] + if matches: + return s, matches + return '', () + + @context_matcher() + def custom_completer_matcher(self, context): + """Dispatch custom completer. + + If a match is found, suppresses all other matchers except for Jedi. + """ + matches = self.dispatch_custom_completer(context.token) or [] + result = _convert_matcher_v1_result_to_v2( + matches, type=_UNKNOWN_TYPE, suppress_if_matches=True + ) + result["ordered"] = True + result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)} + return result + + def dispatch_custom_completer(self, text): + """ + .. deprecated:: 8.6 + You can use :meth:`custom_completer_matcher` instead. + """ + if not self.custom_completers: + return + + line = self.line_buffer + if not line.strip(): + return None + + # Create a little structure to pass all the relevant information about + # the current completion to any custom completer. + event = SimpleNamespace() + event.line = line + event.symbol = text + cmd = line.split(None,1)[0] + event.command = cmd + event.text_until_cursor = self.text_until_cursor + + # for foo etc, try also to find completer for %foo + if not cmd.startswith(self.magic_escape): + try_magic = self.custom_completers.s_matches( + self.magic_escape + cmd) + else: + try_magic = [] + + for c in itertools.chain(self.custom_completers.s_matches(cmd), + try_magic, + self.custom_completers.flat_matches(self.text_until_cursor)): + try: + res = c(event) + if res: + # first, try case sensitive match + withcase = [r for r in res if r.startswith(text)] + if withcase: + return withcase + # if none, then case insensitive ones are ok too + text_low = text.lower() + return [r for r in res if r.lower().startswith(text_low)] + except TryNext: + pass + except KeyboardInterrupt: + """ + If custom completer take too long, + let keyboard interrupt abort and return nothing. + """ + break + + return None + + def completions(self, text: str, offset: int)->Iterator[Completion]: + """ + Returns an iterator over the possible completions + + .. warning:: + + Unstable + + This function is unstable, API may change without warning. + It will also raise unless use in proper context manager. + + Parameters + ---------- + text : str + Full text of the current input, multi line string. + offset : int + Integer representing the position of the cursor in ``text``. Offset + is 0-based indexed. + + Yields + ------ + Completion + + Notes + ----- + The cursor on a text can either be seen as being "in between" + characters or "On" a character depending on the interface visible to + the user. For consistency the cursor being on "in between" characters X + and Y is equivalent to the cursor being "on" character Y, that is to say + the character the cursor is on is considered as being after the cursor. + + Combining characters may span more that one position in the + text. + + .. note:: + + If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--`` + fake Completion token to distinguish completion returned by Jedi + and usual IPython completion. + + .. note:: + + Completions are not completely deduplicated yet. If identical + completions are coming from different sources this function does not + ensure that each completion object will only be present once. + """ + warnings.warn("_complete is a provisional API (as of IPython 6.0). " + "It may change without warnings. " + "Use in corresponding context manager.", + category=ProvisionalCompleterWarning, stacklevel=2) + + seen = set() + profiler:Optional[cProfile.Profile] + try: + if self.profile_completions: + import cProfile + profiler = cProfile.Profile() + profiler.enable() + else: + profiler = None + + for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): + if c and (c in seen): + continue + yield c + seen.add(c) + except KeyboardInterrupt: + """if completions take too long and users send keyboard interrupt, + do not crash and return ASAP. """ + pass + finally: + if profiler is not None: + profiler.disable() + ensure_dir_exists(self.profiler_output_dir) + output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4())) + print("Writing profiler output to", output_path) + profiler.dump_stats(output_path) + + def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]: + """ + Core completion module.Same signature as :any:`completions`, with the + extra `timeout` parameter (in seconds). + + Computing jedi's completion ``.type`` can be quite expensive (it is a + lazy property) and can require some warm-up, more warm up than just + computing the ``name`` of a completion. The warm-up can be : + + - Long warm-up the first time a module is encountered after + install/update: actually build parse/inference tree. + + - first time the module is encountered in a session: load tree from + disk. + + We don't want to block completions for tens of seconds so we give the + completer a "budget" of ``_timeout`` seconds per invocation to compute + completions types, the completions that have not yet been computed will + be marked as "unknown" an will have a chance to be computed next round + are things get cached. + + Keep in mind that Jedi is not the only thing treating the completion so + keep the timeout short-ish as if we take more than 0.3 second we still + have lots of processing to do. + + """ + deadline = time.monotonic() + _timeout + + before = full_text[:offset] + cursor_line, cursor_column = position_to_cursor(full_text, offset) + + jedi_matcher_id = _get_matcher_id(self._jedi_matcher) + + def is_non_jedi_result( + result: MatcherResult, identifier: str + ) -> TypeGuard[SimpleMatcherResult]: + return identifier != jedi_matcher_id + + results = self._complete( + full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column + ) + + non_jedi_results: Dict[str, SimpleMatcherResult] = { + identifier: result + for identifier, result in results.items() + if is_non_jedi_result(result, identifier) + } + + jedi_matches = ( + cast(_JediMatcherResult, results[jedi_matcher_id])["completions"] + if jedi_matcher_id in results + else () + ) + + iter_jm = iter(jedi_matches) + if _timeout: + for jm in iter_jm: + try: + type_ = jm.type + except Exception: + if self.debug: + print("Error in Jedi getting type of ", jm) + type_ = None + delta = len(jm.name_with_symbols) - len(jm.complete) + if type_ == 'function': + signature = _make_signature(jm) + else: + signature = '' + yield Completion(start=offset - delta, + end=offset, + text=jm.name_with_symbols, + type=type_, + signature=signature, + _origin='jedi') + + if time.monotonic() > deadline: + break + + for jm in iter_jm: + delta = len(jm.name_with_symbols) - len(jm.complete) + yield Completion( + start=offset - delta, + end=offset, + text=jm.name_with_symbols, + type=_UNKNOWN_TYPE, # don't compute type for speed + _origin="jedi", + signature="", + ) + + # TODO: + # Suppress this, right now just for debug. + if jedi_matches and non_jedi_results and self.debug: + some_start_offset = before.rfind( + next(iter(non_jedi_results.values()))["matched_fragment"] + ) + yield Completion( + start=some_start_offset, + end=offset, + text="--jedi/ipython--", + _origin="debug", + type="none", + signature="", + ) + + ordered: List[Completion] = [] + sortable: List[Completion] = [] + + for origin, result in non_jedi_results.items(): + matched_text = result["matched_fragment"] + start_offset = before.rfind(matched_text) + is_ordered = result.get("ordered", False) + container = ordered if is_ordered else sortable + + # I'm unsure if this is always true, so let's assert and see if it + # crash + assert before.endswith(matched_text) + + for simple_completion in result["completions"]: + completion = Completion( + start=start_offset, + end=offset, + text=simple_completion.text, + _origin=origin, + signature="", + type=simple_completion.type or _UNKNOWN_TYPE, + ) + container.append(completion) + + yield from list(self._deduplicate(ordered + self._sort(sortable)))[ + :MATCHES_LIMIT + ] + + def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: + """Find completions for the given text and line context. + + Note that both the text and the line_buffer are optional, but at least + one of them must be given. + + Parameters + ---------- + text : string, optional + Text to perform the completion on. If not given, the line buffer + is split using the instance's CompletionSplitter object. + line_buffer : string, optional + If not given, the completer attempts to obtain the current line + buffer via readline. This keyword allows clients which are + requesting for text completions in non-readline contexts to inform + the completer of the entire text. + cursor_pos : int, optional + Index of the cursor in the full line buffer. Should be provided by + remote frontends where kernel has no access to frontend state. + + Returns + ------- + Tuple of two items: + text : str + Text that was actually used in the completion. + matches : list + A list of completion matches. + + Notes + ----- + This API is likely to be deprecated and replaced by + :any:`IPCompleter.completions` in the future. + + """ + warnings.warn('`Completer.complete` is pending deprecation since ' + 'IPython 6.0 and will be replaced by `Completer.completions`.', + PendingDeprecationWarning) + # potential todo, FOLD the 3rd throw away argument of _complete + # into the first 2 one. + # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?) + # TODO: should we deprecate now, or does it stay? + + results = self._complete( + line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0 + ) + + jedi_matcher_id = _get_matcher_id(self._jedi_matcher) + + return self._arrange_and_extract( + results, + # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version? + skip_matchers={jedi_matcher_id}, + # this API does not support different start/end positions (fragments of token). + abort_if_offset_changes=True, + ) + + def _arrange_and_extract( + self, + results: Dict[str, MatcherResult], + skip_matchers: Set[str], + abort_if_offset_changes: bool, + ): + sortable: List[AnyMatcherCompletion] = [] + ordered: List[AnyMatcherCompletion] = [] + most_recent_fragment = None + for identifier, result in results.items(): + if identifier in skip_matchers: + continue + if not result["completions"]: + continue + if not most_recent_fragment: + most_recent_fragment = result["matched_fragment"] + if ( + abort_if_offset_changes + and result["matched_fragment"] != most_recent_fragment + ): + break + if result.get("ordered", False): + ordered.extend(result["completions"]) + else: + sortable.extend(result["completions"]) + + if not most_recent_fragment: + most_recent_fragment = "" # to satisfy typechecker (and just in case) + + return most_recent_fragment, [ + m.text for m in self._deduplicate(ordered + self._sort(sortable)) + ] + + def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, + full_text=None) -> _CompleteResult: + """ + Like complete but can also returns raw jedi completions as well as the + origin of the completion text. This could (and should) be made much + cleaner but that will be simpler once we drop the old (and stateful) + :any:`complete` API. + + With current provisional API, cursor_pos act both (depending on the + caller) as the offset in the ``text`` or ``line_buffer``, or as the + ``column`` when passing multiline strings this could/should be renamed + but would add extra noise. + + Parameters + ---------- + cursor_line + Index of the line the cursor is on. 0 indexed. + cursor_pos + Position of the cursor in the current line/line_buffer/text. 0 + indexed. + line_buffer : optional, str + The current line the cursor is in, this is mostly due to legacy + reason that readline could only give a us the single current line. + Prefer `full_text`. + text : str + The current "token" the cursor is in, mostly also for historical + reasons. as the completer would trigger only after the current line + was parsed. + full_text : str + Full text of the current cell. + + Returns + ------- + An ordered dictionary where keys are identifiers of completion + matchers and values are ``MatcherResult``s. + """ + + # if the cursor position isn't given, the only sane assumption we can + # make is that it's at the end of the line (the common case) + if cursor_pos is None: + cursor_pos = len(line_buffer) if text is None else len(text) + + if self.use_main_ns: + self.namespace = __main__.__dict__ + + # if text is either None or an empty string, rely on the line buffer + if (not line_buffer) and full_text: + line_buffer = full_text.split('\n')[cursor_line] + if not text: # issue #11508: check line_buffer before calling split_line + text = ( + self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else "" + ) + + # If no line buffer is given, assume the input text is all there was + if line_buffer is None: + line_buffer = text + + # deprecated - do not use `line_buffer` in new code. + self.line_buffer = line_buffer + self.text_until_cursor = self.line_buffer[:cursor_pos] + + if not full_text: + full_text = line_buffer + + context = CompletionContext( + full_text=full_text, + cursor_position=cursor_pos, + cursor_line=cursor_line, + token=text, + limit=MATCHES_LIMIT, + ) + + # Start with a clean slate of completions + results: Dict[str, MatcherResult] = {} + + jedi_matcher_id = _get_matcher_id(self._jedi_matcher) + + suppressed_matchers: Set[str] = set() + + matchers = { + _get_matcher_id(matcher): matcher + for matcher in sorted( + self.matchers, key=_get_matcher_priority, reverse=True + ) + } + + for matcher_id, matcher in matchers.items(): + matcher_id = _get_matcher_id(matcher) + + if matcher_id in self.disable_matchers: + continue + + if matcher_id in results: + warnings.warn(f"Duplicate matcher ID: {matcher_id}.") + + if matcher_id in suppressed_matchers: + continue + + result: MatcherResult + try: + if _is_matcher_v1(matcher): + result = _convert_matcher_v1_result_to_v2( + matcher(text), type=_UNKNOWN_TYPE + ) + elif _is_matcher_v2(matcher): + result = matcher(context) + else: + api_version = _get_matcher_api_version(matcher) + raise ValueError(f"Unsupported API version {api_version}") + except: + # Show the ugly traceback if the matcher causes an + # exception, but do NOT crash the kernel! + sys.excepthook(*sys.exc_info()) + continue + + # set default value for matched fragment if suffix was not selected. + result["matched_fragment"] = result.get("matched_fragment", context.token) + + if not suppressed_matchers: + suppression_recommended: Union[bool, Set[str]] = result.get( + "suppress", False + ) + + suppression_config = ( + self.suppress_competing_matchers.get(matcher_id, None) + if isinstance(self.suppress_competing_matchers, dict) + else self.suppress_competing_matchers + ) + should_suppress = ( + (suppression_config is True) + or (suppression_recommended and (suppression_config is not False)) + ) and has_any_completions(result) + + if should_suppress: + suppression_exceptions: Set[str] = result.get( + "do_not_suppress", set() + ) + if isinstance(suppression_recommended, Iterable): + to_suppress = set(suppression_recommended) + else: + to_suppress = set(matchers) + suppressed_matchers = to_suppress - suppression_exceptions + + new_results = {} + for previous_matcher_id, previous_result in results.items(): + if previous_matcher_id not in suppressed_matchers: + new_results[previous_matcher_id] = previous_result + results = new_results + + results[matcher_id] = result + + _, matches = self._arrange_and_extract( + results, + # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission? + # if it was omission, we can remove the filtering step, otherwise remove this comment. + skip_matchers={jedi_matcher_id}, + abort_if_offset_changes=False, + ) + + # populate legacy stateful API + self.matches = matches + + return results + + @staticmethod + def _deduplicate( + matches: Sequence[AnyCompletion], + ) -> Iterable[AnyCompletion]: + filtered_matches: Dict[str, AnyCompletion] = {} + for match in matches: + text = match.text + if ( + text not in filtered_matches + or filtered_matches[text].type == _UNKNOWN_TYPE + ): + filtered_matches[text] = match + + return filtered_matches.values() + + @staticmethod + def _sort(matches: Sequence[AnyCompletion]): + return sorted(matches, key=lambda x: completions_sorting_key(x.text)) + + @context_matcher() + def fwd_unicode_matcher(self, context: CompletionContext): + """Same as :any:`fwd_unicode_match`, but adopted to new Matcher API.""" + # TODO: use `context.limit` to terminate early once we matched the maximum + # number that will be used downstream; can be added as an optional to + # `fwd_unicode_match(text: str, limit: int = None)` or we could re-implement here. + fragment, matches = self.fwd_unicode_match(context.text_until_cursor) + return _convert_matcher_v1_result_to_v2( + matches, type="unicode", fragment=fragment, suppress_if_matches=True + ) + + def fwd_unicode_match(self, text: str) -> Tuple[str, Sequence[str]]: + """ + Forward match a string starting with a backslash with a list of + potential Unicode completions. + + Will compute list of Unicode character names on first call and cache it. + + .. deprecated:: 8.6 + You can use :meth:`fwd_unicode_matcher` instead. + + Returns + ------- + At tuple with: + - matched text (empty if no matches) + - list of potential completions, empty tuple otherwise) + """ + # TODO: self.unicode_names is here a list we traverse each time with ~100k elements. + # We could do a faster match using a Trie. + + # Using pygtrie the following seem to work: + + # s = PrefixSet() + + # for c in range(0,0x10FFFF + 1): + # try: + # s.add(unicodedata.name(chr(c))) + # except ValueError: + # pass + # [''.join(k) for k in s.iter(prefix)] + + # But need to be timed and adds an extra dependency. + + slashpos = text.rfind('\\') + # if text starts with slash + if slashpos > -1: + # PERF: It's important that we don't access self._unicode_names + # until we're inside this if-block. _unicode_names is lazily + # initialized, and it takes a user-noticeable amount of time to + # initialize it, so we don't want to initialize it unless we're + # actually going to use it. + s = text[slashpos + 1 :] + sup = s.upper() + candidates = [x for x in self.unicode_names if x.startswith(sup)] + if candidates: + return s, candidates + candidates = [x for x in self.unicode_names if sup in x] + if candidates: + return s, candidates + splitsup = sup.split(" ") + candidates = [ + x for x in self.unicode_names if all(u in x for u in splitsup) + ] + if candidates: + return s, candidates + + return "", () + + # if text does not start with slash + else: + return '', () + + @property + def unicode_names(self) -> List[str]: + """List of names of unicode code points that can be completed. + + The list is lazily initialized on first access. + """ + if self._unicode_names is None: + names = [] + for c in range(0,0x10FFFF + 1): + try: + names.append(unicodedata.name(chr(c))) + except ValueError: + pass + self._unicode_names = _unicode_name_compute(_UNICODE_RANGES) + + return self._unicode_names + +def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]: + names = [] + for start,stop in ranges: + for c in range(start, stop) : + try: + names.append(unicodedata.name(chr(c))) + except ValueError: + pass + return names diff --git a/myenv/lib/python3.12/site-packages/IPython/core/completerlib.py b/myenv/lib/python3.12/site-packages/IPython/core/completerlib.py new file mode 100644 index 00000000..f15490f2 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/completerlib.py @@ -0,0 +1,382 @@ +# encoding: utf-8 +"""Implementations for various useful completers. + +These are all loaded by default by IPython. +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team. +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib imports +import glob +import inspect +import os +import re +import sys +from importlib import import_module +from importlib.machinery import all_suffixes + + +# Third-party imports +from time import time +from zipimport import zipimporter + +# Our own imports +from .completer import expand_user, compress_user +from .error import TryNext +from ..utils._process_common import arg_split + +# FIXME: this should be pulled in with the right call via the component system +from IPython import get_ipython + +from typing import List + +#----------------------------------------------------------------------------- +# Globals and constants +#----------------------------------------------------------------------------- +_suffixes = all_suffixes() + +# Time in seconds after which the rootmodules will be stored permanently in the +# ipython ip.db database (kept in the user's .ipython dir). +TIMEOUT_STORAGE = 2 + +# Time in seconds after which we give up +TIMEOUT_GIVEUP = 20 + +# Regular expression for the python import statement +import_re = re.compile(r'(?P[^\W\d]\w*?)' + r'(?P[/\\]__init__)?' + r'(?P%s)$' % + r'|'.join(re.escape(s) for s in _suffixes)) + +# RE for the ipython %run command (python + ipython scripts) +magic_run_re = re.compile(r'.*(\.ipy|\.ipynb|\.py[w]?)$') + +#----------------------------------------------------------------------------- +# Local utilities +#----------------------------------------------------------------------------- + + +def module_list(path: str) -> List[str]: + """ + Return the list containing the names of the modules available in the given + folder. + """ + # sys.path has the cwd as an empty string, but isdir/listdir need it as '.' + if path == '': + path = '.' + + # A few local constants to be used in loops below + pjoin = os.path.join + + if os.path.isdir(path): + # Build a list of all files in the directory and all files + # in its subdirectories. For performance reasons, do not + # recurse more than one level into subdirectories. + files: List[str] = [] + for root, dirs, nondirs in os.walk(path, followlinks=True): + subdir = root[len(path)+1:] + if subdir: + files.extend(pjoin(subdir, f) for f in nondirs) + dirs[:] = [] # Do not recurse into additional subdirectories. + else: + files.extend(nondirs) + + else: + try: + files = list(zipimporter(path)._files.keys()) # type: ignore + except Exception: + files = [] + + # Build a list of modules which match the import_re regex. + modules = [] + for f in files: + m = import_re.match(f) + if m: + modules.append(m.group('name')) + return list(set(modules)) + + +def get_root_modules(): + """ + Returns a list containing the names of all the modules available in the + folders of the pythonpath. + + ip.db['rootmodules_cache'] maps sys.path entries to list of modules. + """ + ip = get_ipython() + if ip is None: + # No global shell instance to store cached list of modules. + # Don't try to scan for modules every time. + return list(sys.builtin_module_names) + + if getattr(ip.db, "_mock", False): + rootmodules_cache = {} + else: + rootmodules_cache = ip.db.get("rootmodules_cache", {}) + rootmodules = list(sys.builtin_module_names) + start_time = time() + store = False + for path in sys.path: + try: + modules = rootmodules_cache[path] + except KeyError: + modules = module_list(path) + try: + modules.remove('__init__') + except ValueError: + pass + if path not in ('', '.'): # cwd modules should not be cached + rootmodules_cache[path] = modules + if time() - start_time > TIMEOUT_STORAGE and not store: + store = True + print("\nCaching the list of root modules, please wait!") + print("(This will only be done once - type '%rehashx' to " + "reset cache!)\n") + sys.stdout.flush() + if time() - start_time > TIMEOUT_GIVEUP: + print("This is taking too long, we give up.\n") + return [] + rootmodules.extend(modules) + if store: + ip.db['rootmodules_cache'] = rootmodules_cache + rootmodules = list(set(rootmodules)) + return rootmodules + + +def is_importable(module, attr: str, only_modules) -> bool: + if only_modules: + try: + mod = getattr(module, attr) + except ModuleNotFoundError: + # See gh-14434 + return False + return inspect.ismodule(mod) + else: + return not(attr[:2] == '__' and attr[-2:] == '__') + +def is_possible_submodule(module, attr): + try: + obj = getattr(module, attr) + except AttributeError: + # Is possibly an unimported submodule + return True + except TypeError: + # https://github.com/ipython/ipython/issues/9678 + return False + return inspect.ismodule(obj) + + +def try_import(mod: str, only_modules=False) -> List[str]: + """ + Try to import given module and return list of potential completions. + """ + mod = mod.rstrip('.') + try: + m = import_module(mod) + except: + return [] + + m_is_init = '__init__' in (getattr(m, '__file__', '') or '') + + completions = [] + if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init: + completions.extend( [attr for attr in dir(m) if + is_importable(m, attr, only_modules)]) + + m_all = getattr(m, "__all__", []) + if only_modules: + completions.extend(attr for attr in m_all if is_possible_submodule(m, attr)) + else: + completions.extend(m_all) + + if m_is_init: + file_ = m.__file__ + file_path = os.path.dirname(file_) # type: ignore + if file_path is not None: + completions.extend(module_list(file_path)) + completions_set = {c for c in completions if isinstance(c, str)} + completions_set.discard('__init__') + return list(completions_set) + + +#----------------------------------------------------------------------------- +# Completion-related functions. +#----------------------------------------------------------------------------- + +def quick_completer(cmd, completions): + r""" Easily create a trivial completer for a command. + + Takes either a list of completions, or all completions in string (that will + be split on whitespace). + + Example:: + + [d:\ipython]|1> import ipy_completers + [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz']) + [d:\ipython]|3> foo b + bar baz + [d:\ipython]|3> foo ba + """ + + if isinstance(completions, str): + completions = completions.split() + + def do_complete(self, event): + return completions + + get_ipython().set_hook('complete_command',do_complete, str_key = cmd) + +def module_completion(line): + """ + Returns a list containing the completion possibilities for an import line. + + The line looks like this : + 'import xml.d' + 'from xml.dom import' + """ + + words = line.split(' ') + nwords = len(words) + + # from whatever -> 'import ' + if nwords == 3 and words[0] == 'from': + return ['import '] + + # 'from xy' or 'import xy' + if nwords < 3 and (words[0] in {'%aimport', 'import', 'from'}) : + if nwords == 1: + return get_root_modules() + mod = words[1].split('.') + if len(mod) < 2: + return get_root_modules() + completion_list = try_import('.'.join(mod[:-1]), True) + return ['.'.join(mod[:-1] + [el]) for el in completion_list] + + # 'from xyz import abc' + if nwords >= 3 and words[0] == 'from': + mod = words[1] + return try_import(mod) + +#----------------------------------------------------------------------------- +# Completers +#----------------------------------------------------------------------------- +# These all have the func(self, event) signature to be used as custom +# completers + +def module_completer(self,event): + """Give completions after user has typed 'import ...' or 'from ...'""" + + # This works in all versions of python. While 2.5 has + # pkgutil.walk_packages(), that particular routine is fairly dangerous, + # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full + # of possibly problematic side effects. + # This search the folders in the sys.path for available modules. + + return module_completion(event.line) + +# FIXME: there's a lot of logic common to the run, cd and builtin file +# completers, that is currently reimplemented in each. + +def magic_run_completer(self, event): + """Complete files that end in .py or .ipy or .ipynb for the %run command. + """ + comps = arg_split(event.line, strict=False) + # relpath should be the current token that we need to complete. + if (len(comps) > 1) and (not event.line.endswith(' ')): + relpath = comps[-1].strip("'\"") + else: + relpath = '' + + #print("\nev=", event) # dbg + #print("rp=", relpath) # dbg + #print('comps=', comps) # dbg + + lglob = glob.glob + isdir = os.path.isdir + relpath, tilde_expand, tilde_val = expand_user(relpath) + + # Find if the user has already typed the first filename, after which we + # should complete on all files, since after the first one other files may + # be arguments to the input script. + + if any(magic_run_re.match(c) for c in comps): + matches = [f.replace('\\','/') + ('/' if isdir(f) else '') + for f in lglob(relpath+'*')] + else: + dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)] + pys = [f.replace('\\','/') + for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') + + lglob(relpath+'*.ipynb') + lglob(relpath + '*.pyw')] + + matches = dirs + pys + + #print('run comp:', dirs+pys) # dbg + return [compress_user(p, tilde_expand, tilde_val) for p in matches] + + +def cd_completer(self, event): + """Completer function for cd, which only returns directories.""" + ip = get_ipython() + relpath = event.symbol + + #print(event) # dbg + if event.line.endswith('-b') or ' -b ' in event.line: + # return only bookmark completions + bkms = self.db.get('bookmarks', None) + if bkms: + return bkms.keys() + else: + return [] + + if event.symbol == '-': + width_dh = str(len(str(len(ip.user_ns['_dh']) + 1))) + # jump in directory history by number + fmt = '-%0' + width_dh +'d [%s]' + ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])] + if len(ents) > 1: + return ents + return [] + + if event.symbol.startswith('--'): + return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']] + + # Expand ~ in path and normalize directory separators. + relpath, tilde_expand, tilde_val = expand_user(relpath) + relpath = relpath.replace('\\','/') + + found = [] + for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*') + if os.path.isdir(f)]: + if ' ' in d: + # we don't want to deal with any of that, complex code + # for this is elsewhere + raise TryNext + + found.append(d) + + if not found: + if os.path.isdir(relpath): + return [compress_user(relpath, tilde_expand, tilde_val)] + + # if no completions so far, try bookmarks + bks = self.db.get('bookmarks',{}) + bkmatches = [s for s in bks if s.startswith(event.symbol)] + if bkmatches: + return bkmatches + + raise TryNext + + return [compress_user(p, tilde_expand, tilde_val) for p in found] + +def reset_completer(self, event): + "A completer for %reset magic" + return '-f -s in out array dhist'.split() diff --git a/myenv/lib/python3.12/site-packages/IPython/core/crashhandler.py b/myenv/lib/python3.12/site-packages/IPython/core/crashhandler.py new file mode 100644 index 00000000..9c75c5d8 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/crashhandler.py @@ -0,0 +1,248 @@ +# encoding: utf-8 +"""sys.excepthook for IPython itself, leaves a detailed report on disk. + +Authors: + +* Fernando Perez +* Brian E. Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2001-2007 Fernando Perez. +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys +import traceback +from pprint import pformat +from pathlib import Path + +import builtins as builtin_mod + +from IPython.core import ultratb +from IPython.core.application import Application +from IPython.core.release import author_email +from IPython.utils.sysinfo import sys_info + +from IPython.core.release import __version__ as version + +from typing import Optional, Dict +import types + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +# Template for the user message. +_default_message_template = """\ +Oops, {app_name} crashed. We do our best to make it stable, but... + +A crash report was automatically generated with the following information: + - A verbatim copy of the crash traceback. + - A copy of your input history during this session. + - Data on your current {app_name} configuration. + +It was left in the file named: +\t'{crash_report_fname}' +If you can email this file to the developers, the information in it will help +them in understanding and correcting the problem. + +You can mail it to: {contact_name} at {contact_email} +with the subject '{app_name} Crash Report'. + +If you want to do it now, the following command will work (under Unix): +mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname} + +In your email, please also include information about: +- The operating system under which the crash happened: Linux, macOS, Windows, + other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2, + Windows 10 Pro), and whether it is 32-bit or 64-bit; +- How {app_name} was installed: using pip or conda, from GitHub, as part of + a Docker container, or other, providing more detail if possible; +- How to reproduce the crash: what exact sequence of instructions can one + input to get the same crash? Ideally, find a minimal yet complete sequence + of instructions that yields the crash. + +To ensure accurate tracking of this issue, please file a report about it at: +{bug_tracker} +""" + +_lite_message_template = """ +If you suspect this is an IPython {version} bug, please report it at: + https://github.com/ipython/ipython/issues +or send an email to the mailing list at {email} + +You can print a more detailed traceback right now with "%tb", or use "%debug" +to interactively debug it. + +Extra-detailed tracebacks for bug-reporting purposes can be enabled via: + {config}Application.verbose_crash=True +""" + + +class CrashHandler: + """Customizable crash handlers for IPython applications. + + Instances of this class provide a :meth:`__call__` method which can be + used as a ``sys.excepthook``. The :meth:`__call__` signature is:: + + def __call__(self, etype, evalue, etb) + """ + + message_template = _default_message_template + section_sep = '\n\n'+'*'*75+'\n\n' + info: Dict[str, Optional[str]] + + def __init__( + self, + app: Application, + contact_name: Optional[str] = None, + contact_email: Optional[str] = None, + bug_tracker: Optional[str] = None, + show_crash_traceback: bool = True, + call_pdb: bool = False, + ): + """Create a new crash handler + + Parameters + ---------- + app : Application + A running :class:`Application` instance, which will be queried at + crash time for internal information. + contact_name : str + A string with the name of the person to contact. + contact_email : str + A string with the email address of the contact. + bug_tracker : str + A string with the URL for your project's bug tracker. + show_crash_traceback : bool + If false, don't print the crash traceback on stderr, only generate + the on-disk report + call_pdb + Whether to call pdb on crash + + Attributes + ---------- + These instances contain some non-argument attributes which allow for + further customization of the crash handler's behavior. Please see the + source for further details. + + """ + self.crash_report_fname = "Crash_report_%s.txt" % app.name + self.app = app + self.call_pdb = call_pdb + #self.call_pdb = True # dbg + self.show_crash_traceback = show_crash_traceback + self.info = dict(app_name = app.name, + contact_name = contact_name, + contact_email = contact_email, + bug_tracker = bug_tracker, + crash_report_fname = self.crash_report_fname) + + def __call__( + self, + etype: type[BaseException], + evalue: BaseException, + etb: types.TracebackType, + ) -> None: + """Handle an exception, call for compatible with sys.excepthook""" + + # do not allow the crash handler to be called twice without reinstalling it + # this prevents unlikely errors in the crash handling from entering an + # infinite loop. + sys.excepthook = sys.__excepthook__ + + # Report tracebacks shouldn't use color in general (safer for users) + color_scheme = 'NoColor' + + # Use this ONLY for developer debugging (keep commented out for release) + # color_scheme = 'Linux' # dbg + ipython_dir = getattr(self.app, "ipython_dir", None) + if ipython_dir is not None: + assert isinstance(ipython_dir, str) + rptdir = Path(ipython_dir) + else: + rptdir = Path.cwd() + if not rptdir.is_dir(): + rptdir = Path.cwd() + report_name = rptdir / self.crash_report_fname + # write the report filename into the instance dict so it can get + # properly expanded out in the user message template + self.crash_report_fname = str(report_name) + self.info["crash_report_fname"] = str(report_name) + TBhandler = ultratb.VerboseTB( + color_scheme=color_scheme, + long_header=True, + call_pdb=self.call_pdb, + ) + if self.call_pdb: + TBhandler(etype,evalue,etb) + return + else: + traceback = TBhandler.text(etype,evalue,etb,context=31) + + # print traceback to screen + if self.show_crash_traceback: + print(traceback, file=sys.stderr) + + # and generate a complete report on disk + try: + report = open(report_name, "w", encoding="utf-8") + except: + print('Could not create crash report on disk.', file=sys.stderr) + return + + with report: + # Inform user on stderr of what happened + print('\n'+'*'*70+'\n', file=sys.stderr) + print(self.message_template.format(**self.info), file=sys.stderr) + + # Construct report on disk + report.write(self.make_report(str(traceback))) + + builtin_mod.input("Hit to quit (your terminal may close):") + + def make_report(self, traceback: str) -> str: + """Return a string containing a crash report.""" + + sec_sep = self.section_sep + + report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n'] + rpt_add = report.append + rpt_add(sys_info()) + + try: + config = pformat(self.app.config) + rpt_add(sec_sep) + rpt_add("Application name: %s\n\n" % self.app.name) + rpt_add("Current user configuration structure:\n\n") + rpt_add(config) + except: + pass + rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) + + return ''.join(report) + + +def crash_handler_lite( + etype: type[BaseException], evalue: BaseException, tb: types.TracebackType +) -> None: + """a light excepthook, adding a small message to the usual traceback""" + traceback.print_exception(etype, evalue, tb) + + from IPython.core.interactiveshell import InteractiveShell + if InteractiveShell.initialized(): + # we are in a Shell environment, give %magic example + config = "%config " + else: + # we are not in a shell, show generic config + config = "c." + print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr) + diff --git a/myenv/lib/python3.12/site-packages/IPython/core/debugger.py b/myenv/lib/python3.12/site-packages/IPython/core/debugger.py new file mode 100644 index 00000000..1f0d7b2f --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/debugger.py @@ -0,0 +1,1136 @@ +""" +Pdb debugger class. + + +This is an extension to PDB which adds a number of new features. +Note that there is also the `IPython.terminal.debugger` class which provides UI +improvements. + +We also strongly recommend to use this via the `ipdb` package, which provides +extra configuration options. + +Among other things, this subclass of PDB: + - supports many IPython magics like pdef/psource + - hide frames in tracebacks based on `__tracebackhide__` + - allows to skip frames based on `__debuggerskip__` + + +Global Configuration +-------------------- + +The IPython debugger will by read the global ``~/.pdbrc`` file. +That is to say you can list all commands supported by ipdb in your `~/.pdbrc` +configuration file, to globally configure pdb. + +Example:: + + # ~/.pdbrc + skip_predicates debuggerskip false + skip_hidden false + context 25 + +Features +-------- + +The IPython debugger can hide and skip frames when printing or moving through +the stack. This can have a performance impact, so can be configures. + +The skipping and hiding frames are configurable via the `skip_predicates` +command. + +By default, frames from readonly files will be hidden, frames containing +``__tracebackhide__ = True`` will be hidden. + +Frames containing ``__debuggerskip__`` will be stepped over, frames whose parent +frames value of ``__debuggerskip__`` is ``True`` will also be skipped. + + >>> def helpers_helper(): + ... pass + ... + ... def helper_1(): + ... print("don't step in me") + ... helpers_helpers() # will be stepped over unless breakpoint set. + ... + ... + ... def helper_2(): + ... print("in me neither") + ... + +One can define a decorator that wraps a function between the two helpers: + + >>> def pdb_skipped_decorator(function): + ... + ... + ... def wrapped_fn(*args, **kwargs): + ... __debuggerskip__ = True + ... helper_1() + ... __debuggerskip__ = False + ... result = function(*args, **kwargs) + ... __debuggerskip__ = True + ... helper_2() + ... # setting __debuggerskip__ to False again is not necessary + ... return result + ... + ... return wrapped_fn + +When decorating a function, ipdb will directly step into ``bar()`` by +default: + + >>> @foo_decorator + ... def bar(x, y): + ... return x * y + + +You can toggle the behavior with + + ipdb> skip_predicates debuggerskip false + +or configure it in your ``.pdbrc`` + + + +License +------- + +Modified from the standard pdb.Pdb class to avoid including readline, so that +the command line completion of other programs which include this isn't +damaged. + +In the future, this class will be expanded with improvements over the standard +pdb. + +The original code in this file is mainly lifted out of cmd.py in Python 2.2, +with minor changes. Licensing should therefore be under the standard Python +terms. For details on the PSF (Python Software Foundation) standard license, +see: + +https://docs.python.org/2/license.html + + +All the changes since then are under the same license as IPython. + +""" + +#***************************************************************************** +# +# This file is licensed under the PSF license. +# +# Copyright (C) 2001 Python Software Foundation, www.python.org +# Copyright (C) 2005-2006 Fernando Perez. +# +# +#***************************************************************************** + +from __future__ import annotations + +import inspect +import linecache +import os +import re +import sys +from contextlib import contextmanager +from functools import lru_cache + +from IPython import get_ipython +from IPython.core.excolors import exception_colors +from IPython.utils import PyColorize, coloransi, py3compat + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + # otherwise circular import + from IPython.core.interactiveshell import InteractiveShell + +# skip module docstests +__skip_doctest__ = True + +prompt = 'ipdb> ' + +# We have to check this directly from sys.argv, config struct not yet available +from pdb import Pdb as OldPdb + +# Allow the set_trace code to operate outside of an ipython instance, even if +# it does so with some limitations. The rest of this support is implemented in +# the Tracer constructor. + +DEBUGGERSKIP = "__debuggerskip__" + + +# this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676 +# on lower python versions, we backported the feature. +CHAIN_EXCEPTIONS = sys.version_info < (3, 13) + + +def make_arrow(pad): + """generate the leading arrow in front of traceback or debugger""" + if pad >= 2: + return '-'*(pad-2) + '> ' + elif pad == 1: + return '>' + return '' + + +def BdbQuit_excepthook(et, ev, tb, excepthook=None): + """Exception hook which handles `BdbQuit` exceptions. + + All other exceptions are processed using the `excepthook` + parameter. + """ + raise ValueError( + "`BdbQuit_excepthook` is deprecated since version 5.1. It is still around only because it is still imported by ipdb.", + ) + + +RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+') + + +def strip_indentation(multiline_string): + return RGX_EXTRA_INDENT.sub('', multiline_string) + + +def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): + """Make new_fn have old_fn's doc string. This is particularly useful + for the ``do_...`` commands that hook into the help system. + Adapted from from a comp.lang.python posting + by Duncan Booth.""" + def wrapper(*args, **kw): + return new_fn(*args, **kw) + if old_fn.__doc__: + wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text + return wrapper + + +class Pdb(OldPdb): + """Modified Pdb class, does not load readline. + + for a standalone version that uses prompt_toolkit, see + `IPython.terminal.debugger.TerminalPdb` and + `IPython.terminal.debugger.set_trace()` + + + This debugger can hide and skip frames that are tagged according to some predicates. + See the `skip_predicates` commands. + + """ + + shell: InteractiveShell + + if CHAIN_EXCEPTIONS: + MAX_CHAINED_EXCEPTION_DEPTH = 999 + + default_predicates = { + "tbhide": True, + "readonly": False, + "ipython_internal": True, + "debuggerskip": True, + } + + def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs): + """Create a new IPython debugger. + + Parameters + ---------- + completekey : default None + Passed to pdb.Pdb. + stdin : default None + Passed to pdb.Pdb. + stdout : default None + Passed to pdb.Pdb. + context : int + Number of lines of source code context to show when + displaying stacktrace information. + **kwargs + Passed to pdb.Pdb. + + Notes + ----- + The possibilities are python version dependent, see the python + docs for more info. + """ + + # Parent constructor: + try: + self.context = int(context) + if self.context <= 0: + raise ValueError("Context must be a positive integer") + except (TypeError, ValueError) as e: + raise ValueError("Context must be a positive integer") from e + + # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`. + OldPdb.__init__(self, completekey, stdin, stdout, **kwargs) + + # IPython changes... + self.shell = get_ipython() + + if self.shell is None: + save_main = sys.modules['__main__'] + # No IPython instance running, we must create one + from IPython.terminal.interactiveshell import \ + TerminalInteractiveShell + self.shell = TerminalInteractiveShell.instance() + # needed by any code which calls __import__("__main__") after + # the debugger was entered. See also #9941. + sys.modules["__main__"] = save_main + + + color_scheme = self.shell.colors + + self.aliases = {} + + # Create color table: we copy the default one from the traceback + # module and add a few attributes needed for debugging + self.color_scheme_table = exception_colors() + + # shorthands + C = coloransi.TermColors + cst = self.color_scheme_table + + + # Add a python parser so we can syntax highlight source while + # debugging. + self.parser = PyColorize.Parser(style=color_scheme) + self.set_colors(color_scheme) + + # Set the prompt - the default prompt is '(Pdb)' + self.prompt = prompt + self.skip_hidden = True + self.report_skipped = True + + # list of predicates we use to skip frames + self._predicates = self.default_predicates + + if CHAIN_EXCEPTIONS: + self._chained_exceptions = tuple() + self._chained_exception_index = 0 + + # + def set_colors(self, scheme): + """Shorthand access to the color table scheme selector method.""" + self.color_scheme_table.set_active_scheme(scheme) + self.parser.style = scheme + + def set_trace(self, frame=None): + if frame is None: + frame = sys._getframe().f_back + self.initial_frame = frame + return super().set_trace(frame) + + def _hidden_predicate(self, frame): + """ + Given a frame return whether it it should be hidden or not by IPython. + """ + + if self._predicates["readonly"]: + fname = frame.f_code.co_filename + # we need to check for file existence and interactively define + # function would otherwise appear as RO. + if os.path.isfile(fname) and not os.access(fname, os.W_OK): + return True + + if self._predicates["tbhide"]: + if frame in (self.curframe, getattr(self, "initial_frame", None)): + return False + frame_locals = self._get_frame_locals(frame) + if "__tracebackhide__" not in frame_locals: + return False + return frame_locals["__tracebackhide__"] + return False + + def hidden_frames(self, stack): + """ + Given an index in the stack return whether it should be skipped. + + This is used in up/down and where to skip frames. + """ + # The f_locals dictionary is updated from the actual frame + # locals whenever the .f_locals accessor is called, so we + # avoid calling it here to preserve self.curframe_locals. + # Furthermore, there is no good reason to hide the current frame. + ip_hide = [self._hidden_predicate(s[0]) for s in stack] + ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"] + if ip_start and self._predicates["ipython_internal"]: + ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)] + return ip_hide + + if CHAIN_EXCEPTIONS: + + def _get_tb_and_exceptions(self, tb_or_exc): + """ + Given a tracecack or an exception, return a tuple of chained exceptions + and current traceback to inspect. + This will deal with selecting the right ``__cause__`` or ``__context__`` + as well as handling cycles, and return a flattened list of exceptions we + can jump to with do_exceptions. + """ + _exceptions = [] + if isinstance(tb_or_exc, BaseException): + traceback, current = tb_or_exc.__traceback__, tb_or_exc + + while current is not None: + if current in _exceptions: + break + _exceptions.append(current) + if current.__cause__ is not None: + current = current.__cause__ + elif ( + current.__context__ is not None + and not current.__suppress_context__ + ): + current = current.__context__ + + if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH: + self.message( + f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}" + " chained exceptions found, not all exceptions" + "will be browsable with `exceptions`." + ) + break + else: + traceback = tb_or_exc + return tuple(reversed(_exceptions)), traceback + + @contextmanager + def _hold_exceptions(self, exceptions): + """ + Context manager to ensure proper cleaning of exceptions references + When given a chained exception instead of a traceback, + pdb may hold references to many objects which may leak memory. + We use this context manager to make sure everything is properly cleaned + """ + try: + self._chained_exceptions = exceptions + self._chained_exception_index = len(exceptions) - 1 + yield + finally: + # we can't put those in forget as otherwise they would + # be cleared on exception change + self._chained_exceptions = tuple() + self._chained_exception_index = 0 + + def do_exceptions(self, arg): + """exceptions [number] + List or change current exception in an exception chain. + Without arguments, list all the current exception in the exception + chain. Exceptions will be numbered, with the current exception indicated + with an arrow. + If given an integer as argument, switch to the exception at that index. + """ + if not self._chained_exceptions: + self.message( + "Did not find chained exceptions. To move between" + " exceptions, pdb/post_mortem must be given an exception" + " object rather than a traceback." + ) + return + if not arg: + for ix, exc in enumerate(self._chained_exceptions): + prompt = ">" if ix == self._chained_exception_index else " " + rep = repr(exc) + if len(rep) > 80: + rep = rep[:77] + "..." + indicator = ( + " -" + if self._chained_exceptions[ix].__traceback__ is None + else f"{ix:>3}" + ) + self.message(f"{prompt} {indicator} {rep}") + else: + try: + number = int(arg) + except ValueError: + self.error("Argument must be an integer") + return + if 0 <= number < len(self._chained_exceptions): + if self._chained_exceptions[number].__traceback__ is None: + self.error( + "This exception does not have a traceback, cannot jump to it" + ) + return + + self._chained_exception_index = number + self.setup(None, self._chained_exceptions[number].__traceback__) + self.print_stack_entry(self.stack[self.curindex]) + else: + self.error("No exception with that number") + + def interaction(self, frame, tb_or_exc): + try: + if CHAIN_EXCEPTIONS: + # this context manager is part of interaction in 3.13 + _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc) + if isinstance(tb_or_exc, BaseException): + assert tb is not None, "main exception must have a traceback" + with self._hold_exceptions(_chained_exceptions): + OldPdb.interaction(self, frame, tb) + else: + OldPdb.interaction(self, frame, tb_or_exc) + + except KeyboardInterrupt: + self.stdout.write("\n" + self.shell.get_exception_only()) + + def precmd(self, line): + """Perform useful escapes on the command before it is executed.""" + + if line.endswith("??"): + line = "pinfo2 " + line[:-2] + elif line.endswith("?"): + line = "pinfo " + line[:-1] + + line = super().precmd(line) + + return line + + def new_do_quit(self, arg): + return OldPdb.do_quit(self, arg) + + do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit) + + def print_stack_trace(self, context=None): + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + if context is None: + context = self.context + try: + context = int(context) + if context <= 0: + raise ValueError("Context must be a positive integer") + except (TypeError, ValueError) as e: + raise ValueError("Context must be a positive integer") from e + try: + skipped = 0 + for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack): + if hidden and self.skip_hidden: + skipped += 1 + continue + if skipped: + print( + f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + skipped = 0 + self.print_stack_entry(frame_lineno, context=context) + if skipped: + print( + f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + except KeyboardInterrupt: + pass + + def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ', + context=None): + if context is None: + context = self.context + try: + context = int(context) + if context <= 0: + raise ValueError("Context must be a positive integer") + except (TypeError, ValueError) as e: + raise ValueError("Context must be a positive integer") from e + print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout) + + # vds: >> + frame, lineno = frame_lineno + filename = frame.f_code.co_filename + self.shell.hooks.synchronize_with_editor(filename, lineno, 0) + # vds: << + + def _get_frame_locals(self, frame): + """ " + Accessing f_local of current frame reset the namespace, so we want to avoid + that or the following can happen + + ipdb> foo + "old" + ipdb> foo = "new" + ipdb> foo + "new" + ipdb> where + ipdb> foo + "old" + + So if frame is self.current_frame we instead return self.curframe_locals + + """ + if frame is self.curframe: + return self.curframe_locals + else: + return frame.f_locals + + def format_stack_entry(self, frame_lineno, lprefix=': ', context=None): + if context is None: + context = self.context + try: + context = int(context) + if context <= 0: + print("Context must be a positive integer", file=self.stdout) + except (TypeError, ValueError): + print("Context must be a positive integer", file=self.stdout) + + import reprlib + + ret = [] + + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal) + tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal) + tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal) + tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal) + + frame, lineno = frame_lineno + + return_value = '' + loc_frame = self._get_frame_locals(frame) + if "__return__" in loc_frame: + rv = loc_frame["__return__"] + # return_value += '->' + return_value += reprlib.repr(rv) + "\n" + ret.append(return_value) + + #s = filename + '(' + `lineno` + ')' + filename = self.canonic(frame.f_code.co_filename) + link = tpl_link % py3compat.cast_unicode(filename) + + if frame.f_code.co_name: + func = frame.f_code.co_name + else: + func = "" + + call = "" + if func != "?": + if "__args__" in loc_frame: + args = reprlib.repr(loc_frame["__args__"]) + else: + args = '()' + call = tpl_call % (func, args) + + # The level info should be generated in the same format pdb uses, to + # avoid breaking the pdbtrack functionality of python-mode in *emacs. + if frame is self.curframe: + ret.append('> ') + else: + ret.append(" ") + ret.append("%s(%s)%s\n" % (link, lineno, call)) + + start = lineno - 1 - context//2 + lines = linecache.getlines(filename) + start = min(start, len(lines) - context) + start = max(start, 0) + lines = lines[start : start + context] + + for i, line in enumerate(lines): + show_arrow = start + 1 + i == lineno + linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line + ret.append( + self.__format_line( + linetpl, filename, start + 1 + i, line, arrow=show_arrow + ) + ) + return "".join(ret) + + def __format_line(self, tpl_line, filename, lineno, line, arrow=False): + bp_mark = "" + bp_mark_color = "" + + new_line, err = self.parser.format2(line, 'str') + if not err: + line = new_line + + bp = None + if lineno in self.get_file_breaks(filename): + bps = self.get_breaks(filename, lineno) + bp = bps[-1] + + if bp: + Colors = self.color_scheme_table.active_colors + bp_mark = str(bp.number) + bp_mark_color = Colors.breakpoint_enabled + if not bp.enabled: + bp_mark_color = Colors.breakpoint_disabled + + numbers_width = 7 + if arrow: + # This is the line with the error + pad = numbers_width - len(str(lineno)) - len(bp_mark) + num = '%s%s' % (make_arrow(pad), str(lineno)) + else: + num = '%*s' % (numbers_width - len(bp_mark), str(lineno)) + + return tpl_line % (bp_mark_color + bp_mark, num, line) + + def print_list_lines(self, filename, first, last): + """The printing (as opposed to the parsing part of a 'list' + command.""" + try: + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal) + tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal) + src = [] + if filename == "" and hasattr(self, "_exec_filename"): + filename = self._exec_filename + + for lineno in range(first, last+1): + line = linecache.getline(filename, lineno) + if not line: + break + + if lineno == self.curframe.f_lineno: + line = self.__format_line( + tpl_line_em, filename, lineno, line, arrow=True + ) + else: + line = self.__format_line( + tpl_line, filename, lineno, line, arrow=False + ) + + src.append(line) + self.lineno = lineno + + print(''.join(src), file=self.stdout) + + except KeyboardInterrupt: + pass + + def do_skip_predicates(self, args): + """ + Turn on/off individual predicates as to whether a frame should be hidden/skip. + + The global option to skip (or not) hidden frames is set with skip_hidden + + To change the value of a predicate + + skip_predicates key [true|false] + + Call without arguments to see the current values. + + To permanently change the value of an option add the corresponding + command to your ``~/.pdbrc`` file. If you are programmatically using the + Pdb instance you can also change the ``default_predicates`` class + attribute. + """ + if not args.strip(): + print("current predicates:") + for p, v in self._predicates.items(): + print(" ", p, ":", v) + return + type_value = args.strip().split(" ") + if len(type_value) != 2: + print( + f"Usage: skip_predicates , with one of {set(self._predicates.keys())}" + ) + return + + type_, value = type_value + if type_ not in self._predicates: + print(f"{type_!r} not in {set(self._predicates.keys())}") + return + if value.lower() not in ("true", "yes", "1", "no", "false", "0"): + print( + f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')" + ) + return + + self._predicates[type_] = value.lower() in ("true", "yes", "1") + if not any(self._predicates.values()): + print( + "Warning, all predicates set to False, skip_hidden may not have any effects." + ) + + def do_skip_hidden(self, arg): + """ + Change whether or not we should skip frames with the + __tracebackhide__ attribute. + """ + if not arg.strip(): + print( + f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change." + ) + elif arg.strip().lower() in ("true", "yes"): + self.skip_hidden = True + elif arg.strip().lower() in ("false", "no"): + self.skip_hidden = False + if not any(self._predicates.values()): + print( + "Warning, all predicates set to False, skip_hidden may not have any effects." + ) + + def do_list(self, arg): + """Print lines of code from the current stack frame + """ + self.lastcmd = 'list' + last = None + if arg and arg != ".": + try: + x = eval(arg, {}, {}) + if type(x) == type(()): + first, last = x + first = int(first) + last = int(last) + if last < first: + # Assume it's a count + last = first + last + else: + first = max(1, int(x) - 5) + except: + print('*** Error in argument:', repr(arg), file=self.stdout) + return + elif self.lineno is None or arg == ".": + first = max(1, self.curframe.f_lineno - 5) + else: + first = self.lineno + 1 + if last is None: + last = first + 10 + self.print_list_lines(self.curframe.f_code.co_filename, first, last) + + # vds: >> + lineno = first + filename = self.curframe.f_code.co_filename + self.shell.hooks.synchronize_with_editor(filename, lineno, 0) + # vds: << + + do_l = do_list + + def getsourcelines(self, obj): + lines, lineno = inspect.findsource(obj) + if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj): + # must be a module frame: do not try to cut a block out of it + return lines, 1 + elif inspect.ismodule(obj): + return lines, 1 + return inspect.getblock(lines[lineno:]), lineno+1 + + def do_longlist(self, arg): + """Print lines of code from the current stack frame. + + Shows more lines than 'list' does. + """ + self.lastcmd = 'longlist' + try: + lines, lineno = self.getsourcelines(self.curframe) + except OSError as err: + self.error(err) + return + last = lineno + len(lines) + self.print_list_lines(self.curframe.f_code.co_filename, lineno, last) + do_ll = do_longlist + + def do_debug(self, arg): + """debug code + Enter a recursive debugger that steps through the code + argument (which is an arbitrary expression or statement to be + executed in the current environment). + """ + trace_function = sys.gettrace() + sys.settrace(None) + globals = self.curframe.f_globals + locals = self.curframe_locals + p = self.__class__(completekey=self.completekey, + stdin=self.stdin, stdout=self.stdout) + p.use_rawinput = self.use_rawinput + p.prompt = "(%s) " % self.prompt.strip() + self.message("ENTERING RECURSIVE DEBUGGER") + sys.call_tracing(p.run, (arg, globals, locals)) + self.message("LEAVING RECURSIVE DEBUGGER") + sys.settrace(trace_function) + self.lastcmd = p.lastcmd + + def do_pdef(self, arg): + """Print the call signature for any callable object. + + The debugger interface to %pdef""" + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pdef")(arg, namespaces=namespaces) + + def do_pdoc(self, arg): + """Print the docstring for an object. + + The debugger interface to %pdoc.""" + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces) + + def do_pfile(self, arg): + """Print (or run through pager) the file where an object is defined. + + The debugger interface to %pfile. + """ + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pfile")(arg, namespaces=namespaces) + + def do_pinfo(self, arg): + """Provide detailed information about an object. + + The debugger interface to %pinfo, i.e., obj?.""" + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces) + + def do_pinfo2(self, arg): + """Provide extra detailed information about an object. + + The debugger interface to %pinfo2, i.e., obj??.""" + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces) + + def do_psource(self, arg): + """Print (or run through pager) the source code for an object.""" + namespaces = [ + ("Locals", self.curframe_locals), + ("Globals", self.curframe.f_globals), + ] + self.shell.find_line_magic("psource")(arg, namespaces=namespaces) + + def do_where(self, arg): + """w(here) + Print a stack trace, with the most recent frame at the bottom. + An arrow indicates the "current frame", which determines the + context of most commands. 'bt' is an alias for this command. + + Take a number as argument as an (optional) number of context line to + print""" + if arg: + try: + context = int(arg) + except ValueError as err: + self.error(err) + return + self.print_stack_trace(context) + else: + self.print_stack_trace() + + do_w = do_where + + def break_anywhere(self, frame): + """ + _stop_in_decorator_internals is overly restrictive, as we may still want + to trace function calls, so we need to also update break_anywhere so + that is we don't `stop_here`, because of debugger skip, we may still + stop at any point inside the function + + """ + + sup = super().break_anywhere(frame) + if sup: + return sup + if self._predicates["debuggerskip"]: + if DEBUGGERSKIP in frame.f_code.co_varnames: + return True + if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): + return True + return False + + def _is_in_decorator_internal_and_should_skip(self, frame): + """ + Utility to tell us whether we are in a decorator internal and should stop. + + """ + # if we are disabled don't skip + if not self._predicates["debuggerskip"]: + return False + + return self._cachable_skip(frame) + + @lru_cache(1024) + def _cached_one_parent_frame_debuggerskip(self, frame): + """ + Cache looking up for DEBUGGERSKIP on parent frame. + + This should speedup walking through deep frame when one of the highest + one does have a debugger skip. + + This is likely to introduce fake positive though. + """ + while getattr(frame, "f_back", None): + frame = frame.f_back + if self._get_frame_locals(frame).get(DEBUGGERSKIP): + return True + return None + + @lru_cache(1024) + def _cachable_skip(self, frame): + # if frame is tagged, skip by default. + if DEBUGGERSKIP in frame.f_code.co_varnames: + return True + + # if one of the parent frame value set to True skip as well. + if self._cached_one_parent_frame_debuggerskip(frame): + return True + + return False + + def stop_here(self, frame): + if self._is_in_decorator_internal_and_should_skip(frame) is True: + return False + + hidden = False + if self.skip_hidden: + hidden = self._hidden_predicate(frame) + if hidden: + if self.report_skipped: + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + print( + f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n" + ) + return super().stop_here(frame) + + def do_up(self, arg): + """u(p) [count] + Move the current frame count (default one) levels up in the + stack trace (to an older frame). + + Will skip hidden frames. + """ + # modified version of upstream that skips + # frames with __tracebackhide__ + if self.curindex == 0: + self.error("Oldest frame") + return + try: + count = int(arg or 1) + except ValueError: + self.error("Invalid frame count (%s)" % arg) + return + skipped = 0 + if count < 0: + _newframe = 0 + else: + counter = 0 + hidden_frames = self.hidden_frames(self.stack) + for i in range(self.curindex - 1, -1, -1): + if hidden_frames[i] and self.skip_hidden: + skipped += 1 + continue + counter += 1 + if counter >= count: + break + else: + # if no break occurred. + self.error( + "all frames above hidden, use `skip_hidden False` to get get into those." + ) + return + + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + _newframe = i + self._select_frame(_newframe) + if skipped: + print( + f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + + def do_down(self, arg): + """d(own) [count] + Move the current frame count (default one) levels down in the + stack trace (to a newer frame). + + Will skip hidden frames. + """ + if self.curindex + 1 == len(self.stack): + self.error("Newest frame") + return + try: + count = int(arg or 1) + except ValueError: + self.error("Invalid frame count (%s)" % arg) + return + if count < 0: + _newframe = len(self.stack) - 1 + else: + counter = 0 + skipped = 0 + hidden_frames = self.hidden_frames(self.stack) + for i in range(self.curindex + 1, len(self.stack)): + if hidden_frames[i] and self.skip_hidden: + skipped += 1 + continue + counter += 1 + if counter >= count: + break + else: + self.error( + "all frames below hidden, use `skip_hidden False` to get get into those." + ) + return + + Colors = self.color_scheme_table.active_colors + ColorsNormal = Colors.Normal + if skipped: + print( + f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" + ) + _newframe = i + + self._select_frame(_newframe) + + do_d = do_down + do_u = do_up + + def do_context(self, context): + """context number_of_lines + Set the number of lines of source code to show when displaying + stacktrace information. + """ + try: + new_context = int(context) + if new_context <= 0: + raise ValueError() + self.context = new_context + except ValueError: + self.error( + f"The 'context' command requires a positive integer argument (current value {self.context})." + ) + + +class InterruptiblePdb(Pdb): + """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" + + def cmdloop(self, intro=None): + """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" + try: + return OldPdb.cmdloop(self, intro=intro) + except KeyboardInterrupt: + self.stop_here = lambda frame: False + self.do_quit("") + sys.settrace(None) + self.quitting = False + raise + + def _cmdloop(self): + while True: + try: + # keyboard interrupts allow for an easy way to cancel + # the current command, so allow them during interactive input + self.allow_kbdint = True + self.cmdloop() + self.allow_kbdint = False + break + except KeyboardInterrupt: + self.message('--KeyboardInterrupt--') + raise + + +def set_trace(frame=None, header=None): + """ + Start debugging from `frame`. + + If frame is not specified, debugging starts from caller's frame. + """ + pdb = Pdb() + if header is not None: + pdb.message(header) + pdb.set_trace(frame or sys._getframe().f_back) diff --git a/myenv/lib/python3.12/site-packages/IPython/core/display.py b/myenv/lib/python3.12/site-packages/IPython/core/display.py new file mode 100644 index 00000000..a9434473 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/display.py @@ -0,0 +1,1373 @@ +# -*- coding: utf-8 -*- +"""Top-level display functions for displaying object in different formats.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +from binascii import b2a_base64, hexlify +import html +import json +import mimetypes +import os +import struct +import warnings +from copy import deepcopy +from os.path import splitext +from pathlib import Path, PurePath + +from typing import Optional + +from IPython.testing.skipdoctest import skip_doctest +from . import display_functions + + +__all__ = [ + "display_pretty", + "display_html", + "display_markdown", + "display_svg", + "display_png", + "display_jpeg", + "display_webp", + "display_latex", + "display_json", + "display_javascript", + "display_pdf", + "DisplayObject", + "TextDisplayObject", + "Pretty", + "HTML", + "Markdown", + "Math", + "Latex", + "SVG", + "ProgressBar", + "JSON", + "GeoJSON", + "Javascript", + "Image", + "set_matplotlib_formats", + "set_matplotlib_close", + "Video", +] + +_deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"] + +__all__ = __all__ + _deprecated_names + + +# ----- warn to import from IPython.display ----- + +from warnings import warn + + +def __getattr__(name): + if name in _deprecated_names: + warn( + f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython.display", + DeprecationWarning, + stacklevel=2, + ) + return getattr(display_functions, name) + + if name in globals().keys(): + return globals()[name] + else: + raise AttributeError(f"module {__name__} has no attribute {name}") + + +#----------------------------------------------------------------------------- +# utility functions +#----------------------------------------------------------------------------- + +def _safe_exists(path): + """Check path, but don't let exceptions raise""" + try: + return os.path.exists(path) + except Exception: + return False + + +def _display_mimetype(mimetype, objs, raw=False, metadata=None): + """internal implementation of all display_foo methods + + Parameters + ---------- + mimetype : str + The mimetype to be published (e.g. 'image/png') + *objs : object + The Python objects to display, or if raw=True raw text data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + if metadata: + metadata = {mimetype: metadata} + if raw: + # turn list of pngdata into list of { 'image/png': pngdata } + objs = [ {mimetype: obj} for obj in objs ] + display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype]) + +#----------------------------------------------------------------------------- +# Main functions +#----------------------------------------------------------------------------- + + +def display_pretty(*objs, **kwargs): + """Display the pretty (default) representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw text data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('text/plain', objs, **kwargs) + + +def display_html(*objs, **kwargs): + """Display the HTML representation of an object. + + Note: If raw=False and the object does not have a HTML + representation, no HTML will be shown. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw HTML data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('text/html', objs, **kwargs) + + +def display_markdown(*objs, **kwargs): + """Displays the Markdown representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw markdown data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + + _display_mimetype('text/markdown', objs, **kwargs) + + +def display_svg(*objs, **kwargs): + """Display the SVG representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw svg data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('image/svg+xml', objs, **kwargs) + + +def display_png(*objs, **kwargs): + """Display the PNG representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw png data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('image/png', objs, **kwargs) + + +def display_jpeg(*objs, **kwargs): + """Display the JPEG representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw JPEG data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('image/jpeg', objs, **kwargs) + + +def display_webp(*objs, **kwargs): + """Display the WEBP representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw JPEG data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype("image/webp", objs, **kwargs) + + +def display_latex(*objs, **kwargs): + """Display the LaTeX representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw latex data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('text/latex', objs, **kwargs) + + +def display_json(*objs, **kwargs): + """Display the JSON representation of an object. + + Note that not many frontends support displaying JSON. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw json data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('application/json', objs, **kwargs) + + +def display_javascript(*objs, **kwargs): + """Display the Javascript representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw javascript data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('application/javascript', objs, **kwargs) + + +def display_pdf(*objs, **kwargs): + """Display the PDF representation of an object. + + Parameters + ---------- + *objs : object + The Python objects to display, or if raw=True raw javascript data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + metadata : dict (optional) + Metadata to be associated with the specific mimetype output. + """ + _display_mimetype('application/pdf', objs, **kwargs) + + +#----------------------------------------------------------------------------- +# Smart classes +#----------------------------------------------------------------------------- + + +class DisplayObject(object): + """An object that wraps data to be displayed.""" + + _read_flags = 'r' + _show_mem_addr = False + metadata = None + + def __init__(self, data=None, url=None, filename=None, metadata=None): + """Create a display object given raw data. + + When this object is returned by an expression or passed to the + display function, it will result in the data being displayed + in the frontend. The MIME type of the data should match the + subclasses used, so the Png subclass should be used for 'image/png' + data. If the data is a URL, the data will first be downloaded + and then displayed. + + Parameters + ---------- + data : unicode, str or bytes + The raw data or a URL or file to load the data from + url : unicode + A URL to download the data from. + filename : unicode + Path to a local file to load the data from. + metadata : dict + Dict of metadata associated to be the object when displayed + """ + if isinstance(data, (Path, PurePath)): + data = str(data) + + if data is not None and isinstance(data, str): + if data.startswith('http') and url is None: + url = data + filename = None + data = None + elif _safe_exists(data) and filename is None: + url = None + filename = data + data = None + + self.url = url + self.filename = filename + # because of @data.setter methods in + # subclasses ensure url and filename are set + # before assigning to self.data + self.data = data + + if metadata is not None: + self.metadata = metadata + elif self.metadata is None: + self.metadata = {} + + self.reload() + self._check_data() + + def __repr__(self): + if not self._show_mem_addr: + cls = self.__class__ + r = "<%s.%s object>" % (cls.__module__, cls.__name__) + else: + r = super(DisplayObject, self).__repr__() + return r + + def _check_data(self): + """Override in subclasses if there's something to check.""" + pass + + def _data_and_metadata(self): + """shortcut for returning metadata with shape information, if defined""" + if self.metadata: + return self.data, deepcopy(self.metadata) + else: + return self.data + + def reload(self): + """Reload the raw data from file or URL.""" + if self.filename is not None: + encoding = None if "b" in self._read_flags else "utf-8" + with open(self.filename, self._read_flags, encoding=encoding) as f: + self.data = f.read() + elif self.url is not None: + # Deferred import + from urllib.request import urlopen + response = urlopen(self.url) + data = response.read() + # extract encoding from header, if there is one: + encoding = None + if 'content-type' in response.headers: + for sub in response.headers['content-type'].split(';'): + sub = sub.strip() + if sub.startswith('charset'): + encoding = sub.split('=')[-1].strip() + break + if 'content-encoding' in response.headers: + # TODO: do deflate? + if 'gzip' in response.headers['content-encoding']: + import gzip + from io import BytesIO + + # assume utf-8 if encoding is not specified + with gzip.open( + BytesIO(data), "rt", encoding=encoding or "utf-8" + ) as fp: + encoding = None + data = fp.read() + + # decode data, if an encoding was specified + # We only touch self.data once since + # subclasses such as SVG have @data.setter methods + # that transform self.data into ... well svg. + if encoding: + self.data = data.decode(encoding, 'replace') + else: + self.data = data + + +class TextDisplayObject(DisplayObject): + """Create a text display object given raw data. + + Parameters + ---------- + data : str or unicode + The raw data or a URL or file to load the data from. + url : unicode + A URL to download the data from. + filename : unicode + Path to a local file to load the data from. + metadata : dict + Dict of metadata associated to be the object when displayed + """ + def _check_data(self): + if self.data is not None and not isinstance(self.data, str): + raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data)) + +class Pretty(TextDisplayObject): + + def _repr_pretty_(self, pp, cycle): + return pp.text(self.data) + + +class HTML(TextDisplayObject): + + def __init__(self, data=None, url=None, filename=None, metadata=None): + def warn(): + if not data: + return False + + # + # Avoid calling lower() on the entire data, because it could be a + # long string and we're only interested in its beginning and end. + # + prefix = data[:10].lower() + suffix = data[-10:].lower() + return prefix.startswith("') + m_warn.assert_not_called() + + display.HTML('') + m_warn.assert_called_with('Consider using IPython.display.IFrame instead') + + m_warn.reset_mock() + display.HTML('') + m_warn.assert_called_with('Consider using IPython.display.IFrame instead') + +def test_progress(): + p = display.ProgressBar(10) + assert "0/10" in repr(p) + p.html_width = "100%" + p.progress = 5 + assert ( + p._repr_html_() == "" + ) + + +def test_progress_iter(): + with capture_output(display=False) as captured: + for i in display.ProgressBar(5): + out = captured.stdout + assert "{0}/5".format(i) in out + out = captured.stdout + assert "5/5" in out + + +def test_json(): + d = {'a': 5} + lis = [d] + metadata = [ + {'expanded': False, 'root': 'root'}, + {'expanded': True, 'root': 'root'}, + {'expanded': False, 'root': 'custom'}, + {'expanded': True, 'root': 'custom'}, + ] + json_objs = [ + display.JSON(d), + display.JSON(d, expanded=True), + display.JSON(d, root='custom'), + display.JSON(d, expanded=True, root='custom'), + ] + for j, md in zip(json_objs, metadata): + assert j._repr_json_() == (d, md) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + j = display.JSON(json.dumps(d)) + assert len(w) == 1 + assert j._repr_json_() == (d, metadata[0]) + + json_objs = [ + display.JSON(lis), + display.JSON(lis, expanded=True), + display.JSON(lis, root='custom'), + display.JSON(lis, expanded=True, root='custom'), + ] + for j, md in zip(json_objs, metadata): + assert j._repr_json_() == (lis, md) + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + j = display.JSON(json.dumps(lis)) + assert len(w) == 1 + assert j._repr_json_() == (lis, metadata[0]) + + +def test_video_embedding(): + """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash""" + v = display.Video("http://ignored") + assert not v.embed + html = v._repr_html_() + assert 'src="data:' not in html + assert 'src="http://ignored"' in html + + with pytest.raises(ValueError): + v = display.Video(b'abc') + + with NamedFileInTemporaryDirectory('test.mp4') as f: + f.write(b'abc') + f.close() + + v = display.Video(f.name) + assert not v.embed + html = v._repr_html_() + assert 'src="data:' not in html + + v = display.Video(f.name, embed=True) + html = v._repr_html_() + assert 'src="data:video/mp4;base64,YWJj"' in html + + v = display.Video(f.name, embed=True, mimetype='video/other') + html = v._repr_html_() + assert 'src="data:video/other;base64,YWJj"' in html + + v = display.Video(b'abc', embed=True, mimetype='video/mp4') + html = v._repr_html_() + assert 'src="data:video/mp4;base64,YWJj"' in html + + v = display.Video(u'YWJj', embed=True, mimetype='video/xyz') + html = v._repr_html_() + assert 'src="data:video/xyz;base64,YWJj"' in html + +def test_html_metadata(): + s = "

Test

" + h = display.HTML(s, metadata={"isolated": True}) + assert h._repr_html_() == (s, {"isolated": True}) + + +def test_display_id(): + ip = get_ipython() + with mock.patch.object(ip.display_pub, 'publish') as pub: + handle = display.display('x') + assert handle is None + handle = display.display('y', display_id='secret') + assert isinstance(handle, display.DisplayHandle) + handle2 = display.display('z', display_id=True) + assert isinstance(handle2, display.DisplayHandle) + assert handle.display_id != handle2.display_id + + assert pub.call_count == 3 + args, kwargs = pub.call_args_list[0] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('x') + }, + 'metadata': {}, + } + args, kwargs = pub.call_args_list[1] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('y') + }, + 'metadata': {}, + 'transient': { + 'display_id': handle.display_id, + }, + } + args, kwargs = pub.call_args_list[2] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('z') + }, + 'metadata': {}, + 'transient': { + 'display_id': handle2.display_id, + }, + } + + +def test_update_display(): + ip = get_ipython() + with mock.patch.object(ip.display_pub, 'publish') as pub: + with pytest.raises(TypeError): + display.update_display('x') + display.update_display('x', display_id='1') + display.update_display('y', display_id='2') + args, kwargs = pub.call_args_list[0] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('x') + }, + 'metadata': {}, + 'transient': { + 'display_id': '1', + }, + 'update': True, + } + args, kwargs = pub.call_args_list[1] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('y') + }, + 'metadata': {}, + 'transient': { + 'display_id': '2', + }, + 'update': True, + } + + +def test_display_handle(): + ip = get_ipython() + handle = display.DisplayHandle() + assert isinstance(handle.display_id, str) + handle = display.DisplayHandle("my-id") + assert handle.display_id == "my-id" + with mock.patch.object(ip.display_pub, "publish") as pub: + handle.display("x") + handle.update("y") + + args, kwargs = pub.call_args_list[0] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('x') + }, + 'metadata': {}, + 'transient': { + 'display_id': handle.display_id, + } + } + args, kwargs = pub.call_args_list[1] + assert args == () + assert kwargs == { + 'data': { + 'text/plain': repr('y') + }, + 'metadata': {}, + 'transient': { + 'display_id': handle.display_id, + }, + 'update': True, + } + + +def test_image_alt_tag(): + """Simple test for display.Image(args, alt=x,)""" + thisurl = "http://example.com/image.png" + img = display.Image(url=thisurl, alt="an image") + assert 'an image' % (thisurl) == img._repr_html_() + img = display.Image(url=thisurl, unconfined=True, alt="an image") + assert ( + 'an image' % (thisurl) + == img._repr_html_() + ) + img = display.Image(url=thisurl, alt='>"& <') + assert '>"& <' % (thisurl) == img._repr_html_() + + img = display.Image(url=thisurl, metadata={"alt": "an image"}) + assert img.alt == "an image" + here = os.path.dirname(__file__) + img = display.Image(os.path.join(here, "2x2.png"), alt="an image") + assert img.alt == "an image" + _, md = img._repr_png_() + assert md["alt"] == "an image" + + +def test_image_bad_filename_raises_proper_exception(): + with pytest.raises(FileNotFoundError): + display.Image("/this/file/does/not/exist/")._repr_png_() diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_displayhook.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_displayhook.py new file mode 100644 index 00000000..22899f3d --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_displayhook.py @@ -0,0 +1,112 @@ +import sys +from IPython.testing.tools import AssertPrints, AssertNotPrints +from IPython.core.displayhook import CapturingDisplayHook +from IPython.utils.capture import CapturedIO + +def test_output_displayed(): + """Checking to make sure that output is displayed""" + + with AssertPrints('2'): + ip.run_cell('1+1', store_history=True) + + with AssertPrints('2'): + ip.run_cell('1+1 # comment with a semicolon;', store_history=True) + + with AssertPrints('2'): + ip.run_cell('1+1\n#commented_out_function();', store_history=True) + + +def test_output_quiet(): + """Checking to make sure that output is quiet""" + + with AssertNotPrints('2'): + ip.run_cell('1+1;', store_history=True) + + with AssertNotPrints('2'): + ip.run_cell('1+1; # comment with a semicolon', store_history=True) + + with AssertNotPrints('2'): + ip.run_cell('1+1;\n#commented_out_function()', store_history=True) + +def test_underscore_no_overwrite_user(): + ip.run_cell('_ = 42', store_history=True) + ip.run_cell('1+1', store_history=True) + + with AssertPrints('42'): + ip.run_cell('print(_)', store_history=True) + + ip.run_cell('del _', store_history=True) + ip.run_cell('6+6', store_history=True) + with AssertPrints('12'): + ip.run_cell('_', store_history=True) + + +def test_underscore_no_overwrite_builtins(): + ip.run_cell("import gettext ; gettext.install('foo')", store_history=True) + ip.run_cell('3+3', store_history=True) + + with AssertPrints('gettext'): + ip.run_cell('print(_)', store_history=True) + + ip.run_cell('_ = "userset"', store_history=True) + + with AssertPrints('userset'): + ip.run_cell('print(_)', store_history=True) + ip.run_cell('import builtins; del builtins._') + + +def test_interactivehooks_ast_modes(): + """ + Test that ast nodes can be triggered with different modes + """ + saved_mode = ip.ast_node_interactivity + ip.ast_node_interactivity = 'last_expr_or_assign' + + try: + with AssertPrints('2'): + ip.run_cell('a = 1+1', store_history=True) + + with AssertPrints('9'): + ip.run_cell('b = 1+8 # comment with a semicolon;', store_history=False) + + with AssertPrints('7'): + ip.run_cell('c = 1+6\n#commented_out_function();', store_history=True) + + ip.run_cell('d = 11', store_history=True) + with AssertPrints('12'): + ip.run_cell('d += 1', store_history=True) + + with AssertNotPrints('42'): + ip.run_cell('(u,v) = (41+1, 43-1)') + + finally: + ip.ast_node_interactivity = saved_mode + +def test_interactivehooks_ast_modes_semi_suppress(): + """ + Test that ast nodes can be triggered with different modes and suppressed + by semicolon + """ + saved_mode = ip.ast_node_interactivity + ip.ast_node_interactivity = 'last_expr_or_assign' + + try: + with AssertNotPrints('2'): + ip.run_cell('x = 1+1;', store_history=True) + + with AssertNotPrints('7'): + ip.run_cell('y = 1+6; # comment with a semicolon', store_history=True) + + with AssertNotPrints('9'): + ip.run_cell('z = 1+8;\n#commented_out_function()', store_history=True) + + finally: + ip.ast_node_interactivity = saved_mode + +def test_capture_display_hook_format(): + """Tests that the capture display hook conforms to the CapturedIO output format""" + hook = CapturingDisplayHook(ip) + hook({"foo": "bar"}) + captured = CapturedIO(sys.stdout, sys.stderr, hook.outputs) + # Should not raise with RichOutput transformation error + captured.outputs diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_events.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_events.py new file mode 100644 index 00000000..61bc01d4 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_events.py @@ -0,0 +1,78 @@ +import unittest +from unittest.mock import Mock + +from IPython.core import events +import IPython.testing.tools as tt + + +@events._define_event +def ping_received(): + pass + + +@events._define_event +def event_with_argument(argument): + pass + + +class CallbackTests(unittest.TestCase): + def setUp(self): + self.em = events.EventManager(get_ipython(), + {'ping_received': ping_received, + 'event_with_argument': event_with_argument}) + + def test_register_unregister(self): + cb = Mock() + + self.em.register('ping_received', cb) + self.em.trigger('ping_received') + self.assertEqual(cb.call_count, 1) + + self.em.unregister('ping_received', cb) + self.em.trigger('ping_received') + self.assertEqual(cb.call_count, 1) + + def test_bare_function_missed_unregister(self): + def cb1(): + ... + + def cb2(): + ... + + self.em.register("ping_received", cb1) + self.assertRaises(ValueError, self.em.unregister, "ping_received", cb2) + self.em.unregister("ping_received", cb1) + + def test_cb_error(self): + cb = Mock(side_effect=ValueError) + self.em.register('ping_received', cb) + with tt.AssertPrints("Error in callback"): + self.em.trigger('ping_received') + + def test_cb_keyboard_interrupt(self): + cb = Mock(side_effect=KeyboardInterrupt) + self.em.register('ping_received', cb) + with tt.AssertPrints("Error in callback"): + self.em.trigger('ping_received') + + def test_unregister_during_callback(self): + invoked = [False] * 3 + + def func1(*_): + invoked[0] = True + self.em.unregister('ping_received', func1) + self.em.register('ping_received', func3) + + def func2(*_): + invoked[1] = True + self.em.unregister('ping_received', func2) + + def func3(*_): + invoked[2] = True + + self.em.register('ping_received', func1) + self.em.register('ping_received', func2) + + self.em.trigger('ping_received') + self.assertEqual([True, True, False], invoked) + self.assertEqual([func3], self.em.callbacks['ping_received']) diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_exceptiongroup_tb.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_exceptiongroup_tb.py new file mode 100644 index 00000000..c8e8002e --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_exceptiongroup_tb.py @@ -0,0 +1,112 @@ +import unittest +import re +from IPython.utils.capture import capture_output +import sys +import pytest +from tempfile import TemporaryDirectory +from IPython.testing import tools as tt + + +def _exceptiongroup_common( + outer_chain: str, + inner_chain: str, + native: bool, +) -> None: + pre_raise = "exceptiongroup." if not native else "" + pre_catch = pre_raise if sys.version_info < (3, 11) else "" + filestr = f""" + {"import exceptiongroup" if not native else ""} + import pytest + + def f(): raise ValueError("From f()") + def g(): raise BaseException("From g()") + + def inner(inner_chain): + excs = [] + for callback in [f, g]: + try: + callback() + except BaseException as err: + excs.append(err) + if excs: + if inner_chain == "none": + raise {pre_raise}BaseExceptionGroup("Oops", excs) + try: + raise SyntaxError() + except SyntaxError as e: + if inner_chain == "from": + raise {pre_raise}BaseExceptionGroup("Oops", excs) from e + else: + raise {pre_raise}BaseExceptionGroup("Oops", excs) + + def outer(outer_chain, inner_chain): + try: + inner(inner_chain) + except {pre_catch}BaseExceptionGroup as e: + if outer_chain == "none": + raise + if outer_chain == "from": + raise IndexError() from e + else: + raise IndexError + + + outer("{outer_chain}", "{inner_chain}") + """ + with capture_output() as cap: + ip.run_cell(filestr) + + match_lines = [] + if inner_chain == "another": + match_lines += [ + "During handling of the above exception, another exception occurred:", + ] + elif inner_chain == "from": + match_lines += [ + "The above exception was the direct cause of the following exception:", + ] + + match_lines += [ + " + Exception Group Traceback (most recent call last):", + f" | {pre_catch}BaseExceptionGroup: Oops (2 sub-exceptions)", + " | ValueError: From f()", + " | BaseException: From g()", + ] + + if outer_chain == "another": + match_lines += [ + "During handling of the above exception, another exception occurred:", + "IndexError", + ] + elif outer_chain == "from": + match_lines += [ + "The above exception was the direct cause of the following exception:", + "IndexError", + ] + + error_lines = cap.stderr.split("\n") + + err_index = match_index = 0 + for expected in match_lines: + for i, actual in enumerate(error_lines): + if actual == expected: + error_lines = error_lines[i + 1 :] + break + else: + assert False, f"{expected} not found in cap.stderr" + + +@pytest.mark.skipif( + sys.version_info < (3, 11), reason="Native ExceptionGroup not implemented" +) +@pytest.mark.parametrize("outer_chain", ["none", "from", "another"]) +@pytest.mark.parametrize("inner_chain", ["none", "from", "another"]) +def test_native_exceptiongroup(outer_chain, inner_chain) -> None: + _exceptiongroup_common(outer_chain, inner_chain, native=True) + + +@pytest.mark.parametrize("outer_chain", ["none", "from", "another"]) +@pytest.mark.parametrize("inner_chain", ["none", "from", "another"]) +def test_native_exceptiongroup(outer_chain, inner_chain) -> None: + pytest.importorskip("exceptiongroup") + _exceptiongroup_common(outer_chain, inner_chain, native=False) diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_extension.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_extension.py new file mode 100644 index 00000000..24ecf7e9 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_extension.py @@ -0,0 +1,95 @@ +import os.path + +from tempfile import TemporaryDirectory + +import IPython.testing.tools as tt +from IPython.utils.syspathcontext import prepended_to_syspath + +ext1_content = """ +def load_ipython_extension(ip): + print("Running ext1 load") + +def unload_ipython_extension(ip): + print("Running ext1 unload") +""" + +ext2_content = """ +def load_ipython_extension(ip): + print("Running ext2 load") +""" + +ext3_content = """ +def load_ipython_extension(ip): + ip2 = get_ipython() + print(ip is ip2) +""" + +def test_extension_loading(): + em = get_ipython().extension_manager + with TemporaryDirectory() as td: + ext1 = os.path.join(td, "ext1.py") + with open(ext1, "w", encoding="utf-8") as f: + f.write(ext1_content) + + ext2 = os.path.join(td, "ext2.py") + with open(ext2, "w", encoding="utf-8") as f: + f.write(ext2_content) + + with prepended_to_syspath(td): + assert 'ext1' not in em.loaded + assert 'ext2' not in em.loaded + + # Load extension + with tt.AssertPrints("Running ext1 load"): + assert em.load_extension('ext1') is None + assert 'ext1' in em.loaded + + # Should refuse to load it again + with tt.AssertNotPrints("Running ext1 load"): + assert em.load_extension('ext1') == 'already loaded' + + # Reload + with tt.AssertPrints("Running ext1 unload"): + with tt.AssertPrints("Running ext1 load", suppress=False): + em.reload_extension('ext1') + + # Unload + with tt.AssertPrints("Running ext1 unload"): + assert em.unload_extension('ext1') is None + + # Can't unload again + with tt.AssertNotPrints("Running ext1 unload"): + assert em.unload_extension('ext1') == 'not loaded' + assert em.unload_extension('ext2') == 'not loaded' + + # Load extension 2 + with tt.AssertPrints("Running ext2 load"): + assert em.load_extension('ext2') is None + + # Can't unload this + assert em.unload_extension('ext2') == 'no unload function' + + # But can reload it + with tt.AssertPrints("Running ext2 load"): + em.reload_extension('ext2') + + +def test_extension_builtins(): + em = get_ipython().extension_manager + with TemporaryDirectory() as td: + ext3 = os.path.join(td, "ext3.py") + with open(ext3, "w", encoding="utf-8") as f: + f.write(ext3_content) + + assert 'ext3' not in em.loaded + + with prepended_to_syspath(td): + # Load extension + with tt.AssertPrints("True"): + assert em.load_extension('ext3') is None + assert 'ext3' in em.loaded + + +def test_non_extension(): + em = get_ipython().extension_manager + assert em.load_extension("sys") == "no load function" diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_formatters.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_formatters.py new file mode 100644 index 00000000..5f8eeda2 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_formatters.py @@ -0,0 +1,545 @@ +"""Tests for the Formatters.""" + +from math import pi + +try: + import numpy +except: + numpy = None +import pytest + +from IPython import get_ipython +from traitlets.config import Config +from IPython.core.formatters import ( + PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key, + DisplayFormatter, JSONFormatter, +) +from IPython.utils.io import capture_output + +class A(object): + def __repr__(self): + return 'A()' + +class B(A): + def __repr__(self): + return 'B()' + +class C: + pass + +class BadRepr(object): + def __repr__(self): + raise ValueError("bad repr") + +class BadPretty(object): + _repr_pretty_ = None + +class GoodPretty(object): + def _repr_pretty_(self, pp, cycle): + pp.text('foo') + + def __repr__(self): + return 'GoodPretty()' + +def foo_printer(obj, pp, cycle): + pp.text('foo') + +def test_pretty(): + f = PlainTextFormatter() + f.for_type(A, foo_printer) + assert f(A()) == "foo" + assert f(B()) == "B()" + assert f(GoodPretty()) == "foo" + # Just don't raise an exception for the following: + f(BadPretty()) + + f.pprint = False + assert f(A()) == "A()" + assert f(B()) == "B()" + assert f(GoodPretty()) == "GoodPretty()" + + +def test_deferred(): + f = PlainTextFormatter() + +def test_precision(): + """test various values for float_precision.""" + f = PlainTextFormatter() + assert f(pi) == repr(pi) + f.float_precision = 0 + if numpy: + po = numpy.get_printoptions() + assert po["precision"] == 0 + assert f(pi) == "3" + f.float_precision = 2 + if numpy: + po = numpy.get_printoptions() + assert po["precision"] == 2 + assert f(pi) == "3.14" + f.float_precision = "%g" + if numpy: + po = numpy.get_printoptions() + assert po["precision"] == 2 + assert f(pi) == "3.14159" + f.float_precision = "%e" + assert f(pi) == "3.141593e+00" + f.float_precision = "" + if numpy: + po = numpy.get_printoptions() + assert po["precision"] == 8 + assert f(pi) == repr(pi) + + +def test_bad_precision(): + """test various invalid values for float_precision.""" + f = PlainTextFormatter() + def set_fp(p): + f.float_precision = p + + pytest.raises(ValueError, set_fp, "%") + pytest.raises(ValueError, set_fp, "%.3f%i") + pytest.raises(ValueError, set_fp, "foo") + pytest.raises(ValueError, set_fp, -1) + +def test_for_type(): + f = PlainTextFormatter() + + # initial return, None + assert f.for_type(C, foo_printer) is None + # no func queries + assert f.for_type(C) is foo_printer + # shouldn't change anything + assert f.for_type(C) is foo_printer + # None should do the same + assert f.for_type(C, None) is foo_printer + assert f.for_type(C, None) is foo_printer + +def test_for_type_string(): + f = PlainTextFormatter() + + type_str = '%s.%s' % (C.__module__, 'C') + + # initial return, None + assert f.for_type(type_str, foo_printer) is None + # no func queries + assert f.for_type(type_str) is foo_printer + assert _mod_name_key(C) in f.deferred_printers + assert f.for_type(C) is foo_printer + assert _mod_name_key(C) not in f.deferred_printers + assert C in f.type_printers + +def test_for_type_by_name(): + f = PlainTextFormatter() + + mod = C.__module__ + + # initial return, None + assert f.for_type_by_name(mod, "C", foo_printer) is None + # no func queries + assert f.for_type_by_name(mod, "C") is foo_printer + # shouldn't change anything + assert f.for_type_by_name(mod, "C") is foo_printer + # None should do the same + assert f.for_type_by_name(mod, "C", None) is foo_printer + assert f.for_type_by_name(mod, "C", None) is foo_printer + + +def test_lookup(): + f = PlainTextFormatter() + + f.for_type(C, foo_printer) + assert f.lookup(C()) is foo_printer + with pytest.raises(KeyError): + f.lookup(A()) + +def test_lookup_string(): + f = PlainTextFormatter() + type_str = '%s.%s' % (C.__module__, 'C') + + f.for_type(type_str, foo_printer) + assert f.lookup(C()) is foo_printer + # should move from deferred to imported dict + assert _mod_name_key(C) not in f.deferred_printers + assert C in f.type_printers + +def test_lookup_by_type(): + f = PlainTextFormatter() + f.for_type(C, foo_printer) + assert f.lookup_by_type(C) is foo_printer + with pytest.raises(KeyError): + f.lookup_by_type(A) + +def test_lookup_by_type_string(): + f = PlainTextFormatter() + type_str = '%s.%s' % (C.__module__, 'C') + f.for_type(type_str, foo_printer) + + # verify insertion + assert _mod_name_key(C) in f.deferred_printers + assert C not in f.type_printers + + assert f.lookup_by_type(type_str) is foo_printer + # lookup by string doesn't cause import + assert _mod_name_key(C) in f.deferred_printers + assert C not in f.type_printers + + assert f.lookup_by_type(C) is foo_printer + # should move from deferred to imported dict + assert _mod_name_key(C) not in f.deferred_printers + assert C in f.type_printers + +def test_in_formatter(): + f = PlainTextFormatter() + f.for_type(C, foo_printer) + type_str = '%s.%s' % (C.__module__, 'C') + assert C in f + assert type_str in f + +def test_string_in_formatter(): + f = PlainTextFormatter() + type_str = '%s.%s' % (C.__module__, 'C') + f.for_type(type_str, foo_printer) + assert type_str in f + assert C in f + +def test_pop(): + f = PlainTextFormatter() + f.for_type(C, foo_printer) + assert f.lookup_by_type(C) is foo_printer + assert f.pop(C, None) is foo_printer + f.for_type(C, foo_printer) + assert f.pop(C) is foo_printer + with pytest.raises(KeyError): + f.lookup_by_type(C) + with pytest.raises(KeyError): + f.pop(C) + with pytest.raises(KeyError): + f.pop(A) + assert f.pop(A, None) is None + +def test_pop_string(): + f = PlainTextFormatter() + type_str = '%s.%s' % (C.__module__, 'C') + + with pytest.raises(KeyError): + f.pop(type_str) + + f.for_type(type_str, foo_printer) + f.pop(type_str) + with pytest.raises(KeyError): + f.lookup_by_type(C) + with pytest.raises(KeyError): + f.pop(type_str) + + f.for_type(C, foo_printer) + assert f.pop(type_str, None) is foo_printer + with pytest.raises(KeyError): + f.lookup_by_type(C) + with pytest.raises(KeyError): + f.pop(type_str) + assert f.pop(type_str, None) is None + + +def test_error_method(): + f = HTMLFormatter() + class BadHTML(object): + def _repr_html_(self): + raise ValueError("Bad HTML") + bad = BadHTML() + with capture_output() as captured: + result = f(bad) + assert result is None + assert "Traceback" in captured.stdout + assert "Bad HTML" in captured.stdout + assert "_repr_html_" in captured.stdout + +def test_nowarn_notimplemented(): + f = HTMLFormatter() + class HTMLNotImplemented(object): + def _repr_html_(self): + raise NotImplementedError + h = HTMLNotImplemented() + with capture_output() as captured: + result = f(h) + assert result is None + assert "" == captured.stderr + assert "" == captured.stdout + + +def test_warn_error_for_type(): + f = HTMLFormatter() + f.for_type(int, lambda i: name_error) + with capture_output() as captured: + result = f(5) + assert result is None + assert "Traceback" in captured.stdout + assert "NameError" in captured.stdout + assert "name_error" in captured.stdout + +def test_error_pretty_method(): + f = PlainTextFormatter() + class BadPretty(object): + def _repr_pretty_(self): + return "hello" + bad = BadPretty() + with capture_output() as captured: + result = f(bad) + assert result is None + assert "Traceback" in captured.stdout + assert "_repr_pretty_" in captured.stdout + assert "given" in captured.stdout + assert "argument" in captured.stdout + + +def test_bad_repr_traceback(): + f = PlainTextFormatter() + bad = BadRepr() + with capture_output() as captured: + result = f(bad) + # catches error, returns None + assert result is None + assert "Traceback" in captured.stdout + assert "__repr__" in captured.stdout + assert "ValueError" in captured.stdout + + +class MakePDF(object): + def _repr_pdf_(self): + return 'PDF' + +def test_pdf_formatter(): + pdf = MakePDF() + f = PDFFormatter() + assert f(pdf) == "PDF" + + +def test_print_method_bound(): + f = HTMLFormatter() + class MyHTML(object): + def _repr_html_(self): + return "hello" + with capture_output() as captured: + result = f(MyHTML) + assert result is None + assert "FormatterWarning" not in captured.stderr + + with capture_output() as captured: + result = f(MyHTML()) + assert result == "hello" + assert captured.stderr == "" + + +def test_print_method_weird(): + + class TextMagicHat(object): + def __getattr__(self, key): + return key + + f = HTMLFormatter() + + text_hat = TextMagicHat() + assert text_hat._repr_html_ == "_repr_html_" + with capture_output() as captured: + result = f(text_hat) + + assert result is None + assert "FormatterWarning" not in captured.stderr + + class CallableMagicHat(object): + def __getattr__(self, key): + return lambda : key + + call_hat = CallableMagicHat() + with capture_output() as captured: + result = f(call_hat) + + assert result is None + + class BadReprArgs(object): + def _repr_html_(self, extra, args): + return "html" + + bad = BadReprArgs() + with capture_output() as captured: + result = f(bad) + + assert result is None + assert "FormatterWarning" not in captured.stderr + + +def test_format_config(): + """config objects don't pretend to support fancy reprs with lazy attrs""" + f = HTMLFormatter() + cfg = Config() + with capture_output() as captured: + result = f(cfg) + assert result is None + assert captured.stderr == "" + + with capture_output() as captured: + result = f(Config) + assert result is None + assert captured.stderr == "" + + +def test_pretty_max_seq_length(): + f = PlainTextFormatter(max_seq_length=1) + lis = list(range(3)) + text = f(lis) + assert text == "[0, ...]" + f.max_seq_length = 0 + text = f(lis) + assert text == "[0, 1, 2]" + text = f(list(range(1024))) + lines = text.splitlines() + assert len(lines) == 1024 + + +def test_ipython_display_formatter(): + """Objects with _ipython_display_ defined bypass other formatters""" + f = get_ipython().display_formatter + catcher = [] + class SelfDisplaying(object): + def _ipython_display_(self): + catcher.append(self) + + class NotSelfDisplaying(object): + def __repr__(self): + return "NotSelfDisplaying" + + def _ipython_display_(self): + raise NotImplementedError + + save_enabled = f.ipython_display_formatter.enabled + f.ipython_display_formatter.enabled = True + + yes = SelfDisplaying() + no = NotSelfDisplaying() + + d, md = f.format(no) + assert d == {"text/plain": repr(no)} + assert md == {} + assert catcher == [] + + d, md = f.format(yes) + assert d == {} + assert md == {} + assert catcher == [yes] + + f.ipython_display_formatter.enabled = save_enabled + + +def test_repr_mime(): + class HasReprMime(object): + def _repr_mimebundle_(self, include=None, exclude=None): + return { + 'application/json+test.v2': { + 'x': 'y' + }, + 'plain/text' : '', + 'image/png' : 'i-overwrite' + } + + def _repr_png_(self): + return 'should-be-overwritten' + def _repr_html_(self): + return 'hi!' + + f = get_ipython().display_formatter + html_f = f.formatters['text/html'] + save_enabled = html_f.enabled + html_f.enabled = True + obj = HasReprMime() + d, md = f.format(obj) + html_f.enabled = save_enabled + + assert sorted(d) == [ + "application/json+test.v2", + "image/png", + "plain/text", + "text/html", + "text/plain", + ] + assert md == {} + + d, md = f.format(obj, include={"image/png"}) + assert list(d.keys()) == [ + "image/png" + ], "Include should filter out even things from repr_mimebundle" + + assert d["image/png"] == "i-overwrite", "_repr_mimebundle_ take precedence" + + +def test_pass_correct_include_exclude(): + class Tester(object): + + def __init__(self, include=None, exclude=None): + self.include = include + self.exclude = exclude + + def _repr_mimebundle_(self, include, exclude, **kwargs): + if include and (include != self.include): + raise ValueError('include got modified: display() may be broken.') + if exclude and (exclude != self.exclude): + raise ValueError('exclude got modified: display() may be broken.') + + return None + + include = {'a', 'b', 'c'} + exclude = {'c', 'e' , 'f'} + + f = get_ipython().display_formatter + f.format(Tester(include=include, exclude=exclude), include=include, exclude=exclude) + f.format(Tester(exclude=exclude), exclude=exclude) + f.format(Tester(include=include), include=include) + + +def test_repr_mime_meta(): + class HasReprMimeMeta(object): + def _repr_mimebundle_(self, include=None, exclude=None): + data = { + 'image/png': 'base64-image-data', + } + metadata = { + 'image/png': { + 'width': 5, + 'height': 10, + } + } + return (data, metadata) + + f = get_ipython().display_formatter + obj = HasReprMimeMeta() + d, md = f.format(obj) + assert sorted(d) == ["image/png", "text/plain"] + assert md == { + "image/png": { + "width": 5, + "height": 10, + } + } + + +def test_repr_mime_failure(): + class BadReprMime(object): + def _repr_mimebundle_(self, include=None, exclude=None): + raise RuntimeError + + f = get_ipython().display_formatter + obj = BadReprMime() + d, md = f.format(obj) + assert "text/plain" in d + + +def test_custom_repr_namedtuple_partialmethod(): + from functools import partialmethod + from typing import NamedTuple + + class Foo(NamedTuple): ... + + Foo.__repr__ = partialmethod(lambda obj: "Hello World") + foo = Foo() + + f = PlainTextFormatter() + assert f.pprint + assert f(foo) == "Hello World" diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_guarded_eval.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_guarded_eval.py new file mode 100644 index 00000000..f9057d87 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_guarded_eval.py @@ -0,0 +1,785 @@ +import sys +from contextlib import contextmanager +from typing import ( + Annotated, + AnyStr, + NamedTuple, + Literal, + NewType, + Optional, + Protocol, + TypeGuard, + Union, + TypedDict, +) +from functools import partial +from IPython.core.guarded_eval import ( + EvaluationContext, + GuardRejection, + guarded_eval, + _unbind_method, +) +from IPython.testing import decorators as dec +import pytest + + +if sys.version_info < (3, 11): + from typing_extensions import Self, LiteralString +else: + from typing import Self, LiteralString + +if sys.version_info < (3, 12): + from typing_extensions import TypeAliasType +else: + from typing import TypeAliasType + + +def create_context(evaluation: str, **kwargs): + return EvaluationContext(locals=kwargs, globals={}, evaluation=evaluation) + + +forbidden = partial(create_context, "forbidden") +minimal = partial(create_context, "minimal") +limited = partial(create_context, "limited") +unsafe = partial(create_context, "unsafe") +dangerous = partial(create_context, "dangerous") + +LIMITED_OR_HIGHER = [limited, unsafe, dangerous] +MINIMAL_OR_HIGHER = [minimal, *LIMITED_OR_HIGHER] + + +@contextmanager +def module_not_installed(module: str): + import sys + + try: + to_restore = sys.modules[module] + del sys.modules[module] + except KeyError: + to_restore = None + try: + yield + finally: + sys.modules[module] = to_restore + + +def test_external_not_installed(): + """ + Because attribute check requires checking if object is not of allowed + external type, this tests logic for absence of external module. + """ + + class Custom: + def __init__(self): + self.test = 1 + + def __getattr__(self, key): + return key + + with module_not_installed("pandas"): + context = limited(x=Custom()) + with pytest.raises(GuardRejection): + guarded_eval("x.test", context) + + +@dec.skip_without("pandas") +def test_external_changed_api(monkeypatch): + """Check that the execution rejects if external API changed paths""" + import pandas as pd + + series = pd.Series([1], index=["a"]) + + with monkeypatch.context() as m: + m.delattr(pd, "Series") + context = limited(data=series) + with pytest.raises(GuardRejection): + guarded_eval("data.iloc[0]", context) + + +@dec.skip_without("pandas") +def test_pandas_series_iloc(): + import pandas as pd + + series = pd.Series([1], index=["a"]) + context = limited(data=series) + assert guarded_eval("data.iloc[0]", context) == 1 + + +def test_rejects_custom_properties(): + class BadProperty: + @property + def iloc(self): + return [None] + + series = BadProperty() + context = limited(data=series) + + with pytest.raises(GuardRejection): + guarded_eval("data.iloc[0]", context) + + +@dec.skip_without("pandas") +def test_accepts_non_overriden_properties(): + import pandas as pd + + class GoodProperty(pd.Series): + pass + + series = GoodProperty([1], index=["a"]) + context = limited(data=series) + + assert guarded_eval("data.iloc[0]", context) == 1 + + +@dec.skip_without("pandas") +def test_pandas_series(): + import pandas as pd + + context = limited(data=pd.Series([1], index=["a"])) + assert guarded_eval('data["a"]', context) == 1 + with pytest.raises(KeyError): + guarded_eval('data["c"]', context) + + +@dec.skip_without("pandas") +def test_pandas_bad_series(): + import pandas as pd + + class BadItemSeries(pd.Series): + def __getitem__(self, key): + return "CUSTOM_ITEM" + + class BadAttrSeries(pd.Series): + def __getattr__(self, key): + return "CUSTOM_ATTR" + + bad_series = BadItemSeries([1], index=["a"]) + context = limited(data=bad_series) + + with pytest.raises(GuardRejection): + guarded_eval('data["a"]', context) + with pytest.raises(GuardRejection): + guarded_eval('data["c"]', context) + + # note: here result is a bit unexpected because + # pandas `__getattr__` calls `__getitem__`; + # FIXME - special case to handle it? + assert guarded_eval("data.a", context) == "CUSTOM_ITEM" + + context = unsafe(data=bad_series) + assert guarded_eval('data["a"]', context) == "CUSTOM_ITEM" + + bad_attr_series = BadAttrSeries([1], index=["a"]) + context = limited(data=bad_attr_series) + assert guarded_eval('data["a"]', context) == 1 + with pytest.raises(GuardRejection): + guarded_eval("data.a", context) + + +@dec.skip_without("pandas") +def test_pandas_dataframe_loc(): + import pandas as pd + from pandas.testing import assert_series_equal + + data = pd.DataFrame([{"a": 1}]) + context = limited(data=data) + assert_series_equal(guarded_eval('data.loc[:, "a"]', context), data["a"]) + + +def test_named_tuple(): + class GoodNamedTuple(NamedTuple): + a: str + pass + + class BadNamedTuple(NamedTuple): + a: str + + def __getitem__(self, key): + return None + + good = GoodNamedTuple(a="x") + bad = BadNamedTuple(a="x") + + context = limited(data=good) + assert guarded_eval("data[0]", context) == "x" + + context = limited(data=bad) + with pytest.raises(GuardRejection): + guarded_eval("data[0]", context) + + +def test_dict(): + context = limited(data={"a": 1, "b": {"x": 2}, ("x", "y"): 3}) + assert guarded_eval('data["a"]', context) == 1 + assert guarded_eval('data["b"]', context) == {"x": 2} + assert guarded_eval('data["b"]["x"]', context) == 2 + assert guarded_eval('data["x", "y"]', context) == 3 + + assert guarded_eval("data.keys", context) + + +def test_set(): + context = limited(data={"a", "b"}) + assert guarded_eval("data.difference", context) + + +def test_list(): + context = limited(data=[1, 2, 3]) + assert guarded_eval("data[1]", context) == 2 + assert guarded_eval("data.copy", context) + + +def test_dict_literal(): + context = limited() + assert guarded_eval("{}", context) == {} + assert guarded_eval('{"a": 1}', context) == {"a": 1} + + +def test_list_literal(): + context = limited() + assert guarded_eval("[]", context) == [] + assert guarded_eval('[1, "a"]', context) == [1, "a"] + + +def test_set_literal(): + context = limited() + assert guarded_eval("set()", context) == set() + assert guarded_eval('{"a"}', context) == {"a"} + + +def test_evaluates_if_expression(): + context = limited() + assert guarded_eval("2 if True else 3", context) == 2 + assert guarded_eval("4 if False else 5", context) == 5 + + +def test_object(): + obj = object() + context = limited(obj=obj) + assert guarded_eval("obj.__dir__", context) == obj.__dir__ + + +@pytest.mark.parametrize( + "code,expected", + [ + ["int.numerator", int.numerator], + ["float.is_integer", float.is_integer], + ["complex.real", complex.real], + ], +) +def test_number_attributes(code, expected): + assert guarded_eval(code, limited()) == expected + + +def test_method_descriptor(): + context = limited() + assert guarded_eval("list.copy.__name__", context) == "copy" + + +class HeapType: + pass + + +class CallCreatesHeapType: + def __call__(self) -> HeapType: + return HeapType() + + +class CallCreatesBuiltin: + def __call__(self) -> frozenset: + return frozenset() + + +class HasStaticMethod: + @staticmethod + def static_method() -> HeapType: + return HeapType() + + +class InitReturnsFrozenset: + def __new__(self) -> frozenset: # type:ignore[misc] + return frozenset() + + +class StringAnnotation: + def heap(self) -> "HeapType": + return HeapType() + + def copy(self) -> "StringAnnotation": + return StringAnnotation() + + +CustomIntType = NewType("CustomIntType", int) +CustomHeapType = NewType("CustomHeapType", HeapType) +IntTypeAlias = TypeAliasType("IntTypeAlias", int) +HeapTypeAlias = TypeAliasType("HeapTypeAlias", HeapType) + + +class TestProtocol(Protocol): + def test_method(self) -> bool: + pass + + +class TestProtocolImplementer(TestProtocol): + def test_method(self) -> bool: + return True + + +class Movie(TypedDict): + name: str + year: int + + +class SpecialTyping: + def custom_int_type(self) -> CustomIntType: + return CustomIntType(1) + + def custom_heap_type(self) -> CustomHeapType: + return CustomHeapType(HeapType()) + + # TODO: remove type:ignore comment once mypy + # supports explicit calls to `TypeAliasType`, see: + # https://github.com/python/mypy/issues/16614 + def int_type_alias(self) -> IntTypeAlias: # type:ignore[valid-type] + return 1 + + def heap_type_alias(self) -> HeapTypeAlias: # type:ignore[valid-type] + return 1 + + def literal(self) -> Literal[False]: + return False + + def literal_string(self) -> LiteralString: + return "test" + + def self(self) -> Self: + return self + + def any_str(self, x: AnyStr) -> AnyStr: + return x + + def annotated(self) -> Annotated[float, "positive number"]: + return 1 + + def annotated_self(self) -> Annotated[Self, "self with metadata"]: + self._metadata = "test" + return self + + def int_type_guard(self, x) -> TypeGuard[int]: + return isinstance(x, int) + + def optional_float(self) -> Optional[float]: + return 1.0 + + def union_str_and_int(self) -> Union[str, int]: + return "" + + def protocol(self) -> TestProtocol: + return TestProtocolImplementer() + + def typed_dict(self) -> Movie: + return {"name": "The Matrix", "year": 1999} + + +@pytest.mark.parametrize( + "data,code,expected,equality", + [ + [[1, 2, 3], "data.index(2)", 1, True], + [{"a": 1}, "data.keys().isdisjoint({})", True, True], + [StringAnnotation(), "data.heap()", HeapType, False], + [StringAnnotation(), "data.copy()", StringAnnotation, False], + # test cases for `__call__` + [CallCreatesHeapType(), "data()", HeapType, False], + [CallCreatesBuiltin(), "data()", frozenset, False], + # Test cases for `__init__` + [HeapType, "data()", HeapType, False], + [InitReturnsFrozenset, "data()", frozenset, False], + [HeapType(), "data.__class__()", HeapType, False], + # supported special cases for typing + [SpecialTyping(), "data.custom_int_type()", int, False], + [SpecialTyping(), "data.custom_heap_type()", HeapType, False], + [SpecialTyping(), "data.int_type_alias()", int, False], + [SpecialTyping(), "data.heap_type_alias()", HeapType, False], + [SpecialTyping(), "data.self()", SpecialTyping, False], + [SpecialTyping(), "data.literal()", False, True], + [SpecialTyping(), "data.literal_string()", str, False], + [SpecialTyping(), "data.any_str('a')", str, False], + [SpecialTyping(), "data.any_str(b'a')", bytes, False], + [SpecialTyping(), "data.annotated()", float, False], + [SpecialTyping(), "data.annotated_self()", SpecialTyping, False], + [SpecialTyping(), "data.int_type_guard()", int, False], + # test cases for static methods + [HasStaticMethod, "data.static_method()", HeapType, False], + ], +) +def test_evaluates_calls(data, code, expected, equality): + context = limited(data=data, HeapType=HeapType, StringAnnotation=StringAnnotation) + value = guarded_eval(code, context) + if equality: + assert value == expected + else: + assert isinstance(value, expected) + + +@pytest.mark.parametrize( + "data,code,expected_attributes", + [ + [SpecialTyping(), "data.optional_float()", ["is_integer"]], + [ + SpecialTyping(), + "data.union_str_and_int()", + ["capitalize", "as_integer_ratio"], + ], + [SpecialTyping(), "data.protocol()", ["test_method"]], + [SpecialTyping(), "data.typed_dict()", ["keys", "values", "items"]], + ], +) +def test_mocks_attributes_of_call_results(data, code, expected_attributes): + context = limited(data=data, HeapType=HeapType, StringAnnotation=StringAnnotation) + result = guarded_eval(code, context) + for attr in expected_attributes: + assert hasattr(result, attr) + assert attr in dir(result) + + +@pytest.mark.parametrize( + "data,code,expected_items", + [ + [SpecialTyping(), "data.typed_dict()", {"year": int, "name": str}], + ], +) +def test_mocks_items_of_call_results(data, code, expected_items): + context = limited(data=data, HeapType=HeapType, StringAnnotation=StringAnnotation) + result = guarded_eval(code, context) + ipython_keys = result._ipython_key_completions_() + for key, value in expected_items.items(): + assert isinstance(result[key], value) + assert key in ipython_keys + + +@pytest.mark.parametrize( + "data,bad", + [ + [[1, 2, 3], "data.append(4)"], + [{"a": 1}, "data.update()"], + ], +) +def test_rejects_calls_with_side_effects(data, bad): + context = limited(data=data) + + with pytest.raises(GuardRejection): + guarded_eval(bad, context) + + +@pytest.mark.parametrize( + "code,expected", + [ + ["(1\n+\n1)", 2], + ["list(range(10))[-1:]", [9]], + ["list(range(20))[3:-2:3]", [3, 6, 9, 12, 15]], + ], +) +@pytest.mark.parametrize("context", LIMITED_OR_HIGHER) +def test_evaluates_complex_cases(code, expected, context): + assert guarded_eval(code, context()) == expected + + +@pytest.mark.parametrize( + "code,expected", + [ + ["1", 1], + ["1.0", 1.0], + ["0xdeedbeef", 0xDEEDBEEF], + ["True", True], + ["None", None], + ["{}", {}], + ["[]", []], + ], +) +@pytest.mark.parametrize("context", MINIMAL_OR_HIGHER) +def test_evaluates_literals(code, expected, context): + assert guarded_eval(code, context()) == expected + + +@pytest.mark.parametrize( + "code,expected", + [ + ["-5", -5], + ["+5", +5], + ["~5", -6], + ], +) +@pytest.mark.parametrize("context", LIMITED_OR_HIGHER) +def test_evaluates_unary_operations(code, expected, context): + assert guarded_eval(code, context()) == expected + + +@pytest.mark.parametrize( + "code,expected", + [ + ["1 + 1", 2], + ["3 - 1", 2], + ["2 * 3", 6], + ["5 // 2", 2], + ["5 / 2", 2.5], + ["5**2", 25], + ["2 >> 1", 1], + ["2 << 1", 4], + ["1 | 2", 3], + ["1 & 1", 1], + ["1 & 2", 0], + ], +) +@pytest.mark.parametrize("context", LIMITED_OR_HIGHER) +def test_evaluates_binary_operations(code, expected, context): + assert guarded_eval(code, context()) == expected + + +@pytest.mark.parametrize( + "code,expected", + [ + ["2 > 1", True], + ["2 < 1", False], + ["2 <= 1", False], + ["2 <= 2", True], + ["1 >= 2", False], + ["2 >= 2", True], + ["2 == 2", True], + ["1 == 2", False], + ["1 != 2", True], + ["1 != 1", False], + ["1 < 4 < 3", False], + ["(1 < 4) < 3", True], + ["4 > 3 > 2 > 1", True], + ["4 > 3 > 2 > 9", False], + ["1 < 2 < 3 < 4", True], + ["9 < 2 < 3 < 4", False], + ["1 < 2 > 1 > 0 > -1 < 1", True], + ["1 in [1] in [[1]]", True], + ["1 in [1] in [[2]]", False], + ["1 in [1]", True], + ["0 in [1]", False], + ["1 not in [1]", False], + ["0 not in [1]", True], + ["True is True", True], + ["False is False", True], + ["True is False", False], + ["True is not True", False], + ["False is not True", True], + ], +) +@pytest.mark.parametrize("context", LIMITED_OR_HIGHER) +def test_evaluates_comparisons(code, expected, context): + assert guarded_eval(code, context()) == expected + + +def test_guards_comparisons(): + class GoodEq(int): + pass + + class BadEq(int): + def __eq__(self, other): + assert False + + context = limited(bad=BadEq(1), good=GoodEq(1)) + + with pytest.raises(GuardRejection): + guarded_eval("bad == 1", context) + + with pytest.raises(GuardRejection): + guarded_eval("bad != 1", context) + + with pytest.raises(GuardRejection): + guarded_eval("1 == bad", context) + + with pytest.raises(GuardRejection): + guarded_eval("1 != bad", context) + + assert guarded_eval("good == 1", context) is True + assert guarded_eval("good != 1", context) is False + assert guarded_eval("1 == good", context) is True + assert guarded_eval("1 != good", context) is False + + +def test_guards_unary_operations(): + class GoodOp(int): + pass + + class BadOpInv(int): + def __inv__(self, other): + assert False + + class BadOpInverse(int): + def __inv__(self, other): + assert False + + context = limited(good=GoodOp(1), bad1=BadOpInv(1), bad2=BadOpInverse(1)) + + with pytest.raises(GuardRejection): + guarded_eval("~bad1", context) + + with pytest.raises(GuardRejection): + guarded_eval("~bad2", context) + + +def test_guards_binary_operations(): + class GoodOp(int): + pass + + class BadOp(int): + def __add__(self, other): + assert False + + context = limited(good=GoodOp(1), bad=BadOp(1)) + + with pytest.raises(GuardRejection): + guarded_eval("1 + bad", context) + + with pytest.raises(GuardRejection): + guarded_eval("bad + 1", context) + + assert guarded_eval("good + 1", context) == 2 + assert guarded_eval("1 + good", context) == 2 + + +def test_guards_attributes(): + class GoodAttr(float): + pass + + class BadAttr1(float): + def __getattr__(self, key): + assert False + + class BadAttr2(float): + def __getattribute__(self, key): + assert False + + context = limited(good=GoodAttr(0.5), bad1=BadAttr1(0.5), bad2=BadAttr2(0.5)) + + with pytest.raises(GuardRejection): + guarded_eval("bad1.as_integer_ratio", context) + + with pytest.raises(GuardRejection): + guarded_eval("bad2.as_integer_ratio", context) + + assert guarded_eval("good.as_integer_ratio()", context) == (1, 2) + + +@pytest.mark.parametrize("context", MINIMAL_OR_HIGHER) +def test_access_builtins(context): + assert guarded_eval("round", context()) == round + + +def test_access_builtins_fails(): + context = limited() + with pytest.raises(NameError): + guarded_eval("this_is_not_builtin", context) + + +def test_rejects_forbidden(): + context = forbidden() + with pytest.raises(GuardRejection): + guarded_eval("1", context) + + +def test_guards_locals_and_globals(): + context = EvaluationContext( + locals={"local_a": "a"}, globals={"global_b": "b"}, evaluation="minimal" + ) + + with pytest.raises(GuardRejection): + guarded_eval("local_a", context) + + with pytest.raises(GuardRejection): + guarded_eval("global_b", context) + + +def test_access_locals_and_globals(): + context = EvaluationContext( + locals={"local_a": "a"}, globals={"global_b": "b"}, evaluation="limited" + ) + assert guarded_eval("local_a", context) == "a" + assert guarded_eval("global_b", context) == "b" + + +@pytest.mark.parametrize( + "code", + ["def func(): pass", "class C: pass", "x = 1", "x += 1", "del x", "import ast"], +) +@pytest.mark.parametrize("context", [minimal(), limited(), unsafe()]) +def test_rejects_side_effect_syntax(code, context): + with pytest.raises(SyntaxError): + guarded_eval(code, context) + + +def test_subscript(): + context = EvaluationContext( + locals={}, globals={}, evaluation="limited", in_subscript=True + ) + empty_slice = slice(None, None, None) + assert guarded_eval("", context) == tuple() + assert guarded_eval(":", context) == empty_slice + assert guarded_eval("1:2:3", context) == slice(1, 2, 3) + assert guarded_eval(':, "a"', context) == (empty_slice, "a") + + +def test_unbind_method(): + class X(list): + def index(self, k): + return "CUSTOM" + + x = X() + assert _unbind_method(x.index) is X.index + assert _unbind_method([].index) is list.index + assert _unbind_method(list.index) is None + + +def test_assumption_instance_attr_do_not_matter(): + """This is semi-specified in Python documentation. + + However, since the specification says 'not guaranteed + to work' rather than 'is forbidden to work', future + versions could invalidate this assumptions. This test + is meant to catch such a change if it ever comes true. + """ + + class T: + def __getitem__(self, k): + return "a" + + def __getattr__(self, k): + return "a" + + def f(self): + return "b" + + t = T() + t.__getitem__ = f + t.__getattr__ = f + assert t[1] == "a" + assert t[1] == "a" + + +def test_assumption_named_tuples_share_getitem(): + """Check assumption on named tuples sharing __getitem__""" + from typing import NamedTuple + + class A(NamedTuple): + pass + + class B(NamedTuple): + pass + + assert A.__getitem__ == B.__getitem__ + + +@dec.skip_without("numpy") +def test_module_access(): + import numpy + + context = limited(numpy=numpy) + assert guarded_eval("numpy.linalg.norm", context) == numpy.linalg.norm + + context = minimal(numpy=numpy) + with pytest.raises(GuardRejection): + guarded_eval("np.linalg.norm", context) diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_handlers.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_handlers.py new file mode 100644 index 00000000..905d9abe --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_handlers.py @@ -0,0 +1,75 @@ +"""Tests for input handlers. +""" +#----------------------------------------------------------------------------- +# Module imports +#----------------------------------------------------------------------------- + +# our own packages +from IPython.core import autocall +from IPython.testing import tools as tt +import pytest +from collections.abc import Callable + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + +# Test functions +#----------------------------------------------------------------------------- + +class CallableIndexable(object): + def __getitem__(self, idx): return True + def __call__(self, *args, **kws): return True + + +class Autocallable(autocall.IPyAutocall): + def __call__(self): + return "called" + + +@pytest.mark.parametrize( + "autocall, input, output", + [ + # For many of the below, we're also checking that leading whitespace + # turns off the esc char, which it should unless there is a continuation + # line. + ("1", '"no change"', '"no change"'), # normal + ("1", "lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), # magic + # Only explicit escapes or instances of IPyAutocallable should get + # expanded + ("0", 'len "abc"', 'len "abc"'), + ("0", "autocallable", "autocallable()"), + # Don't add extra brackets (gh-1117) + ("0", "autocallable()", "autocallable()"), + ("1", 'len "abc"', 'len("abc")'), + ("1", 'len "abc";', 'len("abc");'), # ; is special -- moves out of parens + # Autocall is turned off if first arg is [] and the object + # is both callable and indexable. Like so: + ("1", "len [1,2]", "len([1,2])"), # len doesn't support __getitem__... + ("1", "call_idx [1]", "call_idx [1]"), # call_idx *does*.. + ("1", "call_idx 1", "call_idx(1)"), + ("1", "len", "len"), # only at 2 does it auto-call on single args + ("2", 'len "abc"', 'len("abc")'), + ("2", 'len "abc";', 'len("abc");'), + ("2", "len [1,2]", "len([1,2])"), + ("2", "call_idx [1]", "call_idx [1]"), + ("2", "call_idx 1", "call_idx(1)"), + # T his is what's different: + ("2", "len", "len()"), # only at 2 does it auto-call on single args + ("0", "Callable[[int], None]", "Callable[[int], None]"), + ("1", "Callable[[int], None]", "Callable[[int], None]"), + ("1", "Callable[[int], None]", "Callable[[int], None]"), + ], +) +def test_handlers_I(autocall, input, output): + autocallable = Autocallable() + ip.user_ns["autocallable"] = autocallable + + call_idx = CallableIndexable() + ip.user_ns["call_idx"] = call_idx + + ip.user_ns["Callable"] = Callable + + ip.run_line_magic("autocall", autocall) + assert ip.prefilter_manager.prefilter_lines(input) == output + ip.run_line_magic("autocall", "1") diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_history.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_history.py new file mode 100644 index 00000000..fa64fe04 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_history.py @@ -0,0 +1,306 @@ +# coding: utf-8 +"""Tests for the IPython tab-completion machinery. +""" +#----------------------------------------------------------------------------- +# Module imports +#----------------------------------------------------------------------------- + +# stdlib +import io +import sys +import tempfile +from datetime import datetime +from pathlib import Path + +from tempfile import TemporaryDirectory +# our own packages +from traitlets.config.loader import Config + +from IPython.core.history import HistoryAccessor, HistoryManager, extract_hist_ranges + + +def test_proper_default_encoding(): + assert sys.getdefaultencoding() == "utf-8" + +def test_history(): + ip = get_ipython() + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + hist_manager_ori = ip.history_manager + hist_file = tmp_path / "history.sqlite" + try: + ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file) + hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"] + for i, h in enumerate(hist, start=1): + ip.history_manager.store_inputs(i, h) + + ip.history_manager.db_log_output = True + # Doesn't match the input, but we'll just check it's stored. + ip.history_manager.output_hist_reprs[3] = "spam" + ip.history_manager.store_output(3) + + assert ip.history_manager.input_hist_raw == [""] + hist + + # Detailed tests for _get_range_session + grs = ip.history_manager._get_range_session + assert list(grs(start=2, stop=-1)) == list(zip([0], [2], hist[1:-1])) + assert list(grs(start=-2)) == list(zip([0, 0], [2, 3], hist[-2:])) + assert list(grs(output=True)) == list( + zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, "spam"])) + ) + + # Check whether specifying a range beyond the end of the current + # session results in an error (gh-804) + ip.run_line_magic("hist", "2-500") + + # Check that we can write non-ascii characters to a file + ip.run_line_magic("hist", "-f %s" % (tmp_path / "test1")) + ip.run_line_magic("hist", "-pf %s" % (tmp_path / "test2")) + ip.run_line_magic("hist", "-nf %s" % (tmp_path / "test3")) + ip.run_line_magic("save", "%s 1-10" % (tmp_path / "test4")) + + # New session + ip.history_manager.reset() + newcmds = ["z=5", "class X(object):\n pass", "k='p'", "z=5"] + for i, cmd in enumerate(newcmds, start=1): + ip.history_manager.store_inputs(i, cmd) + gothist = ip.history_manager.get_range(start=1, stop=4) + assert list(gothist) == list(zip([0, 0, 0], [1, 2, 3], newcmds)) + # Previous session: + gothist = ip.history_manager.get_range(-1, 1, 4) + assert list(gothist) == list(zip([1, 1, 1], [1, 2, 3], hist)) + + newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)] + + # Check get_hist_tail + gothist = ip.history_manager.get_tail(5, output=True, + include_latest=True) + expected = [(1, 3, (hist[-1], "spam"))] \ + + [(s, n, (c, None)) for (s, n, c) in newhist] + assert list(gothist) == expected + + gothist = ip.history_manager.get_tail(2) + expected = newhist[-3:-1] + assert list(gothist) == expected + + # Check get_hist_search + + gothist = ip.history_manager.search("*test*") + assert list(gothist) == [(1, 2, hist[1])] + + gothist = ip.history_manager.search("*=*") + assert list(gothist) == [ + (1, 1, hist[0]), + (1, 2, hist[1]), + (1, 3, hist[2]), + newhist[0], + newhist[2], + newhist[3], + ] + + gothist = ip.history_manager.search("*=*", n=4) + assert list(gothist) == [ + (1, 3, hist[2]), + newhist[0], + newhist[2], + newhist[3], + ] + + gothist = ip.history_manager.search("*=*", unique=True) + assert list(gothist) == [ + (1, 1, hist[0]), + (1, 2, hist[1]), + (1, 3, hist[2]), + newhist[2], + newhist[3], + ] + + gothist = ip.history_manager.search("*=*", unique=True, n=3) + assert list(gothist) == [(1, 3, hist[2]), newhist[2], newhist[3]] + + gothist = ip.history_manager.search("b*", output=True) + assert list(gothist) == [(1, 3, (hist[2], "spam"))] + + # Cross testing: check that magic %save can get previous session. + testfilename = (tmp_path / "test.py").resolve() + ip.run_line_magic("save", str(testfilename) + " ~1/1-3") + with io.open(testfilename, encoding="utf-8") as testfile: + assert testfile.read() == "# coding: utf-8\n" + "\n".join(hist) + "\n" + + # Duplicate line numbers - check that it doesn't crash, and + # gets a new session + ip.history_manager.store_inputs(1, "rogue") + ip.history_manager.writeout_cache() + assert ip.history_manager.session_number == 3 + + # Check that session and line values are not just max values + sessid, lineno, entry = newhist[-1] + assert lineno > 1 + ip.history_manager.reset() + lineno = 1 + ip.history_manager.store_inputs(lineno, entry) + gothist = ip.history_manager.search("*=*", unique=True) + hist = list(gothist)[-1] + assert sessid < hist[0] + assert hist[1:] == (lineno, entry) + finally: + # Ensure saving thread is shut down before we try to clean up the files + ip.history_manager.save_thread.stop() + # Forcibly close database rather than relying on garbage collection + ip.history_manager.db.close() + # Restore history manager + ip.history_manager = hist_manager_ori + + +def test_extract_hist_ranges(): + instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5 ~10/" + expected = [(0, 1, 2), # 0 == current session + (2, 3, 4), + (-4, 5, 7), + (-4, 7, 10), + (-9, 2, None), # None == to end + (-8, 1, None), + (-7, 1, 6), + (-10, 1, None)] + actual = list(extract_hist_ranges(instr)) + assert actual == expected + + +def test_extract_hist_ranges_empty_str(): + instr = "" + expected = [(0, 1, None)] # 0 == current session, None == to end + actual = list(extract_hist_ranges(instr)) + assert actual == expected + + +def test_magic_rerun(): + """Simple test for %rerun (no args -> rerun last line)""" + ip = get_ipython() + ip.run_cell("a = 10", store_history=True) + ip.run_cell("a += 1", store_history=True) + assert ip.user_ns["a"] == 11 + ip.run_cell("%rerun", store_history=True) + assert ip.user_ns["a"] == 12 + +def test_timestamp_type(): + ip = get_ipython() + info = ip.history_manager.get_session_info() + assert isinstance(info[1], datetime) + +def test_hist_file_config(): + cfg = Config() + tfile = tempfile.NamedTemporaryFile(delete=False) + cfg.HistoryManager.hist_file = Path(tfile.name) + try: + hm = HistoryManager(shell=get_ipython(), config=cfg) + assert hm.hist_file == cfg.HistoryManager.hist_file + finally: + try: + Path(tfile.name).unlink() + except OSError: + # same catch as in testing.tools.TempFileMixin + # On Windows, even though we close the file, we still can't + # delete it. I have no clue why + pass + +def test_histmanager_disabled(): + """Ensure that disabling the history manager doesn't create a database.""" + cfg = Config() + cfg.HistoryAccessor.enabled = False + + ip = get_ipython() + with TemporaryDirectory() as tmpdir: + hist_manager_ori = ip.history_manager + hist_file = Path(tmpdir) / "history.sqlite" + cfg.HistoryManager.hist_file = hist_file + try: + ip.history_manager = HistoryManager(shell=ip, config=cfg) + hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"] + for i, h in enumerate(hist, start=1): + ip.history_manager.store_inputs(i, h) + assert ip.history_manager.input_hist_raw == [""] + hist + ip.history_manager.reset() + ip.history_manager.end_session() + finally: + ip.history_manager = hist_manager_ori + + # hist_file should not be created + assert hist_file.exists() is False + + +def test_get_tail_session_awareness(): + """Test .get_tail() is: + - session specific in HistoryManager + - session agnostic in HistoryAccessor + same for .get_last_session_id() + """ + ip = get_ipython() + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + hist_file = tmp_path / "history.sqlite" + get_source = lambda x: x[2] + hm1 = None + hm2 = None + ha = None + try: + # hm1 creates a new session and adds history entries, + # ha catches up + hm1 = HistoryManager(shell=ip, hist_file=hist_file) + hm1_last_sid = hm1.get_last_session_id + ha = HistoryAccessor(hist_file=hist_file) + ha_last_sid = ha.get_last_session_id + + hist1 = ["a=1", "b=1", "c=1"] + for i, h in enumerate(hist1 + [""], start=1): + hm1.store_inputs(i, h) + assert list(map(get_source, hm1.get_tail())) == hist1 + assert list(map(get_source, ha.get_tail())) == hist1 + sid1 = hm1_last_sid() + assert sid1 is not None + assert ha_last_sid() == sid1 + + # hm2 creates a new session and adds entries, + # ha catches up + hm2 = HistoryManager(shell=ip, hist_file=hist_file) + hm2_last_sid = hm2.get_last_session_id + + hist2 = ["a=2", "b=2", "c=2"] + for i, h in enumerate(hist2 + [""], start=1): + hm2.store_inputs(i, h) + tail = hm2.get_tail(n=3) + assert list(map(get_source, tail)) == hist2 + tail = ha.get_tail(n=3) + assert list(map(get_source, tail)) == hist2 + sid2 = hm2_last_sid() + assert sid2 is not None + assert ha_last_sid() == sid2 + assert sid2 != sid1 + + # but hm1 still maintains its point of reference + # and adding more entries to it doesn't change others + # immediate perspective + assert hm1_last_sid() == sid1 + tail = hm1.get_tail(n=3) + assert list(map(get_source, tail)) == hist1 + + hist3 = ["a=3", "b=3", "c=3"] + for i, h in enumerate(hist3 + [""], start=5): + hm1.store_inputs(i, h) + tail = hm1.get_tail(n=7) + assert list(map(get_source, tail)) == hist1 + [""] + hist3 + tail = hm2.get_tail(n=3) + assert list(map(get_source, tail)) == hist2 + tail = ha.get_tail(n=3) + assert list(map(get_source, tail)) == hist2 + assert hm1_last_sid() == sid1 + assert hm2_last_sid() == sid2 + assert ha_last_sid() == sid2 + finally: + if hm1: + hm1.save_thread.stop() + hm1.db.close() + if hm2: + hm2.save_thread.stop() + hm2.db.close() + if ha: + ha.db.close() diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_hooks.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_hooks.py new file mode 100644 index 00000000..6e0b1c15 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_hooks.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +"""Tests for CommandChainDispatcher.""" + + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import pytest +from IPython.core.error import TryNext +from IPython.core.hooks import CommandChainDispatcher + +#----------------------------------------------------------------------------- +# Local utilities +#----------------------------------------------------------------------------- + +# Define two classes, one which succeeds and one which raises TryNext. Each +# sets the attribute `called` to True when it is called. +class Okay(object): + def __init__(self, message): + self.message = message + self.called = False + def __call__(self): + self.called = True + return self.message + +class Fail(object): + def __init__(self, message): + self.message = message + self.called = False + def __call__(self): + self.called = True + raise TryNext(self.message) + +#----------------------------------------------------------------------------- +# Test functions +#----------------------------------------------------------------------------- + +def test_command_chain_dispatcher_ff(): + """Test two failing hooks""" + fail1 = Fail("fail1") + fail2 = Fail("fail2") + dp = CommandChainDispatcher([(0, fail1), (10, fail2)]) + + with pytest.raises(TryNext) as e: + dp() + assert str(e.value) == "fail2" + + assert fail1.called is True + assert fail2.called is True + +def test_command_chain_dispatcher_fofo(): + """Test a mixture of failing and succeeding hooks.""" + fail1 = Fail("fail1") + fail2 = Fail("fail2") + okay1 = Okay("okay1") + okay2 = Okay("okay2") + + dp = CommandChainDispatcher([(0, fail1), + # (5, okay1), # add this later + (10, fail2), + (15, okay2)]) + dp.add(okay1, 5) + + assert dp() == "okay1" + + assert fail1.called is True + assert okay1.called is True + assert fail2.called is False + assert okay2.called is False + +def test_command_chain_dispatcher_eq_priority(): + okay1 = Okay(u'okay1') + okay2 = Okay(u'okay2') + dp = CommandChainDispatcher([(1, okay1)]) + dp.add(okay2, 1) diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_imports.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_imports.py new file mode 100644 index 00000000..7aa278fb --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_imports.py @@ -0,0 +1,52 @@ +# encoding: utf-8 + +def test_import_completer(): + from IPython.core import completer + +def test_import_crashhandler(): + from IPython.core import crashhandler + +def test_import_debugger(): + from IPython.core import debugger + +def test_import_excolors(): + from IPython.core import excolors + +def test_import_history(): + from IPython.core import history + +def test_import_hooks(): + from IPython.core import hooks + +def test_import_getipython(): + from IPython.core import getipython + +def test_import_interactiveshell(): + from IPython.core import interactiveshell + +def test_import_logger(): + from IPython.core import logger + +def test_import_macro(): + from IPython.core import macro + +def test_import_magic(): + from IPython.core import magic + +def test_import_oinspect(): + from IPython.core import oinspect + +def test_import_prefilter(): + from IPython.core import prefilter + +def test_import_prompts(): + from IPython.core import prompts + +def test_import_release(): + from IPython.core import release + +def test_import_ultratb(): + from IPython.core import ultratb + +def test_import_usage(): + from IPython.core import usage diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputsplitter.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputsplitter.py new file mode 100644 index 00000000..61e06df3 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputsplitter.py @@ -0,0 +1,643 @@ +# -*- coding: utf-8 -*- +"""Tests for the inputsplitter module.""" + + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import unittest +import pytest +import sys + +with pytest.warns(DeprecationWarning, match="inputsplitter"): + from IPython.core import inputsplitter as isp +from IPython.core.inputtransformer import InputTransformer +from IPython.core.tests.test_inputtransformer import syntax, syntax_ml +from IPython.testing import tools as tt + +#----------------------------------------------------------------------------- +# Semi-complete examples (also used as tests) +#----------------------------------------------------------------------------- + +# Note: at the bottom, there's a slightly more complete version of this that +# can be useful during development of code here. + +def mini_interactive_loop(input_func): + """Minimal example of the logic of an interactive interpreter loop. + + This serves as an example, and it is used by the test system with a fake + raw_input that simulates interactive input.""" + + from IPython.core.inputsplitter import InputSplitter + + isp = InputSplitter() + # In practice, this input loop would be wrapped in an outside loop to read + # input indefinitely, until some exit/quit command was issued. Here we + # only illustrate the basic inner loop. + while isp.push_accepts_more(): + indent = ' '*isp.get_indent_spaces() + prompt = '>>> ' + indent + line = indent + input_func(prompt) + isp.push(line) + + # Here we just return input so we can use it in a test suite, but a real + # interpreter would instead send it for execution somewhere. + src = isp.source_reset() + # print('Input source was:\n', src) # dbg + return src + +#----------------------------------------------------------------------------- +# Test utilities, just for local use +#----------------------------------------------------------------------------- + + +def pseudo_input(lines): + """Return a function that acts like raw_input but feeds the input list.""" + ilines = iter(lines) + def raw_in(prompt): + try: + return next(ilines) + except StopIteration: + return '' + return raw_in + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- +def test_spaces(): + tests = [('', 0), + (' ', 1), + ('\n', 0), + (' \n', 1), + ('x', 0), + (' x', 1), + (' x',2), + (' x',4), + # Note: tabs are counted as a single whitespace! + ('\tx', 1), + ('\t x', 2), + ] + with pytest.warns(PendingDeprecationWarning): + tt.check_pairs(isp.num_ini_spaces, tests) + + +def test_remove_comments(): + tests = [('text', 'text'), + ('text # comment', 'text '), + ('text # comment\n', 'text \n'), + ('text # comment \n', 'text \n'), + ('line # c \nline\n','line \nline\n'), + ('line # c \nline#c2 \nline\nline #c\n\n', + 'line \nline\nline\nline \n\n'), + ] + tt.check_pairs(isp.remove_comments, tests) + + +def test_get_input_encoding(): + encoding = isp.get_input_encoding() + assert isinstance(encoding, str) + # simple-minded check that at least encoding a simple string works with the + # encoding we got. + assert "test".encode(encoding) == b"test" + + +class NoInputEncodingTestCase(unittest.TestCase): + def setUp(self): + self.old_stdin = sys.stdin + class X: pass + fake_stdin = X() + sys.stdin = fake_stdin + + def test(self): + # Verify that if sys.stdin has no 'encoding' attribute we do the right + # thing + enc = isp.get_input_encoding() + self.assertEqual(enc, 'ascii') + + def tearDown(self): + sys.stdin = self.old_stdin + + +class InputSplitterTestCase(unittest.TestCase): + def setUp(self): + self.isp = isp.InputSplitter() + + def test_reset(self): + isp = self.isp + isp.push('x=1') + isp.reset() + self.assertEqual(isp._buffer, []) + self.assertEqual(isp.get_indent_spaces(), 0) + self.assertEqual(isp.source, '') + self.assertEqual(isp.code, None) + self.assertEqual(isp._is_complete, False) + + def test_source(self): + self.isp._store('1') + self.isp._store('2') + self.assertEqual(self.isp.source, '1\n2\n') + self.assertEqual(len(self.isp._buffer)>0, True) + self.assertEqual(self.isp.source_reset(), '1\n2\n') + self.assertEqual(self.isp._buffer, []) + self.assertEqual(self.isp.source, '') + + def test_indent(self): + isp = self.isp # shorthand + isp.push('x=1') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n x=1') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('y=2\n') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_indent2(self): + isp = self.isp + isp.push('if 1:') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push(' x=1') + self.assertEqual(isp.get_indent_spaces(), 4) + # Blank lines shouldn't change the indent level + isp.push(' '*2) + self.assertEqual(isp.get_indent_spaces(), 4) + + def test_indent3(self): + isp = self.isp + # When a multiline statement contains parens or multiline strings, we + # shouldn't get confused. + isp.push("if 1:") + isp.push(" x = (1+\n 2)") + self.assertEqual(isp.get_indent_spaces(), 4) + + def test_indent4(self): + isp = self.isp + # whitespace after ':' should not screw up indent level + isp.push('if 1: \n x=1') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('y=2\n') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\t\n x=1') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('y=2\n') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_dedent_pass(self): + isp = self.isp # shorthand + # should NOT cause dedent + isp.push('if 1:\n passes = 5') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('if 1:\n pass') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n pass ') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_dedent_break(self): + isp = self.isp # shorthand + # should NOT cause dedent + isp.push('while 1:\n breaks = 5') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('while 1:\n break') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('while 1:\n break ') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_dedent_continue(self): + isp = self.isp # shorthand + # should NOT cause dedent + isp.push('while 1:\n continues = 5') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('while 1:\n continue') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('while 1:\n continue ') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_dedent_raise(self): + isp = self.isp # shorthand + # should NOT cause dedent + isp.push('if 1:\n raised = 4') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('if 1:\n raise TypeError()') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n raise') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n raise ') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_dedent_return(self): + isp = self.isp # shorthand + # should NOT cause dedent + isp.push('if 1:\n returning = 4') + self.assertEqual(isp.get_indent_spaces(), 4) + isp.push('if 1:\n return 5 + 493') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n return') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n return ') + self.assertEqual(isp.get_indent_spaces(), 0) + isp.push('if 1:\n return(0)') + self.assertEqual(isp.get_indent_spaces(), 0) + + def test_push(self): + isp = self.isp + self.assertEqual(isp.push('x=1'), True) + + def test_push2(self): + isp = self.isp + self.assertEqual(isp.push('if 1:'), False) + for line in [' x=1', '# a comment', ' y=2']: + print(line) + self.assertEqual(isp.push(line), True) + + def test_push3(self): + isp = self.isp + isp.push('if True:') + isp.push(' a = 1') + self.assertEqual(isp.push('b = [1,'), False) + + def test_push_accepts_more(self): + isp = self.isp + isp.push('x=1') + self.assertEqual(isp.push_accepts_more(), False) + + def test_push_accepts_more2(self): + isp = self.isp + isp.push('if 1:') + self.assertEqual(isp.push_accepts_more(), True) + isp.push(' x=1') + self.assertEqual(isp.push_accepts_more(), True) + isp.push('') + self.assertEqual(isp.push_accepts_more(), False) + + def test_push_accepts_more3(self): + isp = self.isp + isp.push("x = (2+\n3)") + self.assertEqual(isp.push_accepts_more(), False) + + def test_push_accepts_more4(self): + isp = self.isp + # When a multiline statement contains parens or multiline strings, we + # shouldn't get confused. + # FIXME: we should be able to better handle de-dents in statements like + # multiline strings and multiline expressions (continued with \ or + # parens). Right now we aren't handling the indentation tracking quite + # correctly with this, though in practice it may not be too much of a + # problem. We'll need to see. + isp.push("if 1:") + isp.push(" x = (2+") + isp.push(" 3)") + self.assertEqual(isp.push_accepts_more(), True) + isp.push(" y = 3") + self.assertEqual(isp.push_accepts_more(), True) + isp.push('') + self.assertEqual(isp.push_accepts_more(), False) + + def test_push_accepts_more5(self): + isp = self.isp + isp.push('try:') + isp.push(' a = 5') + isp.push('except:') + isp.push(' raise') + # We want to be able to add an else: block at this point, so it should + # wait for a blank line. + self.assertEqual(isp.push_accepts_more(), True) + + def test_continuation(self): + isp = self.isp + isp.push("import os, \\") + self.assertEqual(isp.push_accepts_more(), True) + isp.push("sys") + self.assertEqual(isp.push_accepts_more(), False) + + def test_syntax_error(self): + isp = self.isp + # Syntax errors immediately produce a 'ready' block, so the invalid + # Python can be sent to the kernel for evaluation with possible ipython + # special-syntax conversion. + isp.push('run foo') + self.assertEqual(isp.push_accepts_more(), False) + + def test_unicode(self): + self.isp.push(u"Pérez") + self.isp.push(u'\xc3\xa9') + self.isp.push(u"u'\xc3\xa9'") + + @pytest.mark.xfail( + reason="Bug in python 3.9.8 – bpo 45738", + condition=sys.version_info in [(3, 11, 0, "alpha", 2)], + raises=SystemError, + strict=True, + ) + def test_line_continuation(self): + """ Test issue #2108.""" + isp = self.isp + # A blank line after a line continuation should not accept more + isp.push("1 \\\n\n") + self.assertEqual(isp.push_accepts_more(), False) + # Whitespace after a \ is a SyntaxError. The only way to test that + # here is to test that push doesn't accept more (as with + # test_syntax_error() above). + isp.push(r"1 \ ") + self.assertEqual(isp.push_accepts_more(), False) + # Even if the line is continuable (c.f. the regular Python + # interpreter) + isp.push(r"(1 \ ") + self.assertEqual(isp.push_accepts_more(), False) + + def test_check_complete(self): + isp = self.isp + self.assertEqual(isp.check_complete("a = 1"), ('complete', None)) + self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4)) + self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None)) + self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0)) + self.assertEqual(isp.check_complete("def a():\n x=1\n global x"), ('invalid', None)) + +class InteractiveLoopTestCase(unittest.TestCase): + """Tests for an interactive loop like a python shell. + """ + def check_ns(self, lines, ns): + """Validate that the given input lines produce the resulting namespace. + + Note: the input lines are given exactly as they would be typed in an + auto-indenting environment, as mini_interactive_loop above already does + auto-indenting and prepends spaces to the input. + """ + src = mini_interactive_loop(pseudo_input(lines)) + test_ns = {} + exec(src, test_ns) + # We can't check that the provided ns is identical to the test_ns, + # because Python fills test_ns with extra keys (copyright, etc). But + # we can check that the given dict is *contained* in test_ns + for k,v in ns.items(): + self.assertEqual(test_ns[k], v) + + def test_simple(self): + self.check_ns(['x=1'], dict(x=1)) + + def test_simple2(self): + self.check_ns(['if 1:', 'x=2'], dict(x=2)) + + def test_xy(self): + self.check_ns(['x=1; y=2'], dict(x=1, y=2)) + + def test_abc(self): + self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3)) + + def test_multi(self): + self.check_ns(['x =(1+','1+','2)'], dict(x=4)) + + +class IPythonInputTestCase(InputSplitterTestCase): + """By just creating a new class whose .isp is a different instance, we + re-run the same test battery on the new input splitter. + + In addition, this runs the tests over the syntax and syntax_ml dicts that + were tested by individual functions, as part of the OO interface. + + It also makes some checks on the raw buffer storage. + """ + + def setUp(self): + self.isp = isp.IPythonInputSplitter() + + def test_syntax(self): + """Call all single-line syntax tests from the main object""" + isp = self.isp + for example in syntax.values(): + for raw, out_t in example: + if raw.startswith(' '): + continue + + isp.push(raw+'\n') + out_raw = isp.source_raw + out = isp.source_reset() + self.assertEqual(out.rstrip(), out_t, + tt.pair_fail_msg.format("inputsplitter",raw, out_t, out)) + self.assertEqual(out_raw.rstrip(), raw.rstrip()) + + def test_syntax_multiline(self): + isp = self.isp + for example in syntax_ml.values(): + for line_pairs in example: + out_t_parts = [] + raw_parts = [] + for lraw, out_t_part in line_pairs: + if out_t_part is not None: + out_t_parts.append(out_t_part) + + if lraw is not None: + isp.push(lraw) + raw_parts.append(lraw) + + out_raw = isp.source_raw + out = isp.source_reset() + out_t = '\n'.join(out_t_parts).rstrip() + raw = '\n'.join(raw_parts).rstrip() + self.assertEqual(out.rstrip(), out_t) + self.assertEqual(out_raw.rstrip(), raw) + + def test_syntax_multiline_cell(self): + isp = self.isp + for example in syntax_ml.values(): + + out_t_parts = [] + for line_pairs in example: + raw = '\n'.join(r for r, _ in line_pairs if r is not None) + out_t = '\n'.join(t for _,t in line_pairs if t is not None) + out = isp.transform_cell(raw) + # Match ignoring trailing whitespace + self.assertEqual(out.rstrip(), out_t.rstrip()) + + def test_cellmagic_preempt(self): + isp = self.isp + for raw, name, line, cell in [ + ("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'), + ("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'), + (">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'), + ("%%cellm \n>>> hi", u'cellm', u'', u'>>> hi'), + ("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'), + ("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'), + ]: + expected = "get_ipython().run_cell_magic(%r, %r, %r)" % ( + name, line, cell + ) + out = isp.transform_cell(raw) + self.assertEqual(out.rstrip(), expected.rstrip()) + + def test_multiline_passthrough(self): + isp = self.isp + class CommentTransformer(InputTransformer): + def __init__(self): + self._lines = [] + + def push(self, line): + self._lines.append(line + '#') + + def reset(self): + text = '\n'.join(self._lines) + self._lines = [] + return text + + isp.physical_line_transforms.insert(0, CommentTransformer()) + + for raw, expected in [ + ("a=5", "a=5#"), + ("%ls foo", "get_ipython().run_line_magic(%r, %r)" % (u'ls', u'foo#')), + ("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().run_line_magic(%r, %r)" % ( + u'ls foo#', u'ls', u'bar#' + )), + ("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().run_line_magic(%r, %r)\n4#\n5#" % (u'ls', u'foo#')), + ]: + out = isp.transform_cell(raw) + self.assertEqual(out.rstrip(), expected.rstrip()) + +#----------------------------------------------------------------------------- +# Main - use as a script, mostly for developer experiments +#----------------------------------------------------------------------------- + +if __name__ == '__main__': + # A simple demo for interactive experimentation. This code will not get + # picked up by any test suite. + from IPython.core.inputsplitter import IPythonInputSplitter + + # configure here the syntax to use, prompt and whether to autoindent + #isp, start_prompt = InputSplitter(), '>>> ' + isp, start_prompt = IPythonInputSplitter(), 'In> ' + + autoindent = True + #autoindent = False + + try: + while True: + prompt = start_prompt + while isp.push_accepts_more(): + indent = ' '*isp.get_indent_spaces() + if autoindent: + line = indent + input(prompt+indent) + else: + line = input(prompt) + isp.push(line) + prompt = '... ' + + # Here we just return input so we can use it in a test suite, but a + # real interpreter would instead send it for execution somewhere. + #src = isp.source; raise EOFError # dbg + raw = isp.source_raw + src = isp.source_reset() + print('Input source was:\n', src) + print('Raw source was:\n', raw) + except EOFError: + print('Bye') + +# Tests for cell magics support + +def test_last_blank(): + assert isp.last_blank("") is False + assert isp.last_blank("abc") is False + assert isp.last_blank("abc\n") is False + assert isp.last_blank("abc\na") is False + + assert isp.last_blank("\n") is True + assert isp.last_blank("\n ") is True + assert isp.last_blank("abc\n ") is True + assert isp.last_blank("abc\n\n") is True + assert isp.last_blank("abc\nd\n\n") is True + assert isp.last_blank("abc\nd\ne\n\n") is True + assert isp.last_blank("abc \n \n \n\n") is True + + +def test_last_two_blanks(): + assert isp.last_two_blanks("") is False + assert isp.last_two_blanks("abc") is False + assert isp.last_two_blanks("abc\n") is False + assert isp.last_two_blanks("abc\n\na") is False + assert isp.last_two_blanks("abc\n \n") is False + assert isp.last_two_blanks("abc\n\n") is False + + assert isp.last_two_blanks("\n\n") is True + assert isp.last_two_blanks("\n\n ") is True + assert isp.last_two_blanks("\n \n") is True + assert isp.last_two_blanks("abc\n\n ") is True + assert isp.last_two_blanks("abc\n\n\n") is True + assert isp.last_two_blanks("abc\n\n \n") is True + assert isp.last_two_blanks("abc\n\n \n ") is True + assert isp.last_two_blanks("abc\n\n \n \n") is True + assert isp.last_two_blanks("abc\nd\n\n\n") is True + assert isp.last_two_blanks("abc\nd\ne\nf\n\n\n") is True + + +class CellMagicsCommon(object): + + def test_whole_cell(self): + src = "%%cellm line\nbody\n" + out = self.sp.transform_cell(src) + ref = "get_ipython().run_cell_magic('cellm', 'line', 'body')\n" + assert out == ref + + def test_cellmagic_help(self): + self.sp.push('%%cellm?') + assert self.sp.push_accepts_more() is False + + def tearDown(self): + self.sp.reset() + + +class CellModeCellMagics(CellMagicsCommon, unittest.TestCase): + sp = isp.IPythonInputSplitter(line_input_checker=False) + + def test_incremental(self): + sp = self.sp + sp.push("%%cellm firstline\n") + assert sp.push_accepts_more() is True # 1 + sp.push("line2\n") + assert sp.push_accepts_more() is True # 2 + sp.push("\n") + # This should accept a blank line and carry on until the cell is reset + assert sp.push_accepts_more() is True # 3 + + def test_no_strip_coding(self): + src = '\n'.join([ + '%%writefile foo.py', + '# coding: utf-8', + 'print(u"üñîçø∂é")', + ]) + out = self.sp.transform_cell(src) + assert "# coding: utf-8" in out + + +class LineModeCellMagics(CellMagicsCommon, unittest.TestCase): + sp = isp.IPythonInputSplitter(line_input_checker=True) + + def test_incremental(self): + sp = self.sp + sp.push("%%cellm line2\n") + assert sp.push_accepts_more() is True # 1 + sp.push("\n") + # In this case, a blank line should end the cell magic + assert sp.push_accepts_more() is False # 2 + + +indentation_samples = [ + ('a = 1', 0), + ('for a in b:', 4), + ('def f():', 4), + ('def f(): #comment', 4), + ('a = ":#not a comment"', 0), + ('def f():\n a = 1', 4), + ('def f():\n return 1', 0), + ('for a in b:\n' + ' if a < 0:' + ' continue', 3), + ('a = {', 4), + ('a = {\n' + ' 1,', 5), + ('b = """123', 0), + ('', 0), + ('def f():\n pass', 0), + ('class Bar:\n def f():\n pass', 4), + ('class Bar:\n def f():\n raise', 4), +] + +def test_find_next_indent(): + for code, exp in indentation_samples: + res = isp.find_next_indent(code) + msg = "{!r} != {!r} (expected)\n Code: {!r}".format(res, exp, code) + assert res == exp, msg diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputtransformer.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputtransformer.py new file mode 100644 index 00000000..bfc936d3 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputtransformer.py @@ -0,0 +1,469 @@ +import tokenize + +from IPython.testing import tools as tt + +from IPython.core import inputtransformer as ipt + +def transform_and_reset(transformer): + transformer = transformer() + def transform(inp): + try: + return transformer.push(inp) + finally: + transformer.reset() + + return transform + +# Transformer tests +def transform_checker(tests, transformer, **kwargs): + """Utility to loop over test inputs""" + transformer = transformer(**kwargs) + try: + for inp, tr in tests: + if inp is None: + out = transformer.reset() + else: + out = transformer.push(inp) + assert out == tr + finally: + transformer.reset() + +# Data for all the syntax tests in the form of lists of pairs of +# raw/transformed input. We store it here as a global dict so that we can use +# it both within single-function tests and also to validate the behavior of the +# larger objects + +syntax = \ + dict(assign_system = + [('a =! ls', "a = get_ipython().getoutput('ls')"), + ('b = !ls', "b = get_ipython().getoutput('ls')"), + ('c= !ls', "c = get_ipython().getoutput('ls')"), + ('d == !ls', 'd == !ls'), # Invalid syntax, but we leave == alone. + ('x=1', 'x=1'), # normal input is unmodified + (' ',' '), # blank lines are kept intact + # Tuple unpacking + ("a, b = !echo 'a\\nb'", "a, b = get_ipython().getoutput(\"echo 'a\\\\nb'\")"), + ("a,= !echo 'a'", "a, = get_ipython().getoutput(\"echo 'a'\")"), + ("a, *bc = !echo 'a\\nb\\nc'", "a, *bc = get_ipython().getoutput(\"echo 'a\\\\nb\\\\nc'\")"), + # Tuple unpacking with regular Python expressions, not our syntax. + ("a, b = range(2)", "a, b = range(2)"), + ("a, = range(1)", "a, = range(1)"), + ("a, *bc = range(3)", "a, *bc = range(3)"), + ], + + assign_magic = + [('a =% who', "a = get_ipython().run_line_magic('who', '')"), + ('b = %who', "b = get_ipython().run_line_magic('who', '')"), + ('c= %ls', "c = get_ipython().run_line_magic('ls', '')"), + ('d == %ls', 'd == %ls'), # Invalid syntax, but we leave == alone. + ('x=1', 'x=1'), # normal input is unmodified + (' ',' '), # blank lines are kept intact + ("a, b = %foo", "a, b = get_ipython().run_line_magic('foo', '')"), + ], + classic_prompt=[ + (">>> x=1", "x=1"), + ("x=1", "x=1"), # normal input is unmodified + (" ", " "), # blank lines are kept intact + ], + ipy_prompt=[ + ("In [1]: x=1", "x=1"), + ("x=1", "x=1"), # normal input is unmodified + (" ", " "), # blank lines are kept intact + ], + # Tests for the escape transformer to leave normal code alone + escaped_noesc=[ + (" ", " "), + ("x=1", "x=1"), + ], + # System calls + escaped_shell=[ + ("!ls", "get_ipython().system('ls')"), + # Double-escape shell, this means to capture the output of the + # subprocess and return it + ("!!ls", "get_ipython().getoutput('ls')"), + ], + # Help/object info + escaped_help=[ + ("?", "get_ipython().show_usage()"), + ("?x1", "get_ipython().run_line_magic('pinfo', 'x1')"), + ("??x2", "get_ipython().run_line_magic('pinfo2', 'x2')"), + ("?a.*s", "get_ipython().run_line_magic('psearch', 'a.*s')"), + ("?%hist1", "get_ipython().run_line_magic('pinfo', '%hist1')"), + ("?%%hist2", "get_ipython().run_line_magic('pinfo', '%%hist2')"), + ("?abc = qwe", "get_ipython().run_line_magic('pinfo', 'abc')"), + ], + end_help=[ + ("x3?", "get_ipython().run_line_magic('pinfo', 'x3')"), + ("x4??", "get_ipython().run_line_magic('pinfo2', 'x4')"), + ("%hist1?", "get_ipython().run_line_magic('pinfo', '%hist1')"), + ("%hist2??", "get_ipython().run_line_magic('pinfo2', '%hist2')"), + ("%%hist3?", "get_ipython().run_line_magic('pinfo', '%%hist3')"), + ("%%hist4??", "get_ipython().run_line_magic('pinfo2', '%%hist4')"), + ("π.foo?", "get_ipython().run_line_magic('pinfo', 'π.foo')"), + ("f*?", "get_ipython().run_line_magic('psearch', 'f*')"), + ("ax.*aspe*?", "get_ipython().run_line_magic('psearch', 'ax.*aspe*')"), + ("a = abc?", "get_ipython().run_line_magic('pinfo', 'abc')"), + ("a = abc.qe??", "get_ipython().run_line_magic('pinfo2', 'abc.qe')"), + ("a = *.items?", "get_ipython().run_line_magic('psearch', '*.items')"), + ("plot(a?", "get_ipython().run_line_magic('pinfo', 'a')"), + ("a*2 #comment?", "a*2 #comment?"), + ], + # Explicit magic calls + escaped_magic=[ + ("%cd", "get_ipython().run_line_magic('cd', '')"), + ("%cd /home", "get_ipython().run_line_magic('cd', '/home')"), + # Backslashes need to be escaped. + ("%cd C:\\User", "get_ipython().run_line_magic('cd', 'C:\\\\User')"), + (" %magic", " get_ipython().run_line_magic('magic', '')"), + ], + # Quoting with separate arguments + escaped_quote=[ + (",f", 'f("")'), + (",f x", 'f("x")'), + (" ,f y", ' f("y")'), + (",f a b", 'f("a", "b")'), + ], + # Quoting with single argument + escaped_quote2=[ + (";f", 'f("")'), + (";f x", 'f("x")'), + (" ;f y", ' f("y")'), + (";f a b", 'f("a b")'), + ], + # Simply apply parens + escaped_paren=[ + ("/f", "f()"), + ("/f x", "f(x)"), + (" /f y", " f(y)"), + ("/f a b", "f(a, b)"), + ], + # Check that we transform prompts before other transforms + mixed=[ + ("In [1]: %lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), + (">>> %lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), + ("In [2]: !ls", "get_ipython().system('ls')"), + ("In [3]: abs?", "get_ipython().run_line_magic('pinfo', 'abs')"), + ("In [4]: b = %who", "b = get_ipython().run_line_magic('who', '')"), + ], +) + +# multiline syntax examples. Each of these should be a list of lists, with +# each entry itself having pairs of raw/transformed input. The union (with +# '\n'.join() of the transformed inputs is what the splitter should produce +# when fed the raw lines one at a time via push. +syntax_ml = \ + dict(classic_prompt = + [ [('>>> for i in range(10):','for i in range(10):'), + ('... print i',' print i'), + ('... ', ''), + ], + [('>>> a="""','a="""'), + ('... 123"""','123"""'), + ], + [('a="""','a="""'), + ('... 123','123'), + ('... 456"""','456"""'), + ], + [('a="""','a="""'), + ('>>> 123','123'), + ('... 456"""','456"""'), + ], + [('a="""','a="""'), + ('123','123'), + ('... 456"""','... 456"""'), + ], + [('....__class__','....__class__'), + ], + [('a=5', 'a=5'), + ('...', ''), + ], + [('>>> def f(x):', 'def f(x):'), + ('...', ''), + ('... return x', ' return x'), + ], + [('board = """....', 'board = """....'), + ('....', '....'), + ('...."""', '...."""'), + ], + ], + + ipy_prompt = + [ [('In [24]: for i in range(10):','for i in range(10):'), + (' ....: print i',' print i'), + (' ....: ', ''), + ], + [('In [24]: for i in range(10):','for i in range(10):'), + # Qt console prompts expand with spaces, not dots + (' ...: print i',' print i'), + (' ...: ', ''), + ], + [('In [24]: for i in range(10):','for i in range(10):'), + # Sometimes whitespace preceding '...' has been removed + ('...: print i',' print i'), + ('...: ', ''), + ], + [('In [24]: for i in range(10):','for i in range(10):'), + # Space after last continuation prompt has been removed (issue #6674) + ('...: print i',' print i'), + ('...:', ''), + ], + [('In [2]: a="""','a="""'), + (' ...: 123"""','123"""'), + ], + [('a="""','a="""'), + (' ...: 123','123'), + (' ...: 456"""','456"""'), + ], + [('a="""','a="""'), + ('In [1]: 123','123'), + (' ...: 456"""','456"""'), + ], + [('a="""','a="""'), + ('123','123'), + (' ...: 456"""',' ...: 456"""'), + ], + ], + + multiline_datastructure_prompt = + [ [('>>> a = [1,','a = [1,'), + ('... 2]','2]'), + ], + ], + + multiline_datastructure = + [ [('b = ("%s"', None), + ('# comment', None), + ('%foo )', 'b = ("%s"\n# comment\n%foo )'), + ], + ], + + multiline_string = + [ [("'''foo?", None), + ("bar'''", "'''foo?\nbar'''"), + ], + ], + + leading_indent = + [ [(' print "hi"','print "hi"'), + ], + [(' for a in range(5):','for a in range(5):'), + (' a*2',' a*2'), + ], + [(' a="""','a="""'), + (' 123"""','123"""'), + ], + [('a="""','a="""'), + (' 123"""',' 123"""'), + ], + ], + + cellmagic = + [ [('%%foo a', None), + (None, "get_ipython().run_cell_magic('foo', 'a', '')"), + ], + [('%%bar 123', None), + ('hello', None), + (None , "get_ipython().run_cell_magic('bar', '123', 'hello')"), + ], + [('a=5', 'a=5'), + ('%%cellmagic', '%%cellmagic'), + ], + ], + + escaped = + [ [('%abc def \\', None), + ('ghi', "get_ipython().run_line_magic('abc', 'def ghi')"), + ], + [('%abc def \\', None), + ('ghi\\', None), + (None, "get_ipython().run_line_magic('abc', 'def ghi')"), + ], + ], + + assign_magic = + [ [('a = %bc de \\', None), + ('fg', "a = get_ipython().run_line_magic('bc', 'de fg')"), + ], + [('a = %bc de \\', None), + ('fg\\', None), + (None, "a = get_ipython().run_line_magic('bc', 'de fg')"), + ], + ], + + assign_system = + [ [('a = !bc de \\', None), + ('fg', "a = get_ipython().getoutput('bc de fg')"), + ], + [('a = !bc de \\', None), + ('fg\\', None), + (None, "a = get_ipython().getoutput('bc de fg')"), + ], + ], + ) + + +def test_assign_system(): + tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system']) + +def test_assign_magic(): + tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic']) + +def test_classic_prompt(): + tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt']) + for example in syntax_ml['classic_prompt']: + transform_checker(example, ipt.classic_prompt) + for example in syntax_ml['multiline_datastructure_prompt']: + transform_checker(example, ipt.classic_prompt) + + # Check that we don't transform the second line if the first is obviously + # IPython syntax + transform_checker([ + ('%foo', '%foo'), + ('>>> bar', '>>> bar'), + ], ipt.classic_prompt) + + +def test_ipy_prompt(): + tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt']) + for example in syntax_ml['ipy_prompt']: + transform_checker(example, ipt.ipy_prompt) + + # Check that we don't transform the second line if we're inside a cell magic + transform_checker([ + ('%%foo', '%%foo'), + ('In [1]: bar', 'In [1]: bar'), + ], ipt.ipy_prompt) + +def test_assemble_logical_lines(): + tests = \ + [ [("a = \\", None), + ("123", "a = 123"), + ], + [("a = \\", None), # Test resetting when within a multi-line string + ("12 *\\", None), + (None, "a = 12 *"), + ], + [("# foo\\", "# foo\\"), # Comments can't be continued like this + ], + ] + for example in tests: + transform_checker(example, ipt.assemble_logical_lines) + +def test_assemble_python_lines(): + tests = \ + [ [("a = '''", None), + ("abc'''", "a = '''\nabc'''"), + ], + [("a = '''", None), # Test resetting when within a multi-line string + ("def", None), + (None, "a = '''\ndef"), + ], + [("a = [1,", None), + ("2]", "a = [1,\n2]"), + ], + [("a = [1,", None), # Test resetting when within a multi-line string + ("2,", None), + (None, "a = [1,\n2,"), + ], + [("a = '''", None), # Test line continuation within a multi-line string + ("abc\\", None), + ("def", None), + ("'''", "a = '''\nabc\\\ndef\n'''"), + ], + ] + syntax_ml['multiline_datastructure'] + for example in tests: + transform_checker(example, ipt.assemble_python_lines) + + +def test_help_end(): + tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help']) + +def test_escaped_noesc(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_noesc']) + + +def test_escaped_shell(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_shell']) + + +def test_escaped_help(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_help']) + + +def test_escaped_magic(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_magic']) + + +def test_escaped_quote(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote']) + + +def test_escaped_quote2(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote2']) + + +def test_escaped_paren(): + tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_paren']) + + +def test_cellmagic(): + for example in syntax_ml['cellmagic']: + transform_checker(example, ipt.cellmagic) + + line_example = [('%%bar 123', None), + ('hello', None), + ('' , "get_ipython().run_cell_magic('bar', '123', 'hello')"), + ] + transform_checker(line_example, ipt.cellmagic, end_on_blank_line=True) + +def test_has_comment(): + tests = [('text', False), + ('text #comment', True), + ('text #comment\n', True), + ('#comment', True), + ('#comment\n', True), + ('a = "#string"', False), + ('a = "#string" # comment', True), + ('a #comment not "string"', True), + ] + tt.check_pairs(ipt.has_comment, tests) + +@ipt.TokenInputTransformer.wrap +def decistmt(tokens): + """Substitute Decimals for floats in a string of statements. + + Based on an example from the tokenize module docs. + """ + result = [] + for toknum, tokval, _, _, _ in tokens: + if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens + yield from [ + (tokenize.NAME, 'Decimal'), + (tokenize.OP, '('), + (tokenize.STRING, repr(tokval)), + (tokenize.OP, ')') + ] + else: + yield (toknum, tokval) + + + +def test_token_input_transformer(): + tests = [('1.2', "Decimal ('1.2')"), + ('"1.2"', '"1.2"'), + ] + tt.check_pairs(transform_and_reset(decistmt), tests) + ml_tests = \ + [ [("a = 1.2; b = '''x", None), + ("y'''", "a =Decimal ('1.2');b ='''x\ny'''"), + ], + [("a = [1.2,", None), + ("3]", "a =[Decimal ('1.2'),\n3 ]"), + ], + [("a = '''foo", None), # Test resetting when within a multi-line string + ("bar", None), + (None, "a = '''foo\nbar"), + ], + ] + for example in ml_tests: + transform_checker(example, decistmt) diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputtransformer2.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputtransformer2.py new file mode 100644 index 00000000..0f563b72 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputtransformer2.py @@ -0,0 +1,448 @@ +"""Tests for the token-based transformers in IPython.core.inputtransformer2 + +Line-based transformers are the simpler ones; token-based transformers are +more complex. See test_inputtransformer2_line for tests for line-based +transformations. +""" + +import platform +import string +import sys +from textwrap import dedent + +import pytest + +from IPython.core import inputtransformer2 as ipt2 +from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line + +MULTILINE_MAGIC = ( + """\ +a = f() +%foo \\ +bar +g() +""".splitlines( + keepends=True + ), + (2, 0), + """\ +a = f() +get_ipython().run_line_magic('foo', ' bar') +g() +""".splitlines( + keepends=True + ), +) + +INDENTED_MAGIC = ( + """\ +for a in range(5): + %ls +""".splitlines( + keepends=True + ), + (2, 4), + """\ +for a in range(5): + get_ipython().run_line_magic('ls', '') +""".splitlines( + keepends=True + ), +) + +CRLF_MAGIC = ( + ["a = f()\n", "%ls\r\n", "g()\n"], + (2, 0), + ["a = f()\n", "get_ipython().run_line_magic('ls', '')\n", "g()\n"], +) + +MULTILINE_MAGIC_ASSIGN = ( + """\ +a = f() +b = %foo \\ + bar +g() +""".splitlines( + keepends=True + ), + (2, 4), + """\ +a = f() +b = get_ipython().run_line_magic('foo', ' bar') +g() +""".splitlines( + keepends=True + ), +) + +MULTILINE_SYSTEM_ASSIGN = ("""\ +a = f() +b = !foo \\ + bar +g() +""".splitlines(keepends=True), (2, 4), """\ +a = f() +b = get_ipython().getoutput('foo bar') +g() +""".splitlines(keepends=True)) + +##### + +MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = ( + """\ +def test(): + for i in range(1): + print(i) + res =! ls +""".splitlines( + keepends=True + ), + (4, 7), + """\ +def test(): + for i in range(1): + print(i) + res =get_ipython().getoutput(\' ls\') +""".splitlines( + keepends=True + ), +) + +###### + +AUTOCALL_QUOTE = ([",f 1 2 3\n"], (1, 0), ['f("1", "2", "3")\n']) + +AUTOCALL_QUOTE2 = ([";f 1 2 3\n"], (1, 0), ['f("1 2 3")\n']) + +AUTOCALL_PAREN = (["/f 1 2 3\n"], (1, 0), ["f(1, 2, 3)\n"]) + +SIMPLE_HELP = (["foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', 'foo')\n"]) + +DETAILED_HELP = ( + ["foo??\n"], + (1, 0), + ["get_ipython().run_line_magic('pinfo2', 'foo')\n"], +) + +MAGIC_HELP = (["%foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', '%foo')\n"]) + +HELP_IN_EXPR = ( + ["a = b + c?\n"], + (1, 0), + ["get_ipython().run_line_magic('pinfo', 'c')\n"], +) + +HELP_CONTINUED_LINE = ( + """\ +a = \\ +zip? +""".splitlines( + keepends=True + ), + (1, 0), + [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"], +) + +HELP_MULTILINE = ( + """\ +(a, +b) = zip? +""".splitlines( + keepends=True + ), + (1, 0), + [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"], +) + +HELP_UNICODE = ( + ["π.foo?\n"], + (1, 0), + ["get_ipython().run_line_magic('pinfo', 'π.foo')\n"], +) + + +def null_cleanup_transformer(lines): + """ + A cleanup transform that returns an empty list. + """ + return [] + + +def test_check_make_token_by_line_never_ends_empty(): + """ + Check that not sequence of single or double characters ends up leading to en empty list of tokens + """ + from string import printable + + for c in printable: + assert make_tokens_by_line(c)[-1] != [] + for k in printable: + assert make_tokens_by_line(c + k)[-1] != [] + + +def check_find(transformer, case, match=True): + sample, expected_start, _ = case + tbl = make_tokens_by_line(sample) + res = transformer.find(tbl) + if match: + # start_line is stored 0-indexed, expected values are 1-indexed + assert (res.start_line + 1, res.start_col) == expected_start + return res + else: + assert res is None + + +def check_transform(transformer_cls, case): + lines, start, expected = case + transformer = transformer_cls(start) + assert transformer.transform(lines) == expected + + +def test_continued_line(): + lines = MULTILINE_MAGIC_ASSIGN[0] + assert ipt2.find_end_of_continued_line(lines, 1) == 2 + + assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar" + + +def test_find_assign_magic(): + check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN) + check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False) + check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False) + + +def test_transform_assign_magic(): + check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN) + + +def test_find_assign_system(): + check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN) + check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT) + check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None)) + check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None)) + check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False) + + +def test_transform_assign_system(): + check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN) + check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT) + + +def test_find_magic_escape(): + check_find(ipt2.EscapedCommand, MULTILINE_MAGIC) + check_find(ipt2.EscapedCommand, INDENTED_MAGIC) + check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False) + + +def test_transform_magic_escape(): + check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC) + check_transform(ipt2.EscapedCommand, INDENTED_MAGIC) + check_transform(ipt2.EscapedCommand, CRLF_MAGIC) + + +def test_find_autocalls(): + for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]: + print("Testing %r" % case[0]) + check_find(ipt2.EscapedCommand, case) + + +def test_transform_autocall(): + for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]: + print("Testing %r" % case[0]) + check_transform(ipt2.EscapedCommand, case) + + +def test_find_help(): + for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]: + check_find(ipt2.HelpEnd, case) + + tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE) + assert tf.q_line == 1 + assert tf.q_col == 3 + + tf = check_find(ipt2.HelpEnd, HELP_MULTILINE) + assert tf.q_line == 1 + assert tf.q_col == 8 + + # ? in a comment does not trigger help + check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False) + # Nor in a string + check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False) + + +def test_transform_help(): + tf = ipt2.HelpEnd((1, 0), (1, 9)) + assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2] + + tf = ipt2.HelpEnd((1, 0), (2, 3)) + assert tf.transform(HELP_CONTINUED_LINE[0]) == HELP_CONTINUED_LINE[2] + + tf = ipt2.HelpEnd((1, 0), (2, 8)) + assert tf.transform(HELP_MULTILINE[0]) == HELP_MULTILINE[2] + + tf = ipt2.HelpEnd((1, 0), (1, 0)) + assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2] + + +def test_find_assign_op_dedent(): + """ + be careful that empty token like dedent are not counted as parens + """ + + class Tk: + def __init__(self, s): + self.string = s + + assert _find_assign_op([Tk(s) for s in ("", "a", "=", "b")]) == 2 + assert ( + _find_assign_op([Tk(s) for s in ("", "(", "a", "=", "b", ")", "=", "5")]) == 6 + ) + + +extra_closing_paren_param = ( + pytest.param("(\n))", "invalid", None) + if sys.version_info >= (3, 12) + else pytest.param("(\n))", "incomplete", 0) +) +examples = [ + pytest.param("a = 1", "complete", None), + pytest.param("for a in range(5):", "incomplete", 4), + pytest.param("for a in range(5):\n if a > 0:", "incomplete", 8), + pytest.param("raise = 2", "invalid", None), + pytest.param("a = [1,\n2,", "incomplete", 0), + extra_closing_paren_param, + pytest.param("\\\r\n", "incomplete", 0), + pytest.param("a = '''\n hi", "incomplete", 3), + pytest.param("def a():\n x=1\n global x", "invalid", None), + pytest.param( + "a \\ ", + "invalid", + None, + marks=pytest.mark.xfail( + reason="Bug in python 3.9.8 – bpo 45738", + condition=sys.version_info in [(3, 11, 0, "alpha", 2)], + raises=SystemError, + strict=True, + ), + ), # Nothing allowed after backslash, + pytest.param("1\\\n+2", "complete", None), +] + + +@pytest.mark.parametrize("code, expected, number", examples) +def test_check_complete_param(code, expected, number): + cc = ipt2.TransformerManager().check_complete + assert cc(code) == (expected, number) + + +@pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy") +@pytest.mark.xfail( + reason="Bug in python 3.9.8 – bpo 45738", + condition=sys.version_info in [(3, 11, 0, "alpha", 2)], + raises=SystemError, + strict=True, +) +def test_check_complete(): + cc = ipt2.TransformerManager().check_complete + + example = dedent( + """ + if True: + a=1""" + ) + + assert cc(example) == ("incomplete", 4) + assert cc(example + "\n") == ("complete", None) + assert cc(example + "\n ") == ("complete", None) + + # no need to loop on all the letters/numbers. + short = "12abAB" + string.printable[62:] + for c in short: + # test does not raise: + cc(c) + for k in short: + cc(c + k) + + assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2) + + +@pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy") +@pytest.mark.parametrize( + "value, expected", + [ + ('''def foo():\n """''', ("incomplete", 4)), + ("""async with example:\n pass""", ("incomplete", 4)), + ("""async with example:\n pass\n """, ("complete", None)), + ], +) +def test_check_complete_II(value, expected): + """ + Test that multiple line strings are properly handled. + + Separate test function for convenience + + """ + cc = ipt2.TransformerManager().check_complete + assert cc(value) == expected + + +@pytest.mark.parametrize( + "value, expected", + [ + (")", ("invalid", None)), + ("]", ("invalid", None)), + ("}", ("invalid", None)), + (")(", ("invalid", None)), + ("][", ("invalid", None)), + ("}{", ("invalid", None)), + ("]()(", ("invalid", None)), + ("())(", ("invalid", None)), + (")[](", ("invalid", None)), + ("()](", ("invalid", None)), + ], +) +def test_check_complete_invalidates_sunken_brackets(value, expected): + """ + Test that a single line with more closing brackets than the opening ones is + interpreted as invalid + """ + cc = ipt2.TransformerManager().check_complete + assert cc(value) == expected + + +def test_null_cleanup_transformer(): + manager = ipt2.TransformerManager() + manager.cleanup_transforms.insert(0, null_cleanup_transformer) + assert manager.transform_cell("") == "" + + +def test_side_effects_I(): + count = 0 + + def counter(lines): + nonlocal count + count += 1 + return lines + + counter.has_side_effects = True + + manager = ipt2.TransformerManager() + manager.cleanup_transforms.insert(0, counter) + assert manager.check_complete("a=1\n") == ("complete", None) + assert count == 0 + + +def test_side_effects_II(): + count = 0 + + def counter(lines): + nonlocal count + count += 1 + return lines + + counter.has_side_effects = True + + manager = ipt2.TransformerManager() + manager.line_transforms.insert(0, counter) + assert manager.check_complete("b=1\n") == ("complete", None) + assert count == 0 diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputtransformer2_line.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputtransformer2_line.py new file mode 100644 index 00000000..ec7a8736 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_inputtransformer2_line.py @@ -0,0 +1,167 @@ +"""Tests for the line-based transformers in IPython.core.inputtransformer2 + +Line-based transformers are the simpler ones; token-based transformers are +more complex. See test_inputtransformer2 for tests for token-based transformers. +""" + +from IPython.core import inputtransformer2 as ipt2 + +CELL_MAGIC = ("""\ +%%foo arg +body 1 +body 2 +""", """\ +get_ipython().run_cell_magic('foo', 'arg', 'body 1\\nbody 2\\n') +""") + +def test_cell_magic(): + for sample, expected in [CELL_MAGIC]: + assert ipt2.cell_magic(sample.splitlines(keepends=True)) == expected.splitlines( + keepends=True + ) + +CLASSIC_PROMPT = ("""\ +>>> for a in range(5): +... print(a) +""", """\ +for a in range(5): + print(a) +""") + +CLASSIC_PROMPT_L2 = ("""\ +for a in range(5): +... print(a) +... print(a ** 2) +""", """\ +for a in range(5): + print(a) + print(a ** 2) +""") + +def test_classic_prompt(): + for sample, expected in [CLASSIC_PROMPT, CLASSIC_PROMPT_L2]: + assert ipt2.classic_prompt( + sample.splitlines(keepends=True) + ) == expected.splitlines(keepends=True) + +IPYTHON_PROMPT = ("""\ +In [1]: for a in range(5): + ...: print(a) +""", """\ +for a in range(5): + print(a) +""") + +IPYTHON_PROMPT_L2 = ("""\ +for a in range(5): + ...: print(a) + ...: print(a ** 2) +""", """\ +for a in range(5): + print(a) + print(a ** 2) +""") + + +IPYTHON_PROMPT_VI_INS = ( + """\ +[ins] In [11]: def a(): + ...: 123 + ...: + ...: 123 +""", + """\ +def a(): + 123 + +123 +""", +) + +IPYTHON_PROMPT_VI_NAV = ( + """\ +[nav] In [11]: def a(): + ...: 123 + ...: + ...: 123 +""", + """\ +def a(): + 123 + +123 +""", +) + + +def test_ipython_prompt(): + for sample, expected in [ + IPYTHON_PROMPT, + IPYTHON_PROMPT_L2, + IPYTHON_PROMPT_VI_INS, + IPYTHON_PROMPT_VI_NAV, + ]: + assert ipt2.ipython_prompt( + sample.splitlines(keepends=True) + ) == expected.splitlines(keepends=True) + + +INDENT_SPACES = ("""\ + if True: + a = 3 +""", """\ +if True: + a = 3 +""") + +INDENT_TABS = ("""\ +\tif True: +\t\tb = 4 +""", """\ +if True: +\tb = 4 +""") + +def test_leading_indent(): + for sample, expected in [INDENT_SPACES, INDENT_TABS]: + assert ipt2.leading_indent( + sample.splitlines(keepends=True) + ) == expected.splitlines(keepends=True) + +LEADING_EMPTY_LINES = ("""\ + \t + +if True: + a = 3 + +b = 4 +""", """\ +if True: + a = 3 + +b = 4 +""") + +ONLY_EMPTY_LINES = ("""\ + \t + +""", """\ + \t + +""") + +def test_leading_empty_lines(): + for sample, expected in [LEADING_EMPTY_LINES, ONLY_EMPTY_LINES]: + assert ipt2.leading_empty_lines( + sample.splitlines(keepends=True) + ) == expected.splitlines(keepends=True) + +CRLF_MAGIC = ([ + "%%ls\r\n" +], [ + "get_ipython().run_cell_magic('ls', '', '')\n" +]) + +def test_crlf_magic(): + for sample, expected in [CRLF_MAGIC]: + assert ipt2.cell_magic(sample) == expected diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_interactiveshell.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_interactiveshell.py new file mode 100644 index 00000000..f87d96f5 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_interactiveshell.py @@ -0,0 +1,1221 @@ +# -*- coding: utf-8 -*- +"""Tests for the key interactiveshell module. + +Historically the main classes in interactiveshell have been under-tested. This +module should grow as many single-method tests as possible to trap many of the +recurring bugs we seem to encounter with high-level interaction. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import asyncio +import ast +import os +import signal +import shutil +import sys +import tempfile +import unittest +import pytest +from unittest import mock + +from os.path import join + +from IPython.core.error import InputRejected +from IPython.core.inputtransformer import InputTransformer +from IPython.core import interactiveshell +from IPython.core.oinspect import OInfo +from IPython.testing.decorators import ( + skipif, + skip_win32, + onlyif_unicode_paths, + onlyif_cmds_exist, + skip_if_not_osx, +) +from IPython.testing import tools as tt +from IPython.utils.process import find_cmd + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- +# This is used by every single test, no point repeating it ad nauseam + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +class DerivedInterrupt(KeyboardInterrupt): + pass + +class InteractiveShellTestCase(unittest.TestCase): + def test_naked_string_cells(self): + """Test that cells with only naked strings are fully executed""" + # First, single-line inputs + ip.run_cell('"a"\n') + self.assertEqual(ip.user_ns['_'], 'a') + # And also multi-line cells + ip.run_cell('"""a\nb"""\n') + self.assertEqual(ip.user_ns['_'], 'a\nb') + + def test_run_empty_cell(self): + """Just make sure we don't get a horrible error with a blank + cell of input. Yes, I did overlook that.""" + old_xc = ip.execution_count + res = ip.run_cell('') + self.assertEqual(ip.execution_count, old_xc) + self.assertEqual(res.execution_count, None) + + def test_run_cell_multiline(self): + """Multi-block, multi-line cells must execute correctly. + """ + src = '\n'.join(["x=1", + "y=2", + "if 1:", + " x += 1", + " y += 1",]) + res = ip.run_cell(src) + self.assertEqual(ip.user_ns['x'], 2) + self.assertEqual(ip.user_ns['y'], 3) + self.assertEqual(res.success, True) + self.assertEqual(res.result, None) + + def test_multiline_string_cells(self): + "Code sprinkled with multiline strings should execute (GH-306)" + ip.run_cell('tmp=0') + self.assertEqual(ip.user_ns['tmp'], 0) + res = ip.run_cell('tmp=1;"""a\nb"""\n') + self.assertEqual(ip.user_ns['tmp'], 1) + self.assertEqual(res.success, True) + self.assertEqual(res.result, "a\nb") + + def test_dont_cache_with_semicolon(self): + "Ending a line with semicolon should not cache the returned object (GH-307)" + oldlen = len(ip.user_ns['Out']) + for cell in ['1;', '1;1;']: + res = ip.run_cell(cell, store_history=True) + newlen = len(ip.user_ns['Out']) + self.assertEqual(oldlen, newlen) + self.assertIsNone(res.result) + i = 0 + #also test the default caching behavior + for cell in ['1', '1;1']: + ip.run_cell(cell, store_history=True) + newlen = len(ip.user_ns['Out']) + i += 1 + self.assertEqual(oldlen+i, newlen) + + def test_syntax_error(self): + res = ip.run_cell("raise = 3") + self.assertIsInstance(res.error_before_exec, SyntaxError) + + def test_open_standard_input_stream(self): + res = ip.run_cell("open(0)") + self.assertIsInstance(res.error_in_exec, ValueError) + + def test_open_standard_output_stream(self): + res = ip.run_cell("open(1)") + self.assertIsInstance(res.error_in_exec, ValueError) + + def test_open_standard_error_stream(self): + res = ip.run_cell("open(2)") + self.assertIsInstance(res.error_in_exec, ValueError) + + def test_In_variable(self): + "Verify that In variable grows with user input (GH-284)" + oldlen = len(ip.user_ns['In']) + ip.run_cell('1;', store_history=True) + newlen = len(ip.user_ns['In']) + self.assertEqual(oldlen+1, newlen) + self.assertEqual(ip.user_ns['In'][-1],'1;') + + def test_magic_names_in_string(self): + ip.run_cell('a = """\n%exit\n"""') + self.assertEqual(ip.user_ns['a'], '\n%exit\n') + + def test_trailing_newline(self): + """test that running !(command) does not raise a SyntaxError""" + ip.run_cell('!(true)\n', False) + ip.run_cell('!(true)\n\n\n', False) + + def test_gh_597(self): + """Pretty-printing lists of objects with non-ascii reprs may cause + problems.""" + class Spam(object): + def __repr__(self): + return "\xe9"*50 + import IPython.core.formatters + f = IPython.core.formatters.PlainTextFormatter() + f([Spam(), Spam()]) + + def test_future_flags(self): + """Check that future flags are used for parsing code (gh-777)""" + ip.run_cell('from __future__ import barry_as_FLUFL') + try: + ip.run_cell('prfunc_return_val = 1 <> 2') + assert 'prfunc_return_val' in ip.user_ns + finally: + # Reset compiler flags so we don't mess up other tests. + ip.compile.reset_compiler_flags() + + def test_can_pickle(self): + "Can we pickle objects defined interactively (GH-29)" + ip = get_ipython() + ip.reset() + ip.run_cell(("class Mylist(list):\n" + " def __init__(self,x=[]):\n" + " list.__init__(self,x)")) + ip.run_cell("w=Mylist([1,2,3])") + + from pickle import dumps + + # We need to swap in our main module - this is only necessary + # inside the test framework, because IPython puts the interactive module + # in place (but the test framework undoes this). + _main = sys.modules['__main__'] + sys.modules['__main__'] = ip.user_module + try: + res = dumps(ip.user_ns["w"]) + finally: + sys.modules['__main__'] = _main + self.assertTrue(isinstance(res, bytes)) + + def test_global_ns(self): + "Code in functions must be able to access variables outside them." + ip = get_ipython() + ip.run_cell("a = 10") + ip.run_cell(("def f(x):\n" + " return x + a")) + ip.run_cell("b = f(12)") + self.assertEqual(ip.user_ns["b"], 22) + + def test_bad_custom_tb(self): + """Check that InteractiveShell is protected from bad custom exception handlers""" + ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0) + self.assertEqual(ip.custom_exceptions, (IOError,)) + with tt.AssertPrints("Custom TB Handler failed", channel='stderr'): + ip.run_cell(u'raise IOError("foo")') + self.assertEqual(ip.custom_exceptions, ()) + + def test_bad_custom_tb_return(self): + """Check that InteractiveShell is protected from bad return types in custom exception handlers""" + ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1) + self.assertEqual(ip.custom_exceptions, (NameError,)) + with tt.AssertPrints("Custom TB Handler failed", channel='stderr'): + ip.run_cell(u'a=abracadabra') + self.assertEqual(ip.custom_exceptions, ()) + + def test_drop_by_id(self): + myvars = {"a":object(), "b":object(), "c": object()} + ip.push(myvars, interactive=False) + for name in myvars: + assert name in ip.user_ns, name + assert name in ip.user_ns_hidden, name + ip.user_ns['b'] = 12 + ip.drop_by_id(myvars) + for name in ["a", "c"]: + assert name not in ip.user_ns, name + assert name not in ip.user_ns_hidden, name + assert ip.user_ns['b'] == 12 + ip.reset() + + def test_var_expand(self): + ip.user_ns['f'] = u'Ca\xf1o' + self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o') + self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o') + self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1') + self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2') + + self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'") + + ip.user_ns['f'] = b'Ca\xc3\xb1o' + # This should not raise any exception: + ip.var_expand(u'echo $f') + + def test_var_expand_local(self): + """Test local variable expansion in !system and %magic calls""" + # !system + ip.run_cell( + "def test():\n" + ' lvar = "ttt"\n' + " ret = !echo {lvar}\n" + " return ret[0]\n" + ) + res = ip.user_ns["test"]() + self.assertIn("ttt", res) + + # %magic + ip.run_cell( + "def makemacro():\n" + ' macroname = "macro_var_expand_locals"\n' + " %macro {macroname} codestr\n" + ) + ip.user_ns["codestr"] = "str(12)" + ip.run_cell("makemacro()") + self.assertIn("macro_var_expand_locals", ip.user_ns) + + def test_var_expand_self(self): + """Test variable expansion with the name 'self', which was failing. + + See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218 + """ + ip.run_cell( + "class cTest:\n" + ' classvar="see me"\n' + " def test(self):\n" + " res = !echo Variable: {self.classvar}\n" + " return res[0]\n" + ) + self.assertIn("see me", ip.user_ns["cTest"]().test()) + + def test_bad_var_expand(self): + """var_expand on invalid formats shouldn't raise""" + # SyntaxError + self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}") + # NameError + self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}") + # ZeroDivisionError + self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}") + + def test_silent_postexec(self): + """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks""" + pre_explicit = mock.Mock() + pre_always = mock.Mock() + post_explicit = mock.Mock() + post_always = mock.Mock() + all_mocks = [pre_explicit, pre_always, post_explicit, post_always] + + ip.events.register('pre_run_cell', pre_explicit) + ip.events.register('pre_execute', pre_always) + ip.events.register('post_run_cell', post_explicit) + ip.events.register('post_execute', post_always) + + try: + ip.run_cell("1", silent=True) + assert pre_always.called + assert not pre_explicit.called + assert post_always.called + assert not post_explicit.called + # double-check that non-silent exec did what we expected + # silent to avoid + ip.run_cell("1") + assert pre_explicit.called + assert post_explicit.called + info, = pre_explicit.call_args[0] + result, = post_explicit.call_args[0] + self.assertEqual(info, result.info) + # check that post hooks are always called + [m.reset_mock() for m in all_mocks] + ip.run_cell("syntax error") + assert pre_always.called + assert pre_explicit.called + assert post_always.called + assert post_explicit.called + info, = pre_explicit.call_args[0] + result, = post_explicit.call_args[0] + self.assertEqual(info, result.info) + finally: + # remove post-exec + ip.events.unregister('pre_run_cell', pre_explicit) + ip.events.unregister('pre_execute', pre_always) + ip.events.unregister('post_run_cell', post_explicit) + ip.events.unregister('post_execute', post_always) + + def test_silent_noadvance(self): + """run_cell(silent=True) doesn't advance execution_count""" + ec = ip.execution_count + # silent should force store_history=False + ip.run_cell("1", store_history=True, silent=True) + + self.assertEqual(ec, ip.execution_count) + # double-check that non-silent exec did what we expected + # silent to avoid + ip.run_cell("1", store_history=True) + self.assertEqual(ec+1, ip.execution_count) + + def test_silent_nodisplayhook(self): + """run_cell(silent=True) doesn't trigger displayhook""" + d = dict(called=False) + + trap = ip.display_trap + save_hook = trap.hook + + def failing_hook(*args, **kwargs): + d['called'] = True + + try: + trap.hook = failing_hook + res = ip.run_cell("1", silent=True) + self.assertFalse(d['called']) + self.assertIsNone(res.result) + # double-check that non-silent exec did what we expected + # silent to avoid + ip.run_cell("1") + self.assertTrue(d['called']) + finally: + trap.hook = save_hook + + def test_ofind_line_magic(self): + from IPython.core.magic import register_line_magic + + @register_line_magic + def lmagic(line): + "A line magic" + + # Get info on line magic + lfind = ip._ofind("lmagic") + info = OInfo( + found=True, + isalias=False, + ismagic=True, + namespace="IPython internal", + obj=lmagic, + parent=None, + ) + self.assertEqual(lfind, info) + + def test_ofind_cell_magic(self): + from IPython.core.magic import register_cell_magic + + @register_cell_magic + def cmagic(line, cell): + "A cell magic" + + # Get info on cell magic + find = ip._ofind("cmagic") + info = OInfo( + found=True, + isalias=False, + ismagic=True, + namespace="IPython internal", + obj=cmagic, + parent=None, + ) + self.assertEqual(find, info) + + def test_ofind_property_with_error(self): + class A(object): + @property + def foo(self): + raise NotImplementedError() # pragma: no cover + + a = A() + + found = ip._ofind("a.foo", [("locals", locals())]) + info = OInfo( + found=True, + isalias=False, + ismagic=False, + namespace="locals", + obj=A.foo, + parent=a, + ) + self.assertEqual(found, info) + + def test_ofind_multiple_attribute_lookups(self): + class A(object): + @property + def foo(self): + raise NotImplementedError() # pragma: no cover + + a = A() + a.a = A() + a.a.a = A() + + found = ip._ofind("a.a.a.foo", [("locals", locals())]) + info = OInfo( + found=True, + isalias=False, + ismagic=False, + namespace="locals", + obj=A.foo, + parent=a.a.a, + ) + self.assertEqual(found, info) + + def test_ofind_slotted_attributes(self): + class A(object): + __slots__ = ['foo'] + def __init__(self): + self.foo = 'bar' + + a = A() + found = ip._ofind("a.foo", [("locals", locals())]) + info = OInfo( + found=True, + isalias=False, + ismagic=False, + namespace="locals", + obj=a.foo, + parent=a, + ) + self.assertEqual(found, info) + + found = ip._ofind("a.bar", [("locals", locals())]) + expected = OInfo( + found=False, + isalias=False, + ismagic=False, + namespace=None, + obj=None, + parent=a, + ) + assert found == expected + + def test_ofind_prefers_property_to_instance_level_attribute(self): + class A(object): + @property + def foo(self): + return 'bar' + a = A() + a.__dict__["foo"] = "baz" + self.assertEqual(a.foo, "bar") + found = ip._ofind("a.foo", [("locals", locals())]) + self.assertIs(found.obj, A.foo) + + def test_custom_syntaxerror_exception(self): + called = [] + def my_handler(shell, etype, value, tb, tb_offset=None): + called.append(etype) + shell.showtraceback((etype, value, tb), tb_offset=tb_offset) + + ip.set_custom_exc((SyntaxError,), my_handler) + try: + ip.run_cell("1f") + # Check that this was called, and only once. + self.assertEqual(called, [SyntaxError]) + finally: + # Reset the custom exception hook + ip.set_custom_exc((), None) + + def test_custom_exception(self): + called = [] + def my_handler(shell, etype, value, tb, tb_offset=None): + called.append(etype) + shell.showtraceback((etype, value, tb), tb_offset=tb_offset) + + ip.set_custom_exc((ValueError,), my_handler) + try: + res = ip.run_cell("raise ValueError('test')") + # Check that this was called, and only once. + self.assertEqual(called, [ValueError]) + # Check that the error is on the result object + self.assertIsInstance(res.error_in_exec, ValueError) + finally: + # Reset the custom exception hook + ip.set_custom_exc((), None) + + @mock.patch("builtins.print") + def test_showtraceback_with_surrogates(self, mocked_print): + values = [] + + def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False): + values.append(value) + if value == chr(0xD8FF): + raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "") + + # mock builtins.print + mocked_print.side_effect = mock_print_func + + # ip._showtraceback() is replaced in globalipapp.py. + # Call original method to test. + interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF)) + + self.assertEqual(mocked_print.call_count, 2) + self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"]) + + def test_mktempfile(self): + filename = ip.mktempfile() + # Check that we can open the file again on Windows + with open(filename, "w", encoding="utf-8") as f: + f.write("abc") + + filename = ip.mktempfile(data="blah") + with open(filename, "r", encoding="utf-8") as f: + self.assertEqual(f.read(), "blah") + + def test_new_main_mod(self): + # Smoketest to check that this accepts a unicode module name + name = u'jiefmw' + mod = ip.new_main_mod(u'%s.py' % name, name) + self.assertEqual(mod.__name__, name) + + def test_get_exception_only(self): + try: + raise KeyboardInterrupt + except KeyboardInterrupt: + msg = ip.get_exception_only() + self.assertEqual(msg, 'KeyboardInterrupt\n') + + try: + raise DerivedInterrupt("foo") + except KeyboardInterrupt: + msg = ip.get_exception_only() + self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n') + + def test_inspect_text(self): + ip.run_cell('a = 5') + text = ip.object_inspect_text('a') + self.assertIsInstance(text, str) + + def test_last_execution_result(self): + """ Check that last execution result gets set correctly (GH-10702) """ + result = ip.run_cell('a = 5; a') + self.assertTrue(ip.last_execution_succeeded) + self.assertEqual(ip.last_execution_result.result, 5) + + result = ip.run_cell('a = x_invalid_id_x') + self.assertFalse(ip.last_execution_succeeded) + self.assertFalse(ip.last_execution_result.success) + self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError) + + def test_reset_aliasing(self): + """ Check that standard posix aliases work after %reset. """ + if os.name != 'posix': + return + + ip.reset() + for cmd in ('clear', 'more', 'less', 'man'): + res = ip.run_cell('%' + cmd) + self.assertEqual(res.success, True) + + +@pytest.mark.skipif( + sys.implementation.name == "pypy" + and ((7, 3, 13) < sys.implementation.version < (7, 3, 16)), + reason="Unicode issues with scandir on PyPy, see https://github.com/pypy/pypy/issues/4860", +) +class TestSafeExecfileNonAsciiPath(unittest.TestCase): + @onlyif_unicode_paths + def setUp(self): + self.BASETESTDIR = tempfile.mkdtemp() + self.TESTDIR = join(self.BASETESTDIR, u"åäö") + os.mkdir(self.TESTDIR) + with open( + join(self.TESTDIR, "åäötestscript.py"), "w", encoding="utf-8" + ) as sfile: + sfile.write("pass\n") + self.oldpath = os.getcwd() + os.chdir(self.TESTDIR) + self.fname = u"åäötestscript.py" + + def tearDown(self): + os.chdir(self.oldpath) + shutil.rmtree(self.BASETESTDIR) + + @onlyif_unicode_paths + def test_1(self): + """Test safe_execfile with non-ascii path + """ + ip.safe_execfile(self.fname, {}, raise_exceptions=True) + +class ExitCodeChecks(tt.TempFileMixin): + + def setUp(self): + self.system = ip.system_raw + + def test_exit_code_ok(self): + self.system('exit 0') + self.assertEqual(ip.user_ns['_exit_code'], 0) + + def test_exit_code_error(self): + self.system('exit 1') + self.assertEqual(ip.user_ns['_exit_code'], 1) + + @skipif(not hasattr(signal, 'SIGALRM')) + def test_exit_code_signal(self): + self.mktmp("import signal, time\n" + "signal.setitimer(signal.ITIMER_REAL, 0.1)\n" + "time.sleep(1)\n") + self.system("%s %s" % (sys.executable, self.fname)) + self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM) + + @onlyif_cmds_exist("csh") + def test_exit_code_signal_csh(self): # pragma: no cover + SHELL = os.environ.get("SHELL", None) + os.environ["SHELL"] = find_cmd("csh") + try: + self.test_exit_code_signal() + finally: + if SHELL is not None: + os.environ['SHELL'] = SHELL + else: + del os.environ['SHELL'] + + +class TestSystemRaw(ExitCodeChecks): + + def setUp(self): + super().setUp() + self.system = ip.system_raw + + @onlyif_unicode_paths + def test_1(self): + """Test system_raw with non-ascii cmd + """ + cmd = u'''python -c "'åäö'" ''' + ip.system_raw(cmd) + + @mock.patch('subprocess.call', side_effect=KeyboardInterrupt) + @mock.patch('os.system', side_effect=KeyboardInterrupt) + def test_control_c(self, *mocks): + try: + self.system("sleep 1 # won't happen") + except KeyboardInterrupt: # pragma: no cove + self.fail( + "system call should intercept " + "keyboard interrupt from subprocess.call" + ) + self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT) + + +@pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"]) +def test_magic_warnings(magic_cmd): + if sys.platform == "win32": + to_mock = "os.system" + expected_arg, expected_kwargs = magic_cmd, dict() + else: + to_mock = "subprocess.call" + expected_arg, expected_kwargs = magic_cmd, dict( + shell=True, executable=os.environ.get("SHELL", None) + ) + + with mock.patch(to_mock, return_value=0) as mock_sub: + with pytest.warns(Warning, match=r"You executed the system command"): + ip.system_raw(magic_cmd) + mock_sub.assert_called_once_with(expected_arg, **expected_kwargs) + + +# TODO: Exit codes are currently ignored on Windows. +class TestSystemPipedExitCode(ExitCodeChecks): + + def setUp(self): + super().setUp() + self.system = ip.system_piped + + @skip_win32 + def test_exit_code_ok(self): + ExitCodeChecks.test_exit_code_ok(self) + + @skip_win32 + def test_exit_code_error(self): + ExitCodeChecks.test_exit_code_error(self) + + @skip_win32 + def test_exit_code_signal(self): + ExitCodeChecks.test_exit_code_signal(self) + +class TestModules(tt.TempFileMixin): + def test_extraneous_loads(self): + """Test we're not loading modules on startup that we shouldn't. + """ + self.mktmp("import sys\n" + "print('numpy' in sys.modules)\n" + "print('ipyparallel' in sys.modules)\n" + "print('ipykernel' in sys.modules)\n" + ) + out = "False\nFalse\nFalse\n" + tt.ipexec_validate(self.fname, out) + +class Negator(ast.NodeTransformer): + """Negates all number literals in an AST.""" + + def visit_Num(self, node): + node.value = -node.value + return node + + def visit_Constant(self, node): + if isinstance(node.value, int): + return self.visit_Num(node) + return node + +class TestAstTransform(unittest.TestCase): + def setUp(self): + self.negator = Negator() + ip.ast_transformers.append(self.negator) + + def tearDown(self): + ip.ast_transformers.remove(self.negator) + + def test_non_int_const(self): + with tt.AssertPrints("hello"): + ip.run_cell('print("hello")') + + def test_run_cell(self): + with tt.AssertPrints("-34"): + ip.run_cell("print(12 + 22)") + + # A named reference to a number shouldn't be transformed. + ip.user_ns["n"] = 55 + with tt.AssertNotPrints("-55"): + ip.run_cell("print(n)") + + def test_timeit(self): + called = set() + def f(x): + called.add(x) + ip.push({'f':f}) + + with tt.AssertPrints("std. dev. of"): + ip.run_line_magic("timeit", "-n1 f(1)") + self.assertEqual(called, {-1}) + called.clear() + + with tt.AssertPrints("std. dev. of"): + ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") + self.assertEqual(called, {-2, -3}) + + def test_time(self): + called = [] + def f(x): + called.append(x) + ip.push({'f':f}) + + # Test with an expression + with tt.AssertPrints("Wall time: "): + ip.run_line_magic("time", "f(5+9)") + self.assertEqual(called, [-14]) + called[:] = [] + + # Test with a statement (different code path) + with tt.AssertPrints("Wall time: "): + ip.run_line_magic("time", "a = f(-3 + -2)") + self.assertEqual(called, [5]) + + def test_macro(self): + ip.push({'a':10}) + # The AST transformation makes this do a+=-1 + ip.define_macro("amacro", "a+=1\nprint(a)") + + with tt.AssertPrints("9"): + ip.run_cell("amacro") + with tt.AssertPrints("8"): + ip.run_cell("amacro") + +class TestMiscTransform(unittest.TestCase): + + + def test_transform_only_once(self): + cleanup = 0 + line_t = 0 + def count_cleanup(lines): + nonlocal cleanup + cleanup += 1 + return lines + + def count_line_t(lines): + nonlocal line_t + line_t += 1 + return lines + + ip.input_transformer_manager.cleanup_transforms.append(count_cleanup) + ip.input_transformer_manager.line_transforms.append(count_line_t) + + ip.run_cell('1') + + assert cleanup == 1 + assert line_t == 1 + +class IntegerWrapper(ast.NodeTransformer): + """Wraps all integers in a call to Integer()""" + + # for Python 3.7 and earlier + + # for Python 3.7 and earlier + def visit_Num(self, node): + if isinstance(node.n, int): + return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()), + args=[node], keywords=[]) + return node + + # For Python 3.8+ + def visit_Constant(self, node): + if isinstance(node.value, int): + return self.visit_Num(node) + return node + + +class TestAstTransform2(unittest.TestCase): + def setUp(self): + self.intwrapper = IntegerWrapper() + ip.ast_transformers.append(self.intwrapper) + + self.calls = [] + def Integer(*args): + self.calls.append(args) + return args + ip.push({"Integer": Integer}) + + def tearDown(self): + ip.ast_transformers.remove(self.intwrapper) + del ip.user_ns['Integer'] + + def test_run_cell(self): + ip.run_cell("n = 2") + self.assertEqual(self.calls, [(2,)]) + + # This shouldn't throw an error + ip.run_cell("o = 2.0") + self.assertEqual(ip.user_ns['o'], 2.0) + + def test_run_cell_non_int(self): + ip.run_cell("n = 'a'") + assert self.calls == [] + + def test_timeit(self): + called = set() + def f(x): + called.add(x) + ip.push({'f':f}) + + with tt.AssertPrints("std. dev. of"): + ip.run_line_magic("timeit", "-n1 f(1)") + self.assertEqual(called, {(1,)}) + called.clear() + + with tt.AssertPrints("std. dev. of"): + ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)") + self.assertEqual(called, {(2,), (3,)}) + +class ErrorTransformer(ast.NodeTransformer): + """Throws an error when it sees a number.""" + + def visit_Constant(self, node): + if isinstance(node.value, int): + raise ValueError("test") + return node + + +class TestAstTransformError(unittest.TestCase): + def test_unregistering(self): + err_transformer = ErrorTransformer() + ip.ast_transformers.append(err_transformer) + + with self.assertWarnsRegex(UserWarning, "It will be unregistered"): + ip.run_cell("1 + 2") + + # This should have been removed. + self.assertNotIn(err_transformer, ip.ast_transformers) + + +class StringRejector(ast.NodeTransformer): + """Throws an InputRejected when it sees a string literal. + + Used to verify that NodeTransformers can signal that a piece of code should + not be executed by throwing an InputRejected. + """ + + def visit_Constant(self, node): + if isinstance(node.value, str): + raise InputRejected("test") + return node + + +class TestAstTransformInputRejection(unittest.TestCase): + + def setUp(self): + self.transformer = StringRejector() + ip.ast_transformers.append(self.transformer) + + def tearDown(self): + ip.ast_transformers.remove(self.transformer) + + def test_input_rejection(self): + """Check that NodeTransformers can reject input.""" + + expect_exception_tb = tt.AssertPrints("InputRejected: test") + expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False) + + # Run the same check twice to verify that the transformer is not + # disabled after raising. + with expect_exception_tb, expect_no_cell_output: + ip.run_cell("'unsafe'") + + with expect_exception_tb, expect_no_cell_output: + res = ip.run_cell("'unsafe'") + + self.assertIsInstance(res.error_before_exec, InputRejected) + +def test__IPYTHON__(): + # This shouldn't raise a NameError, that's all + __IPYTHON__ + + +class DummyRepr(object): + def __repr__(self): + return "DummyRepr" + + def _repr_html_(self): + return "dummy" + + def _repr_javascript_(self): + return "console.log('hi');", {'key': 'value'} + + +def test_user_variables(): + # enable all formatters + ip.display_formatter.active_types = ip.display_formatter.format_types + + ip.user_ns['dummy'] = d = DummyRepr() + keys = {'dummy', 'doesnotexist'} + r = ip.user_expressions({ key:key for key in keys}) + + assert keys == set(r.keys()) + dummy = r["dummy"] + assert {"status", "data", "metadata"} == set(dummy.keys()) + assert dummy["status"] == "ok" + data = dummy["data"] + metadata = dummy["metadata"] + assert data.get("text/html") == d._repr_html_() + js, jsmd = d._repr_javascript_() + assert data.get("application/javascript") == js + assert metadata.get("application/javascript") == jsmd + + dne = r["doesnotexist"] + assert dne["status"] == "error" + assert dne["ename"] == "NameError" + + # back to text only + ip.display_formatter.active_types = ['text/plain'] + +def test_user_expression(): + # enable all formatters + ip.display_formatter.active_types = ip.display_formatter.format_types + query = { + 'a' : '1 + 2', + 'b' : '1/0', + } + r = ip.user_expressions(query) + import pprint + pprint.pprint(r) + assert set(r.keys()) == set(query.keys()) + a = r["a"] + assert {"status", "data", "metadata"} == set(a.keys()) + assert a["status"] == "ok" + data = a["data"] + metadata = a["metadata"] + assert data.get("text/plain") == "3" + + b = r["b"] + assert b["status"] == "error" + assert b["ename"] == "ZeroDivisionError" + + # back to text only + ip.display_formatter.active_types = ['text/plain'] + + +class TestSyntaxErrorTransformer(unittest.TestCase): + """Check that SyntaxError raised by an input transformer is handled by run_cell()""" + + @staticmethod + def transformer(lines): + for line in lines: + pos = line.find('syntaxerror') + if pos >= 0: + e = SyntaxError('input contains "syntaxerror"') + e.text = line + e.offset = pos + 1 + raise e + return lines + + def setUp(self): + ip.input_transformers_post.append(self.transformer) + + def tearDown(self): + ip.input_transformers_post.remove(self.transformer) + + def test_syntaxerror_input_transformer(self): + with tt.AssertPrints('1234'): + ip.run_cell('1234') + with tt.AssertPrints('SyntaxError: invalid syntax'): + ip.run_cell('1 2 3') # plain python syntax error + with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'): + ip.run_cell('2345 # syntaxerror') # input transformer syntax error + with tt.AssertPrints('3456'): + ip.run_cell('3456') + + +class TestWarningSuppression(unittest.TestCase): + def test_warning_suppression(self): + ip.run_cell("import warnings") + try: + with self.assertWarnsRegex(UserWarning, "asdf"): + ip.run_cell("warnings.warn('asdf')") + # Here's the real test -- if we run that again, we should get the + # warning again. Traditionally, each warning was only issued once per + # IPython session (approximately), even if the user typed in new and + # different code that should have also triggered the warning, leading + # to much confusion. + with self.assertWarnsRegex(UserWarning, "asdf"): + ip.run_cell("warnings.warn('asdf')") + finally: + ip.run_cell("del warnings") + + + def test_deprecation_warning(self): + ip.run_cell(""" +import warnings +def wrn(): + warnings.warn( + "I AM A WARNING", + DeprecationWarning + ) + """) + try: + with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"): + ip.run_cell("wrn()") + finally: + ip.run_cell("del warnings") + ip.run_cell("del wrn") + + +class TestImportNoDeprecate(tt.TempFileMixin): + + def setUp(self): + """Make a valid python temp file.""" + self.mktmp(""" +import warnings +def wrn(): + warnings.warn( + "I AM A WARNING", + DeprecationWarning + ) +""") + super().setUp() + + def test_no_dep(self): + """ + No deprecation warning should be raised from imported functions + """ + ip.run_cell("from {} import wrn".format(self.fname)) + + with tt.AssertNotPrints("I AM A WARNING"): + ip.run_cell("wrn()") + ip.run_cell("del wrn") + + +def test_custom_exc_count(): + hook = mock.Mock(return_value=None) + ip.set_custom_exc((SyntaxError,), hook) + before = ip.execution_count + ip.run_cell("def foo()", store_history=True) + # restore default excepthook + ip.set_custom_exc((), None) + assert hook.call_count == 1 + assert ip.execution_count == before + 1 + + +def test_run_cell_async(): + ip.run_cell("import asyncio") + coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5") + assert asyncio.iscoroutine(coro) + loop = asyncio.new_event_loop() + result = loop.run_until_complete(coro) + assert isinstance(result, interactiveshell.ExecutionResult) + assert result.result == 5 + + +def test_run_cell_await(): + ip.run_cell("import asyncio") + result = ip.run_cell("await asyncio.sleep(0.01); 10") + assert ip.user_ns["_"] == 10 + + +def test_run_cell_asyncio_run(): + ip.run_cell("import asyncio") + result = ip.run_cell("await asyncio.sleep(0.01); 1") + assert ip.user_ns["_"] == 1 + result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2") + assert ip.user_ns["_"] == 2 + result = ip.run_cell("await asyncio.sleep(0.01); 3") + assert ip.user_ns["_"] == 3 + + +def test_should_run_async(): + assert not ip.should_run_async("a = 5", transformed_cell="a = 5") + assert ip.should_run_async("await x", transformed_cell="await x") + assert ip.should_run_async( + "import asyncio; await asyncio.sleep(1)", + transformed_cell="import asyncio; await asyncio.sleep(1)", + ) + + +def test_set_custom_completer(): + num_completers = len(ip.Completer.matchers) + + def foo(*args, **kwargs): + return "I'm a completer!" + + ip.set_custom_completer(foo, 0) + + # check that we've really added a new completer + assert len(ip.Completer.matchers) == num_completers + 1 + + # check that the first completer is the function we defined + assert ip.Completer.matchers[0]() == "I'm a completer!" + + # clean up + ip.Completer.custom_matchers.pop() + + +class TestShowTracebackAttack(unittest.TestCase): + """Test that the interactive shell is resilient against the client attack of + manipulating the showtracebacks method. These attacks shouldn't result in an + unhandled exception in the kernel.""" + + def setUp(self): + self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback + + def tearDown(self): + interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback + + def test_set_show_tracebacks_none(self): + """Test the case of the client setting showtracebacks to None""" + + result = ip.run_cell( + """ + import IPython.core.interactiveshell + IPython.core.interactiveshell.InteractiveShell.showtraceback = None + + assert False, "This should not raise an exception" + """ + ) + print(result) + + assert result.result is None + assert isinstance(result.error_in_exec, TypeError) + assert str(result.error_in_exec) == "'NoneType' object is not callable" + + def test_set_show_tracebacks_noop(self): + """Test the case of the client setting showtracebacks to a no op lambda""" + + result = ip.run_cell( + """ + import IPython.core.interactiveshell + IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None + + assert False, "This should not raise an exception" + """ + ) + print(result) + + assert result.result is None + assert isinstance(result.error_in_exec, AssertionError) + assert str(result.error_in_exec) == "This should not raise an exception" + + +@skip_if_not_osx +def test_enable_gui_osx(): + simple_prompt = ip.simple_prompt + ip.simple_prompt = False + + ip.enable_gui("osx") + assert ip.active_eventloop == "osx" + ip.enable_gui() + + # The following line fails for IPython <= 8.25.0 + ip.enable_gui("macosx") + assert ip.active_eventloop == "osx" + ip.enable_gui() + + ip.simple_prompt = simple_prompt diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_iplib.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_iplib.py new file mode 100644 index 00000000..a5e51fbc --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_iplib.py @@ -0,0 +1,246 @@ +"""Tests for the key interactiveshell module, where the main ipython class is defined. +""" + +import stack_data +import sys + +SV_VERSION = tuple([int(x) for x in stack_data.__version__.split(".")[0:2]]) + + +def test_reset(): + """reset must clear most namespaces.""" + + # Check that reset runs without error + ip.reset() + + # Once we've reset it (to clear of any junk that might have been there from + # other tests, we can count how many variables are in the user's namespace + nvars_user_ns = len(ip.user_ns) + nvars_hidden = len(ip.user_ns_hidden) + + # Now add a few variables to user_ns, and check that reset clears them + ip.user_ns['x'] = 1 + ip.user_ns['y'] = 1 + ip.reset() + + # Finally, check that all namespaces have only as many variables as we + # expect to find in them: + assert len(ip.user_ns) == nvars_user_ns + assert len(ip.user_ns_hidden) == nvars_hidden + + +# Tests for reporting of exceptions in various modes, handling of SystemExit, +# and %tb functionality. This is really a mix of testing ultraTB and interactiveshell. + +def doctest_tb_plain(): + """ + In [18]: xmode plain + Exception reporting mode: Plain + + In [19]: run simpleerr.py + Traceback (most recent call last): + File ...:... + bar(mode) + File ...:... in bar + div0() + File ...:... in div0 + x/y + ZeroDivisionError: ... + """ + + +def doctest_tb_context(): + """ + In [3]: xmode context + Exception reporting mode: Context + + In [4]: run simpleerr.py + --------------------------------------------------------------------------- + ZeroDivisionError Traceback (most recent call last) + + ... + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) + + ... in bar(mode) + 15 "bar" + 16 if mode=='div': + ---> 17 div0() + 18 elif mode=='exit': + 19 try: + + ... in div0() + 6 x = 1 + 7 y = 0 + ----> 8 x/y + + ZeroDivisionError: ...""" + + +def doctest_tb_verbose(): + """ + In [5]: xmode verbose + Exception reporting mode: Verbose + + In [6]: run simpleerr.py + --------------------------------------------------------------------------- + ZeroDivisionError Traceback (most recent call last) + + ... + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) + mode = 'div' + + ... in bar(mode='div') + 15 "bar" + 16 if mode=='div': + ---> 17 div0() + 18 elif mode=='exit': + 19 try: + + ... in div0() + 6 x = 1 + 7 y = 0 + ----> 8 x/y + x = 1 + y = 0 + + ZeroDivisionError: ... + """ + + +def doctest_tb_sysexit(): + """ + In [17]: %xmode plain + Exception reporting mode: Plain + + In [18]: %run simpleerr.py exit + An exception has occurred, use %tb to see the full traceback. + SystemExit: (1, 'Mode = exit') + + In [19]: %run simpleerr.py exit 2 + An exception has occurred, use %tb to see the full traceback. + SystemExit: (2, 'Mode = exit') + + In [20]: %tb + Traceback (most recent call last): + File ...:... in execfile + exec(compiler(f.read(), fname, "exec"), glob, loc) + File ...:... + bar(mode) + File ...:... in bar + sysexit(stat, mode) + File ...:... in sysexit + raise SystemExit(stat, f"Mode = {mode}") + SystemExit: (2, 'Mode = exit') + + In [21]: %xmode context + Exception reporting mode: Context + + In [22]: %tb + --------------------------------------------------------------------------- + SystemExit Traceback (most recent call last) + File ..., in execfile(fname, glob, loc, compiler) + ... with open(fname, "rb") as f: + ... compiler = compiler or compile + ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc) + ... + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) + + ...bar(mode) + 21 except: + 22 stat = 1 + ---> 23 sysexit(stat, mode) + 24 else: + 25 raise ValueError('Unknown mode') + + ...sysexit(stat, mode) + 10 def sysexit(stat, mode): + ---> 11 raise SystemExit(stat, f"Mode = {mode}") + + SystemExit: (2, 'Mode = exit') + """ + + +if SV_VERSION < (0, 6): + + def doctest_tb_sysexit_verbose_stack_data_05(): + """ + In [18]: %run simpleerr.py exit + An exception has occurred, use %tb to see the full traceback. + SystemExit: (1, 'Mode = exit') + + In [19]: %run simpleerr.py exit 2 + An exception has occurred, use %tb to see the full traceback. + SystemExit: (2, 'Mode = exit') + + In [23]: %xmode verbose + Exception reporting mode: Verbose + + In [24]: %tb + --------------------------------------------------------------------------- + SystemExit Traceback (most recent call last) + + ... + 30 except IndexError: + 31 mode = 'div' + ---> 33 bar(mode) + mode = 'exit' + + ... in bar(mode='exit') + ... except: + ... stat = 1 + ---> ... sysexit(stat, mode) + mode = 'exit' + stat = 2 + ... else: + ... raise ValueError('Unknown mode') + + ... in sysexit(stat=2, mode='exit') + 10 def sysexit(stat, mode): + ---> 11 raise SystemExit(stat, f"Mode = {mode}") + stat = 2 + + SystemExit: (2, 'Mode = exit') + """ + + +def test_run_cell(): + import textwrap + + ip.run_cell("a = 10\na+=1") + ip.run_cell("assert a == 11\nassert 1") + + assert ip.user_ns["a"] == 11 + complex = textwrap.dedent( + """ + if 1: + print "hello" + if 1: + print "world" + + if 2: + print "foo" + + if 3: + print "bar" + + if 4: + print "bar" + + """ + ) + # Simply verifies that this kind of input is run + ip.run_cell(complex) + + +def test_db(): + """Test the internal database used for variable persistence.""" + ip.db["__unittest_"] = 12 + assert ip.db["__unittest_"] == 12 + del ip.db["__unittest_"] + assert "__unittest_" not in ip.db diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_logger.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_logger.py new file mode 100644 index 00000000..003f603f --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_logger.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +"""Test IPython.core.logger""" + +import os.path + +import pytest +from tempfile import TemporaryDirectory + + +def test_logstart_inaccessible_file(): + with pytest.raises(IOError): + _ip.logger.logstart(logfname="/") # Opening that filename will fail. + + try: + _ip.run_cell("a=1") # Check it doesn't try to log this + finally: + _ip.logger.log_active = False # If this fails, don't let later tests fail + +def test_logstart_unicode(): + with TemporaryDirectory() as tdir: + logfname = os.path.join(tdir, "test_unicode.log") + _ip.run_cell("'abc€'") + try: + _ip.run_line_magic("logstart", "-to %s" % logfname) + _ip.run_cell("'abc€'") + finally: + _ip.logger.logstop() diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_magic.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_magic.py new file mode 100644 index 00000000..036f250d --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_magic.py @@ -0,0 +1,1556 @@ +# -*- coding: utf-8 -*- +"""Tests for various magic functions.""" + +import gc +import io +import os +import re +import shlex +import sys +import warnings +from importlib import invalidate_caches +from io import StringIO +from pathlib import Path +from textwrap import dedent +from unittest import TestCase, mock + +import pytest + +from IPython import get_ipython +from IPython.core import magic +from IPython.core.error import UsageError +from IPython.core.magic import ( + Magics, + cell_magic, + line_magic, + magics_class, + register_cell_magic, + register_line_magic, +) +from IPython.core.magics import code, execution, logging, osm, script +from IPython.testing import decorators as dec +from IPython.testing import tools as tt +from IPython.utils.io import capture_output +from IPython.utils.process import find_cmd +from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory +from IPython.utils.syspathcontext import prepended_to_syspath + +from .test_debugger import PdbTestInput + +from tempfile import NamedTemporaryFile + +@magic.magics_class +class DummyMagics(magic.Magics): pass + +def test_extract_code_ranges(): + instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :" + expected = [ + (0, 1), + (2, 3), + (4, 6), + (6, 9), + (9, 14), + (16, None), + (None, 9), + (9, None), + (None, 13), + (None, None), + ] + actual = list(code.extract_code_ranges(instr)) + assert actual == expected + +def test_extract_symbols(): + source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n""" + symbols_args = ["a", "b", "A", "A,b", "A,a", "z"] + expected = [([], ['a']), + (["def b():\n return 42\n"], []), + (["class A: pass\n"], []), + (["class A: pass\n", "def b():\n return 42\n"], []), + (["class A: pass\n"], ['a']), + ([], ['z'])] + for symbols, exp in zip(symbols_args, expected): + assert code.extract_symbols(source, symbols) == exp + + +def test_extract_symbols_raises_exception_with_non_python_code(): + source = ("=begin A Ruby program :)=end\n" + "def hello\n" + "puts 'Hello world'\n" + "end") + with pytest.raises(SyntaxError): + code.extract_symbols(source, "hello") + + +def test_magic_not_found(): + # magic not found raises UsageError + with pytest.raises(UsageError): + _ip.run_line_magic("doesntexist", "") + + # ensure result isn't success when a magic isn't found + result = _ip.run_cell('%doesntexist') + assert isinstance(result.error_in_exec, UsageError) + + +def test_cell_magic_not_found(): + # magic not found raises UsageError + with pytest.raises(UsageError): + _ip.run_cell_magic('doesntexist', 'line', 'cell') + + # ensure result isn't success when a magic isn't found + result = _ip.run_cell('%%doesntexist') + assert isinstance(result.error_in_exec, UsageError) + + +def test_magic_error_status(): + def fail(shell): + 1/0 + _ip.register_magic_function(fail) + result = _ip.run_cell('%fail') + assert isinstance(result.error_in_exec, ZeroDivisionError) + + +def test_config(): + """ test that config magic does not raise + can happen if Configurable init is moved too early into + Magics.__init__ as then a Config object will be registered as a + magic. + """ + ## should not raise. + _ip.run_line_magic("config", "") + + +def test_config_available_configs(): + """ test that config magic prints available configs in unique and + sorted order. """ + with capture_output() as captured: + _ip.run_line_magic("config", "") + + stdout = captured.stdout + config_classes = stdout.strip().split('\n')[1:] + assert config_classes == sorted(set(config_classes)) + +def test_config_print_class(): + """ test that config with a classname prints the class's options. """ + with capture_output() as captured: + _ip.run_line_magic("config", "TerminalInteractiveShell") + + stdout = captured.stdout + assert re.match( + "TerminalInteractiveShell.* options", stdout.splitlines()[0] + ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'" + + +def test_rehashx(): + # clear up everything + _ip.alias_manager.clear_aliases() + del _ip.db['syscmdlist'] + + _ip.run_line_magic("rehashx", "") + # Practically ALL ipython development systems will have more than 10 aliases + + assert len(_ip.alias_manager.aliases) > 10 + for name, cmd in _ip.alias_manager.aliases: + # we must strip dots from alias names + assert "." not in name + + # rehashx must fill up syscmdlist + scoms = _ip.db['syscmdlist'] + assert len(scoms) > 10 + + +def test_magic_parse_options(): + """Test that we don't mangle paths when parsing magic options.""" + ip = get_ipython() + path = 'c:\\x' + m = DummyMagics(ip) + opts = m.parse_options('-f %s' % path,'f:')[0] + # argv splitting is os-dependent + if os.name == 'posix': + expected = 'c:x' + else: + expected = path + assert opts["f"] == expected + + +def test_magic_parse_long_options(): + """Magic.parse_options can handle --foo=bar long options""" + ip = get_ipython() + m = DummyMagics(ip) + opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=") + assert "foo" in opts + assert "bar" in opts + assert opts["bar"] == "bubble" + + +def doctest_hist_f(): + """Test %hist -f with temporary filename. + + In [9]: import tempfile + + In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-') + + In [11]: %hist -nl -f $tfile 3 + + In [13]: import os; os.unlink(tfile) + """ + + +def doctest_hist_op(): + """Test %hist -op + + In [1]: class b(float): + ...: pass + ...: + + In [2]: class s(object): + ...: def __str__(self): + ...: return 's' + ...: + + In [3]: + + In [4]: class r(b): + ...: def __repr__(self): + ...: return 'r' + ...: + + In [5]: class sr(s,r): pass + ...: + + In [6]: + + In [7]: bb=b() + + In [8]: ss=s() + + In [9]: rr=r() + + In [10]: ssrr=sr() + + In [11]: 4.5 + Out[11]: 4.5 + + In [12]: str(ss) + Out[12]: 's' + + In [13]: + + In [14]: %hist -op + >>> class b: + ... pass + ... + >>> class s(b): + ... def __str__(self): + ... return 's' + ... + >>> + >>> class r(b): + ... def __repr__(self): + ... return 'r' + ... + >>> class sr(s,r): pass + >>> + >>> bb=b() + >>> ss=s() + >>> rr=r() + >>> ssrr=sr() + >>> 4.5 + 4.5 + >>> str(ss) + 's' + >>> + """ + +def test_hist_pof(): + ip = get_ipython() + ip.run_cell("1+2", store_history=True) + #raise Exception(ip.history_manager.session_number) + #raise Exception(list(ip.history_manager._get_range_session())) + with TemporaryDirectory() as td: + tf = os.path.join(td, 'hist.py') + ip.run_line_magic('history', '-pof %s' % tf) + assert os.path.isfile(tf) + + +def test_macro(): + ip = get_ipython() + ip.history_manager.reset() # Clear any existing history. + cmds = ["a=1", "def b():\n return a**2", "print(a,b())"] + for i, cmd in enumerate(cmds, start=1): + ip.history_manager.store_inputs(i, cmd) + ip.run_line_magic("macro", "test 1-3") + assert ip.user_ns["test"].value == "\n".join(cmds) + "\n" + + # List macros + assert "test" in ip.run_line_magic("macro", "") + + +def test_macro_run(): + """Test that we can run a multi-line macro successfully.""" + ip = get_ipython() + ip.history_manager.reset() + cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"] + for cmd in cmds: + ip.run_cell(cmd, store_history=True) + assert ip.user_ns["test"].value == "a+=1\nprint(a)\n" + with tt.AssertPrints("12"): + ip.run_cell("test") + with tt.AssertPrints("13"): + ip.run_cell("test") + + +def test_magic_magic(): + """Test %magic""" + ip = get_ipython() + with capture_output() as captured: + ip.run_line_magic("magic", "") + + stdout = captured.stdout + assert "%magic" in stdout + assert "IPython" in stdout + assert "Available" in stdout + + +@dec.skipif_not_numpy +def test_numpy_reset_array_undec(): + "Test '%reset array' functionality" + _ip.ex("import numpy as np") + _ip.ex("a = np.empty(2)") + assert "a" in _ip.user_ns + _ip.run_line_magic("reset", "-f array") + assert "a" not in _ip.user_ns + + +def test_reset_out(): + "Test '%reset out' magic" + _ip.run_cell("parrot = 'dead'", store_history=True) + # test '%reset -f out', make an Out prompt + _ip.run_cell("parrot", store_history=True) + assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")] + _ip.run_line_magic("reset", "-f out") + assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")] + assert len(_ip.user_ns["Out"]) == 0 + + +def test_reset_in(): + "Test '%reset in' magic" + # test '%reset -f in' + _ip.run_cell("parrot", store_history=True) + assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] + _ip.run_line_magic("reset", "-f in") + assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")] + assert len(set(_ip.user_ns["In"])) == 1 + + +def test_reset_dhist(): + "Test '%reset dhist' magic" + _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing + _ip.run_line_magic("cd", os.path.dirname(pytest.__file__)) + _ip.run_line_magic("cd", "-") + assert len(_ip.user_ns["_dh"]) > 0 + _ip.run_line_magic("reset", "-f dhist") + assert len(_ip.user_ns["_dh"]) == 0 + _ip.run_cell("_dh = [d for d in tmp]") # restore + + +def test_reset_in_length(): + "Test that '%reset in' preserves In[] length" + _ip.run_cell("print 'foo'") + _ip.run_cell("reset -f in") + assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1 + + +class TestResetErrors(TestCase): + + def test_reset_redefine(self): + + @magics_class + class KernelMagics(Magics): + @line_magic + def less(self, shell): pass + + _ip.register_magics(KernelMagics) + + with self.assertLogs() as cm: + # hack, we want to just capture logs, but assertLogs fails if not + # logs get produce. + # so log one things we ignore. + import logging as log_mod + log = log_mod.getLogger() + log.info('Nothing') + # end hack. + _ip.run_cell("reset -f") + + assert len(cm.output) == 1 + for out in cm.output: + assert "Invalid alias" not in out + +def test_tb_syntaxerror(): + """test %tb after a SyntaxError""" + ip = get_ipython() + ip.run_cell("for") + + # trap and validate stdout + save_stdout = sys.stdout + try: + sys.stdout = StringIO() + ip.run_cell("%tb") + out = sys.stdout.getvalue() + finally: + sys.stdout = save_stdout + # trim output, and only check the last line + last_line = out.rstrip().splitlines()[-1].strip() + assert last_line == "SyntaxError: invalid syntax" + + +def test_time(): + ip = get_ipython() + + with tt.AssertPrints("Wall time: "): + ip.run_cell("%time None") + + ip.run_cell("def f(kmjy):\n" + " %time print (2*kmjy)") + + with tt.AssertPrints("Wall time: "): + with tt.AssertPrints("hihi", suppress=False): + ip.run_cell("f('hi')") + + +# ';' at the end of %time prevents instruction value to be printed. +# This tests fix for #13837. +def test_time_no_output_with_semicolon(): + ip = get_ipython() + + # Test %time cases + with tt.AssertPrints(" 123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%time 123000+456") + + with tt.AssertNotPrints(" 123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%time 123000+456;") + + with tt.AssertPrints(" 123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%time 123000+456 # Comment") + + with tt.AssertNotPrints(" 123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%time 123000+456; # Comment") + + with tt.AssertPrints(" 123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%time 123000+456 # ;Comment") + + # Test %%time cases + with tt.AssertPrints("123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%%time\n123000+456\n\n\n") + + with tt.AssertNotPrints("123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%%time\n123000+456;\n\n\n") + + with tt.AssertPrints("123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%%time\n123000+456 # Comment\n\n\n") + + with tt.AssertNotPrints("123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%%time\n123000+456; # Comment\n\n\n") + + with tt.AssertPrints("123456"): + with tt.AssertPrints("Wall time: ", suppress=False): + with tt.AssertPrints("CPU times: ", suppress=False): + ip.run_cell("%%time\n123000+456 # ;Comment\n\n\n") + + +def test_time_last_not_expression(): + ip.run_cell("%%time\n" + "var_1 = 1\n" + "var_2 = 2\n") + assert ip.user_ns['var_1'] == 1 + del ip.user_ns['var_1'] + assert ip.user_ns['var_2'] == 2 + del ip.user_ns['var_2'] + + +@dec.skip_win32 +def test_time2(): + ip = get_ipython() + + with tt.AssertPrints("CPU times: user "): + ip.run_cell("%time None") + +def test_time3(): + """Erroneous magic function calls, issue gh-3334""" + ip = get_ipython() + ip.user_ns.pop('run', None) + + with tt.AssertNotPrints("not found", channel='stderr'): + ip.run_cell("%%time\n" + "run = 0\n" + "run += 1") + +def test_multiline_time(): + """Make sure last statement from time return a value.""" + ip = get_ipython() + ip.user_ns.pop('run', None) + + ip.run_cell( + dedent( + """\ + %%time + a = "ho" + b = "hey" + a+b + """ + ) + ) + assert ip.user_ns_hidden["_"] == "hohey" + + +def test_time_local_ns(): + """ + Test that local_ns is actually global_ns when running a cell magic + """ + ip = get_ipython() + ip.run_cell("%%time\n" "myvar = 1") + assert ip.user_ns["myvar"] == 1 + del ip.user_ns["myvar"] + + +def test_time_microseconds_display(): + """Ensure ASCII is used when necessary""" + with mock.patch("sys.stdout", io.TextIOWrapper(StringIO(), encoding="utf-8")): + assert execution._format_time(0.000001) == "1 \u03bcs" + with mock.patch("sys.stdout", io.TextIOWrapper(StringIO(), encoding="ascii")): + assert execution._format_time(0.000001) == "1 us" + + +# Test %%capture magic. Added to test issue #13926 +def test_capture(): + ip = get_ipython() + + # Test %%capture nominal case + ip.run_cell("%%capture abc\n1+2") + with tt.AssertPrints("True", suppress=False): + ip.run_cell("'abc' in locals()") + with tt.AssertPrints("True", suppress=False): + ip.run_cell("'outputs' in dir(abc)") + with tt.AssertPrints("3", suppress=False): + ip.run_cell("abc.outputs[0]") + + # Test %%capture with ';' at end of expression + ip.run_cell("%%capture abc\n7+8;") + with tt.AssertPrints("False", suppress=False): + ip.run_cell("'abc' in locals()") + + +def test_doctest_mode(): + "Toggle doctest_mode twice, it should be a no-op and run without error" + _ip.run_line_magic("doctest_mode", "") + _ip.run_line_magic("doctest_mode", "") + + +def test_parse_options(): + """Tests for basic options parsing in magics.""" + # These are only the most minimal of tests, more should be added later. At + # the very least we check that basic text/unicode calls work OK. + m = DummyMagics(_ip) + assert m.parse_options("foo", "")[1] == "foo" + assert m.parse_options("foo", "")[1] == "foo" + + +def test_parse_options_preserve_non_option_string(): + """Test to assert preservation of non-option part of magic-block, while parsing magic options.""" + m = DummyMagics(_ip) + opts, stmt = m.parse_options( + " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True + ) + assert opts == {"n": "1", "r": "13"} + assert stmt == "_ = 314 + foo" + + +def test_run_magic_preserve_code_block(): + """Test to assert preservation of non-option part of magic-block, while running magic.""" + _ip.user_ns["spaces"] = [] + _ip.run_line_magic( + "timeit", "-n1 -r1 spaces.append([s.count(' ') for s in ['document']])" + ) + assert _ip.user_ns["spaces"] == [[0]] + + +def test_dirops(): + """Test various directory handling operations.""" + # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/') + curpath = os.getcwd + startdir = os.getcwd() + ipdir = os.path.realpath(_ip.ipython_dir) + try: + _ip.run_line_magic("cd", '"%s"' % ipdir) + assert curpath() == ipdir + _ip.run_line_magic("cd", "-") + assert curpath() == startdir + _ip.run_line_magic("pushd", '"%s"' % ipdir) + assert curpath() == ipdir + _ip.run_line_magic("popd", "") + assert curpath() == startdir + finally: + os.chdir(startdir) + + +def test_cd_force_quiet(): + """Test OSMagics.cd_force_quiet option""" + _ip.config.OSMagics.cd_force_quiet = True + osmagics = osm.OSMagics(shell=_ip) + + startdir = os.getcwd() + ipdir = os.path.realpath(_ip.ipython_dir) + + try: + with tt.AssertNotPrints(ipdir): + osmagics.cd('"%s"' % ipdir) + with tt.AssertNotPrints(startdir): + osmagics.cd('-') + finally: + os.chdir(startdir) + + +def test_xmode(): + # Calling xmode three times should be a no-op + xmode = _ip.InteractiveTB.mode + for i in range(4): + _ip.run_line_magic("xmode", "") + assert _ip.InteractiveTB.mode == xmode + +def test_reset_hard(): + monitor = [] + class A(object): + def __del__(self): + monitor.append(1) + def __repr__(self): + return "" + + _ip.user_ns["a"] = A() + _ip.run_cell("a") + + assert monitor == [] + _ip.run_line_magic("reset", "-f") + assert monitor == [1] + +class TestXdel(tt.TempFileMixin): + def test_xdel(self): + """Test that references from %run are cleared by xdel.""" + src = ("class A(object):\n" + " monitor = []\n" + " def __del__(self):\n" + " self.monitor.append(1)\n" + "a = A()\n") + self.mktmp(src) + # %run creates some hidden references... + _ip.run_line_magic("run", "%s" % self.fname) + # ... as does the displayhook. + _ip.run_cell("a") + + monitor = _ip.user_ns["A"].monitor + assert monitor == [] + + _ip.run_line_magic("xdel", "a") + + # Check that a's __del__ method has been called. + gc.collect(0) + assert monitor == [1] + +def doctest_who(): + """doctest for %who + + In [1]: %reset -sf + + In [2]: alpha = 123 + + In [3]: beta = 'beta' + + In [4]: %who int + alpha + + In [5]: %who str + beta + + In [6]: %whos + Variable Type Data/Info + ---------------------------- + alpha int 123 + beta str beta + + In [7]: %who_ls + Out[7]: ['alpha', 'beta'] + """ + +def test_whos(): + """Check that whos is protected against objects where repr() fails.""" + class A(object): + def __repr__(self): + raise Exception() + _ip.user_ns['a'] = A() + _ip.run_line_magic("whos", "") + +def doctest_precision(): + """doctest for %precision + + In [1]: f = get_ipython().display_formatter.formatters['text/plain'] + + In [2]: %precision 5 + Out[2]: '%.5f' + + In [3]: f.float_format + Out[3]: '%.5f' + + In [4]: %precision %e + Out[4]: '%e' + + In [5]: f(3.1415927) + Out[5]: '3.141593e+00' + """ + + +def test_debug_magic(): + """Test debugging a small code with %debug + + In [1]: with PdbTestInput(['c']): + ...: %debug print("a b") #doctest: +ELLIPSIS + ...: + ... + ipdb> c + a b + In [2]: + """ + + +def test_debug_magic_locals(): + """Test debugging a small code with %debug with locals + + In [1]: with PdbTestInput(['c']): + ...: def fun(): + ...: res = 1 + ...: %debug print(res) + ...: fun() + ...: + ... + ipdb> c + 1 + In [2]: + """ + +def test_psearch(): + with tt.AssertPrints("dict.fromkeys"): + _ip.run_cell("dict.fr*?") + with tt.AssertPrints("π.is_integer"): + _ip.run_cell("π = 3.14;\nπ.is_integ*?") + +def test_timeit_shlex(): + """test shlex issues with timeit (#1109)""" + _ip.ex("def f(*a,**kw): pass") + _ip.run_line_magic("timeit", '-n1 "this is a bug".count(" ")') + _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1)') + _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1, " ", 2, " ")') + _ip.run_line_magic("timeit", '-r1 -n1 ("a " + "b")') + _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b")') + _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b ")') + + +def test_timeit_special_syntax(): + "Test %%timeit with IPython special syntax" + @register_line_magic + def lmagic(line): + ip = get_ipython() + ip.user_ns['lmagic_out'] = line + + # line mode test + _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line") + assert _ip.user_ns["lmagic_out"] == "my line" + # cell mode test + _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2") + assert _ip.user_ns["lmagic_out"] == "my line2" + + +def test_timeit_return(): + """ + test whether timeit -o return object + """ + + res = _ip.run_line_magic('timeit','-n10 -r10 -o 1') + assert(res is not None) + +def test_timeit_quiet(): + """ + test quiet option of timeit magic + """ + with tt.AssertNotPrints("loops"): + _ip.run_cell("%timeit -n1 -r1 -q 1") + +def test_timeit_return_quiet(): + with tt.AssertNotPrints("loops"): + res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1') + assert (res is not None) + +def test_timeit_invalid_return(): + with pytest.raises(SyntaxError): + _ip.run_line_magic('timeit', 'return') + +@dec.skipif(execution.profile is None) +def test_prun_special_syntax(): + "Test %%prun with IPython special syntax" + @register_line_magic + def lmagic(line): + ip = get_ipython() + ip.user_ns['lmagic_out'] = line + + # line mode test + _ip.run_line_magic("prun", "-q %lmagic my line") + assert _ip.user_ns["lmagic_out"] == "my line" + # cell mode test + _ip.run_cell_magic("prun", "-q", "%lmagic my line2") + assert _ip.user_ns["lmagic_out"] == "my line2" + + +@dec.skipif(execution.profile is None) +def test_prun_quotes(): + "Test that prun does not clobber string escapes (GH #1302)" + _ip.run_line_magic("prun", r"-q x = '\t'") + assert _ip.user_ns["x"] == "\t" + + +def test_extension(): + # Debugging information for failures of this test + print('sys.path:') + for p in sys.path: + print(' ', p) + print('CWD', os.getcwd()) + + pytest.raises(ImportError, _ip.run_line_magic, "load_ext", "daft_extension") + daft_path = os.path.join(os.path.dirname(__file__), "daft_extension") + sys.path.insert(0, daft_path) + try: + _ip.user_ns.pop('arq', None) + invalidate_caches() # Clear import caches + _ip.run_line_magic("load_ext", "daft_extension") + assert _ip.user_ns["arq"] == 185 + _ip.run_line_magic("unload_ext", "daft_extension") + assert 'arq' not in _ip.user_ns + finally: + sys.path.remove(daft_path) + + +def test_notebook_export_json(): + pytest.importorskip("nbformat") + _ip = get_ipython() + _ip.history_manager.reset() # Clear any existing history. + cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"] + for i, cmd in enumerate(cmds, start=1): + _ip.history_manager.store_inputs(i, cmd) + with TemporaryDirectory() as td: + outfile = os.path.join(td, "nb.ipynb") + _ip.run_line_magic("notebook", "%s" % outfile) + + +class TestEnv(TestCase): + + def test_env(self): + env = _ip.run_line_magic("env", "") + self.assertTrue(isinstance(env, dict)) + + def test_env_secret(self): + env = _ip.run_line_magic("env", "") + hidden = "" + with mock.patch.dict( + os.environ, + { + "API_KEY": "abc123", + "SECRET_THING": "ssshhh", + "JUPYTER_TOKEN": "", + "VAR": "abc" + } + ): + env = _ip.run_line_magic("env", "") + assert env["API_KEY"] == hidden + assert env["SECRET_THING"] == hidden + assert env["JUPYTER_TOKEN"] == hidden + assert env["VAR"] == "abc" + + def test_env_get_set_simple(self): + env = _ip.run_line_magic("env", "var val1") + self.assertEqual(env, None) + self.assertEqual(os.environ["var"], "val1") + self.assertEqual(_ip.run_line_magic("env", "var"), "val1") + env = _ip.run_line_magic("env", "var=val2") + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], 'val2') + + def test_env_get_set_complex(self): + env = _ip.run_line_magic("env", "var 'val1 '' 'val2") + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], "'val1 '' 'val2") + self.assertEqual(_ip.run_line_magic("env", "var"), "'val1 '' 'val2") + env = _ip.run_line_magic("env", 'var=val2 val3="val4') + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], 'val2 val3="val4') + + def test_env_set_bad_input(self): + self.assertRaises(UsageError, lambda: _ip.run_line_magic("set_env", "var")) + + def test_env_set_whitespace(self): + self.assertRaises(UsageError, lambda: _ip.run_line_magic("env", "var A=B")) + + +class CellMagicTestCase(TestCase): + + def check_ident(self, magic): + # Manually called, we get the result + out = _ip.run_cell_magic(magic, "a", "b") + assert out == ("a", "b") + # Via run_cell, it goes into the user's namespace via displayhook + _ip.run_cell("%%" + magic + " c\nd\n") + assert _ip.user_ns["_"] == ("c", "d\n") + + def test_cell_magic_func_deco(self): + "Cell magic using simple decorator" + @register_cell_magic + def cellm(line, cell): + return line, cell + + self.check_ident('cellm') + + def test_cell_magic_reg(self): + "Cell magic manually registered" + def cellm(line, cell): + return line, cell + + _ip.register_magic_function(cellm, 'cell', 'cellm2') + self.check_ident('cellm2') + + def test_cell_magic_class(self): + "Cell magics declared via a class" + @magics_class + class MyMagics(Magics): + + @cell_magic + def cellm3(self, line, cell): + return line, cell + + _ip.register_magics(MyMagics) + self.check_ident('cellm3') + + def test_cell_magic_class2(self): + "Cell magics declared via a class, #2" + @magics_class + class MyMagics2(Magics): + + @cell_magic('cellm4') + def cellm33(self, line, cell): + return line, cell + + _ip.register_magics(MyMagics2) + self.check_ident('cellm4') + # Check that nothing is registered as 'cellm33' + c33 = _ip.find_cell_magic('cellm33') + assert c33 == None + +def test_file(): + """Basic %%writefile""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, "file1") + ip.run_cell_magic( + "writefile", + fname, + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line2" in s + + +@dec.skip_win32 +def test_file_single_quote(): + """Basic %%writefile with embedded single quotes""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, "'file1'") + ip.run_cell_magic( + "writefile", + fname, + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line2" in s + + +@dec.skip_win32 +def test_file_double_quote(): + """Basic %%writefile with embedded double quotes""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, '"file1"') + ip.run_cell_magic( + "writefile", + fname, + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line2" in s + + +def test_file_var_expand(): + """%%writefile $filename""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, "file1") + ip.user_ns["filename"] = fname + ip.run_cell_magic( + "writefile", + "$filename", + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line2" in s + + +def test_file_unicode(): + """%%writefile with unicode cell""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, 'file1') + ip.run_cell_magic("writefile", fname, u'\n'.join([ + u'liné1', + u'liné2', + ])) + with io.open(fname, encoding='utf-8') as f: + s = f.read() + assert "liné1\n" in s + assert "liné2" in s + + +def test_file_amend(): + """%%writefile -a amends files""" + ip = get_ipython() + with TemporaryDirectory() as td: + fname = os.path.join(td, "file2") + ip.run_cell_magic( + "writefile", + fname, + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + ip.run_cell_magic( + "writefile", + "-a %s" % fname, + "\n".join( + [ + "line3", + "line4", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line3\n" in s + + +def test_file_spaces(): + """%%file with spaces in filename""" + ip = get_ipython() + with TemporaryWorkingDirectory() as td: + fname = "file name" + ip.run_cell_magic( + "file", + '"%s"' % fname, + "\n".join( + [ + "line1", + "line2", + ] + ), + ) + s = Path(fname).read_text(encoding="utf-8") + assert "line1\n" in s + assert "line2" in s + + +def test_script_config(): + ip = get_ipython() + ip.config.ScriptMagics.script_magics = ['whoda'] + sm = script.ScriptMagics(shell=ip) + assert "whoda" in sm.magics["cell"] + + +def test_script_out(): + ip = get_ipython() + ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')") + assert ip.user_ns["output"].strip() == "hi" + + +def test_script_err(): + ip = get_ipython() + ip.run_cell_magic( + "script", + f"--err error {sys.executable}", + "import sys; print('hello', file=sys.stderr)", + ) + assert ip.user_ns["error"].strip() == "hello" + + +def test_script_out_err(): + ip = get_ipython() + ip.run_cell_magic( + "script", + f"--out output --err error {sys.executable}", + "\n".join( + [ + "import sys", + "print('hi')", + "print('hello', file=sys.stderr)", + ] + ), + ) + assert ip.user_ns["output"].strip() == "hi" + assert ip.user_ns["error"].strip() == "hello" + + +async def test_script_bg_out(): + ip = get_ipython() + ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')") + assert (await ip.user_ns["output"].read()).strip() == b"hi" + assert ip.user_ns["output"].at_eof() + + +async def test_script_bg_err(): + ip = get_ipython() + ip.run_cell_magic( + "script", + f"--bg --err error {sys.executable}", + "import sys; print('hello', file=sys.stderr)", + ) + assert (await ip.user_ns["error"].read()).strip() == b"hello" + assert ip.user_ns["error"].at_eof() + + +async def test_script_bg_out_err(): + ip = get_ipython() + ip.run_cell_magic( + "script", + f"--bg --out output --err error {sys.executable}", + "\n".join( + [ + "import sys", + "print('hi')", + "print('hello', file=sys.stderr)", + ] + ), + ) + assert (await ip.user_ns["output"].read()).strip() == b"hi" + assert (await ip.user_ns["error"].read()).strip() == b"hello" + assert ip.user_ns["output"].at_eof() + assert ip.user_ns["error"].at_eof() + + +async def test_script_bg_proc(): + ip = get_ipython() + ip.run_cell_magic( + "script", + f"--bg --out output --proc p {sys.executable}", + "\n".join( + [ + "import sys", + "print('hi')", + "print('hello', file=sys.stderr)", + ] + ), + ) + p = ip.user_ns["p"] + await p.wait() + assert p.returncode == 0 + assert (await p.stdout.read()).strip() == b"hi" + # not captured, so empty + assert (await p.stderr.read()) == b"" + assert p.stdout.at_eof() + assert p.stderr.at_eof() + + +def test_script_defaults(): + ip = get_ipython() + for cmd in ['sh', 'bash', 'perl', 'ruby']: + try: + find_cmd(cmd) + except Exception: + pass + else: + assert cmd in ip.magics_manager.magics["cell"] + + +@magics_class +class FooFoo(Magics): + """class with both %foo and %%foo magics""" + @line_magic('foo') + def line_foo(self, line): + "I am line foo" + pass + + @cell_magic("foo") + def cell_foo(self, line, cell): + "I am cell foo, not line foo" + pass + +def test_line_cell_info(): + """%%foo and %foo magics are distinguishable to inspect""" + ip = get_ipython() + ip.magics_manager.register(FooFoo) + oinfo = ip.object_inspect("foo") + assert oinfo["found"] is True + assert oinfo["ismagic"] is True + + oinfo = ip.object_inspect("%%foo") + assert oinfo["found"] is True + assert oinfo["ismagic"] is True + assert oinfo["docstring"] == FooFoo.cell_foo.__doc__ + + oinfo = ip.object_inspect("%foo") + assert oinfo["found"] is True + assert oinfo["ismagic"] is True + assert oinfo["docstring"] == FooFoo.line_foo.__doc__ + + +def test_multiple_magics(): + ip = get_ipython() + foo1 = FooFoo(ip) + foo2 = FooFoo(ip) + mm = ip.magics_manager + mm.register(foo1) + assert mm.magics["line"]["foo"].__self__ is foo1 + mm.register(foo2) + assert mm.magics["line"]["foo"].__self__ is foo2 + + +def test_alias_magic(): + """Test %alias_magic.""" + ip = get_ipython() + mm = ip.magics_manager + + # Basic operation: both cell and line magics are created, if possible. + ip.run_line_magic("alias_magic", "timeit_alias timeit") + assert "timeit_alias" in mm.magics["line"] + assert "timeit_alias" in mm.magics["cell"] + + # --cell is specified, line magic not created. + ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit") + assert "timeit_cell_alias" not in mm.magics["line"] + assert "timeit_cell_alias" in mm.magics["cell"] + + # Test that line alias is created successfully. + ip.run_line_magic("alias_magic", "--line env_alias env") + assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "") + + # Test that line alias with parameters passed in is created successfully. + ip.run_line_magic( + "alias_magic", "--line history_alias history --params " + shlex.quote("3") + ) + assert "history_alias" in mm.magics["line"] + + +def test_save(): + """Test %save.""" + ip = get_ipython() + ip.history_manager.reset() # Clear any existing history. + cmds = ["a=1", "def b():\n return a**2", "print(a, b())"] + for i, cmd in enumerate(cmds, start=1): + ip.history_manager.store_inputs(i, cmd) + with TemporaryDirectory() as tmpdir: + file = os.path.join(tmpdir, "testsave.py") + ip.run_line_magic("save", "%s 1-10" % file) + content = Path(file).read_text(encoding="utf-8") + assert content.count(cmds[0]) == 1 + assert "coding: utf-8" in content + ip.run_line_magic("save", "-a %s 1-10" % file) + content = Path(file).read_text(encoding="utf-8") + assert content.count(cmds[0]) == 2 + assert "coding: utf-8" in content + + +def test_save_with_no_args(): + ip = get_ipython() + ip.history_manager.reset() # Clear any existing history. + cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"] + for i, cmd in enumerate(cmds, start=1): + ip.history_manager.store_inputs(i, cmd) + + with TemporaryDirectory() as tmpdir: + path = os.path.join(tmpdir, "testsave.py") + ip.run_line_magic("save", path) + content = Path(path).read_text(encoding="utf-8") + expected_content = dedent( + """\ + # coding: utf-8 + a=1 + def b(): + return a**2 + print(a, b()) + """ + ) + assert content == expected_content + + +def test_store(): + """Test %store.""" + ip = get_ipython() + ip.run_line_magic('load_ext', 'storemagic') + + # make sure the storage is empty + ip.run_line_magic("store", "-z") + ip.user_ns["var"] = 42 + ip.run_line_magic("store", "var") + ip.user_ns["var"] = 39 + ip.run_line_magic("store", "-r") + assert ip.user_ns["var"] == 42 + + ip.run_line_magic("store", "-d var") + ip.user_ns["var"] = 39 + ip.run_line_magic("store", "-r") + assert ip.user_ns["var"] == 39 + + +def _run_edit_test(arg_s, exp_filename=None, + exp_lineno=-1, + exp_contents=None, + exp_is_temp=None): + ip = get_ipython() + M = code.CodeMagics(ip) + last_call = ['',''] + opts,args = M.parse_options(arg_s,'prxn:') + filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call) + + if exp_filename is not None: + assert exp_filename == filename + if exp_contents is not None: + with io.open(filename, 'r', encoding='utf-8') as f: + contents = f.read() + assert exp_contents == contents + if exp_lineno != -1: + assert exp_lineno == lineno + if exp_is_temp is not None: + assert exp_is_temp == is_temp + + +def test_edit_interactive(): + """%edit on interactively defined objects""" + ip = get_ipython() + n = ip.execution_count + ip.run_cell("def foo(): return 1", store_history=True) + + with pytest.raises(code.InteractivelyDefined) as e: + _run_edit_test("foo") + assert e.value.index == n + + +def test_edit_cell(): + """%edit [cell id]""" + ip = get_ipython() + + ip.run_cell("def foo(): return 1", store_history=True) + + # test + _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True) + +def test_edit_fname(): + """%edit file""" + # test + _run_edit_test("test file.py", exp_filename="test file.py") + +def test_bookmark(): + ip = get_ipython() + ip.run_line_magic('bookmark', 'bmname') + with tt.AssertPrints('bmname'): + ip.run_line_magic('bookmark', '-l') + ip.run_line_magic('bookmark', '-d bmname') + +def test_ls_magic(): + ip = get_ipython() + json_formatter = ip.display_formatter.formatters['application/json'] + json_formatter.enabled = True + lsmagic = ip.run_line_magic("lsmagic", "") + with warnings.catch_warnings(record=True) as w: + j = json_formatter(lsmagic) + assert sorted(j) == ["cell", "line"] + assert w == [] # no warnings + + +def test_strip_initial_indent(): + def sii(s): + lines = s.splitlines() + return '\n'.join(code.strip_initial_indent(lines)) + + assert sii(" a = 1\nb = 2") == "a = 1\nb = 2" + assert sii(" a\n b\nc") == "a\n b\nc" + assert sii("a\n b") == "a\n b" + +def test_logging_magic_quiet_from_arg(): + _ip.config.LoggingMagics.quiet = False + lm = logging.LoggingMagics(shell=_ip) + with TemporaryDirectory() as td: + try: + with tt.AssertNotPrints(re.compile("Activating.*")): + lm.logstart('-q {}'.format( + os.path.join(td, "quiet_from_arg.log"))) + finally: + _ip.logger.logstop() + +def test_logging_magic_quiet_from_config(): + _ip.config.LoggingMagics.quiet = True + lm = logging.LoggingMagics(shell=_ip) + with TemporaryDirectory() as td: + try: + with tt.AssertNotPrints(re.compile("Activating.*")): + lm.logstart(os.path.join(td, "quiet_from_config.log")) + finally: + _ip.logger.logstop() + + +def test_logging_magic_not_quiet(): + _ip.config.LoggingMagics.quiet = False + lm = logging.LoggingMagics(shell=_ip) + with TemporaryDirectory() as td: + try: + with tt.AssertPrints(re.compile("Activating.*")): + lm.logstart(os.path.join(td, "not_quiet.log")) + finally: + _ip.logger.logstop() + + +def test_time_no_var_expand(): + _ip.user_ns["a"] = 5 + _ip.user_ns["b"] = [] + _ip.run_line_magic("time", 'b.append("{a}")') + assert _ip.user_ns["b"] == ["{a}"] + + +# this is slow, put at the end for local testing. +def test_timeit_arguments(): + "Test valid timeit arguments, should not cause SyntaxError (GH #1269)" + _ip.run_line_magic("timeit", "-n1 -r1 a=('#')") + + +MINIMAL_LAZY_MAGIC = """ +from IPython.core.magic import ( + Magics, + magics_class, + line_magic, + cell_magic, +) + + +@magics_class +class LazyMagics(Magics): + @line_magic + def lazy_line(self, line): + print("Lazy Line") + + @cell_magic + def lazy_cell(self, line, cell): + print("Lazy Cell") + + +def load_ipython_extension(ipython): + ipython.register_magics(LazyMagics) +""" + + +def test_lazy_magics(): + with pytest.raises(UsageError): + ip.run_line_magic("lazy_line", "") + + startdir = os.getcwd() + + with TemporaryDirectory() as tmpdir: + with prepended_to_syspath(tmpdir): + ptempdir = Path(tmpdir) + tf = ptempdir / "lazy_magic_module.py" + tf.write_text(MINIMAL_LAZY_MAGIC) + ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3]) + with tt.AssertPrints("Lazy Line"): + ip.run_line_magic("lazy_line", "") + + +TEST_MODULE = """ +print('Loaded my_tmp') +if __name__ == "__main__": + print('I just ran a script') +""" + +def test_run_module_from_import_hook(): + "Test that a module can be loaded via an import hook" + with TemporaryDirectory() as tmpdir: + fullpath = os.path.join(tmpdir, "my_tmp.py") + Path(fullpath).write_text(TEST_MODULE, encoding="utf-8") + + import importlib.abc + import importlib.util + + class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader): + def find_spec(self, fullname, path, target=None): + if fullname == "my_tmp": + return importlib.util.spec_from_loader(fullname, self) + + def get_filename(self, fullname): + assert fullname == "my_tmp" + return fullpath + + def get_data(self, path): + assert Path(path).samefile(fullpath) + return Path(fullpath).read_text(encoding="utf-8") + + sys.meta_path.insert(0, MyTempImporter()) + + with capture_output() as captured: + _ip.run_line_magic("run", "-m my_tmp") + _ip.run_cell("import my_tmp") + + output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n" + assert output == captured.stdout + + sys.meta_path.pop(0) diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_magic_arguments.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_magic_arguments.py new file mode 100644 index 00000000..0044163f --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_magic_arguments.py @@ -0,0 +1,142 @@ +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011, IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +import argparse +import sys + +from IPython.core.magic_arguments import (argument, argument_group, kwds, + magic_arguments, parse_argstring, real_name) + +LEADING_SPACE = "" if sys.version_info > (3, 13) else " " + + +@magic_arguments() +@argument('-f', '--foo', help="an argument") +def magic_foo1(self, args): + """ A docstring. + """ + return parse_argstring(magic_foo1, args) + + +@magic_arguments() +def magic_foo2(self, args): + """ A docstring. + """ + return parse_argstring(magic_foo2, args) + + +@magic_arguments() +@argument('-f', '--foo', help="an argument") +@argument_group('Group') +@argument('-b', '--bar', help="a grouped argument") +@argument_group('Second Group') +@argument('-z', '--baz', help="another grouped argument") +def magic_foo3(self, args): + """ A docstring. + """ + return parse_argstring(magic_foo3, args) + + +@magic_arguments() +@kwds(argument_default=argparse.SUPPRESS) +@argument('-f', '--foo', help="an argument") +def magic_foo4(self, args): + """ A docstring. + """ + return parse_argstring(magic_foo4, args) + + +@magic_arguments('frobnicate') +@argument('-f', '--foo', help="an argument") +def magic_foo5(self, args): + """ A docstring. + """ + return parse_argstring(magic_foo5, args) + + +@magic_arguments() +@argument('-f', '--foo', help="an argument") +def magic_magic_foo(self, args): + """ A docstring. + """ + return parse_argstring(magic_magic_foo, args) + + +@magic_arguments() +@argument('-f', '--foo', help="an argument") +def foo(self, args): + """ A docstring. + """ + return parse_argstring(foo, args) + + +def test_magic_arguments(): + # “optional arguments” was replaced with “options” in argparse help + # https://docs.python.org/3/whatsnew/3.10.html#argparse + # https://bugs.python.org/issue9694 + options = "optional arguments" if sys.version_info < (3, 10) else "options" + + assert ( + magic_foo1.__doc__ + == f"::\n\n %foo1 [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + ) + assert getattr(magic_foo1, "argcmd_name", None) == None + assert real_name(magic_foo1) == "foo1" + assert magic_foo1(None, "") == argparse.Namespace(foo=None) + assert hasattr(magic_foo1, "has_arguments") + + assert magic_foo2.__doc__ == f"::\n\n %foo2\n\n{LEADING_SPACE}A docstring.\n" + assert getattr(magic_foo2, "argcmd_name", None) == None + assert real_name(magic_foo2) == "foo2" + assert magic_foo2(None, "") == argparse.Namespace() + assert hasattr(magic_foo2, "has_arguments") + + assert ( + magic_foo3.__doc__ + == f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n" + ) + assert getattr(magic_foo3, "argcmd_name", None) == None + assert real_name(magic_foo3) == "foo3" + assert magic_foo3(None, "") == argparse.Namespace(bar=None, baz=None, foo=None) + assert hasattr(magic_foo3, "has_arguments") + + assert ( + magic_foo4.__doc__ + == f"::\n\n %foo4 [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + ) + assert getattr(magic_foo4, "argcmd_name", None) == None + assert real_name(magic_foo4) == "foo4" + assert magic_foo4(None, "") == argparse.Namespace() + assert hasattr(magic_foo4, "has_arguments") + + assert ( + magic_foo5.__doc__ + == f"::\n\n %frobnicate [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + ) + assert getattr(magic_foo5, "argcmd_name", None) == "frobnicate" + assert real_name(magic_foo5) == "frobnicate" + assert magic_foo5(None, "") == argparse.Namespace(foo=None) + assert hasattr(magic_foo5, "has_arguments") + + assert ( + magic_magic_foo.__doc__ + == f"::\n\n %magic_foo [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + ) + assert getattr(magic_magic_foo, "argcmd_name", None) == None + assert real_name(magic_magic_foo) == "magic_foo" + assert magic_magic_foo(None, "") == argparse.Namespace(foo=None) + assert hasattr(magic_magic_foo, "has_arguments") + + assert ( + foo.__doc__ + == f"::\n\n %foo [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + ) + assert getattr(foo, "argcmd_name", None) == None + assert real_name(foo) == "foo" + assert foo(None, "") == argparse.Namespace(foo=None) + assert hasattr(foo, "has_arguments") diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_magic_terminal.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_magic_terminal.py new file mode 100644 index 00000000..5dfa0f0e --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_magic_terminal.py @@ -0,0 +1,216 @@ +"""Tests for various magic functions specific to the terminal frontend.""" + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import sys +from io import StringIO +from unittest import TestCase + +from IPython.testing import tools as tt +#----------------------------------------------------------------------------- +# Test functions begin +#----------------------------------------------------------------------------- + + +MINIMAL_LAZY_MAGIC = """ +from IPython.core.magic import ( + Magics, + magics_class, + line_magic, + cell_magic, +) + + +@magics_class +class LazyMagics(Magics): + @line_magic + def lazy_line(self, line): + print("Lazy Line") + + @cell_magic + def lazy_cell(self, line, cell): + print("Lazy Cell") + + +def load_ipython_extension(ipython): + ipython.register_magics(LazyMagics) +""" + +def check_cpaste(code, should_fail=False): + """Execute code via 'cpaste' and ensure it was executed, unless + should_fail is set. + """ + ip.user_ns['code_ran'] = False + + src = StringIO() + src.write(code) + src.write('\n--\n') + src.seek(0) + + stdin_save = sys.stdin + sys.stdin = src + + try: + context = tt.AssertPrints if should_fail else tt.AssertNotPrints + with context("Traceback (most recent call last)"): + ip.run_line_magic("cpaste", "") + + if not should_fail: + assert ip.user_ns['code_ran'], "%r failed" % code + finally: + sys.stdin = stdin_save + +def test_cpaste(): + """Test cpaste magic""" + + def runf(): + """Marker function: sets a flag when executed. + """ + ip.user_ns['code_ran'] = True + return 'runf' # return string so '+ runf()' doesn't result in success + + tests = {'pass': ["runf()", + "In [1]: runf()", + "In [1]: if 1:\n ...: runf()", + "> > > runf()", + ">>> runf()", + " >>> runf()", + ], + + 'fail': ["1 + runf()", + "++ runf()", + ]} + + ip.user_ns['runf'] = runf + + for code in tests['pass']: + check_cpaste(code) + + for code in tests['fail']: + check_cpaste(code, should_fail=True) + + + +class PasteTestCase(TestCase): + """Multiple tests for clipboard pasting""" + + def paste(self, txt, flags='-q'): + """Paste input text, by default in quiet mode""" + ip.hooks.clipboard_get = lambda: txt + ip.run_line_magic("paste", flags) + + def setUp(self): + # Inject fake clipboard hook but save original so we can restore it later + self.original_clip = ip.hooks.clipboard_get + + def tearDown(self): + # Restore original hook + ip.hooks.clipboard_get = self.original_clip + + def test_paste(self): + ip.user_ns.pop("x", None) + self.paste("x = 1") + self.assertEqual(ip.user_ns["x"], 1) + ip.user_ns.pop("x") + + def test_paste_pyprompt(self): + ip.user_ns.pop("x", None) + self.paste(">>> x=2") + self.assertEqual(ip.user_ns["x"], 2) + ip.user_ns.pop("x") + + def test_paste_py_multi(self): + self.paste( + """ + >>> x = [1,2,3] + >>> y = [] + >>> for i in x: + ... y.append(i**2) + ... + """ + ) + self.assertEqual(ip.user_ns["x"], [1, 2, 3]) + self.assertEqual(ip.user_ns["y"], [1, 4, 9]) + + def test_paste_py_multi_r(self): + "Now, test that self.paste -r works" + self.test_paste_py_multi() + self.assertEqual(ip.user_ns.pop("x"), [1, 2, 3]) + self.assertEqual(ip.user_ns.pop("y"), [1, 4, 9]) + self.assertFalse("x" in ip.user_ns) + ip.run_line_magic("paste", "-r") + self.assertEqual(ip.user_ns["x"], [1, 2, 3]) + self.assertEqual(ip.user_ns["y"], [1, 4, 9]) + + def test_paste_email(self): + "Test pasting of email-quoted contents" + self.paste( + """\ + >> def foo(x): + >> return x + 1 + >> xx = foo(1.1)""" + ) + self.assertEqual(ip.user_ns["xx"], 2.1) + + def test_paste_email2(self): + "Email again; some programs add a space also at each quoting level" + self.paste( + """\ + > > def foo(x): + > > return x + 1 + > > yy = foo(2.1) """ + ) + self.assertEqual(ip.user_ns["yy"], 3.1) + + def test_paste_email_py(self): + "Email quoting of interactive input" + self.paste( + """\ + >> >>> def f(x): + >> ... return x+1 + >> ... + >> >>> zz = f(2.5) """ + ) + self.assertEqual(ip.user_ns["zz"], 3.5) + + def test_paste_echo(self): + "Also test self.paste echoing, by temporarily faking the writer" + w = StringIO() + old_write = sys.stdout.write + sys.stdout.write = w.write + code = """ + a = 100 + b = 200""" + try: + self.paste(code,'') + out = w.getvalue() + finally: + sys.stdout.write = old_write + self.assertEqual(ip.user_ns["a"], 100) + self.assertEqual(ip.user_ns["b"], 200) + assert out == code + "\n## -- End pasted text --\n" + + def test_paste_leading_commas(self): + "Test multiline strings with leading commas" + tm = ip.magics_manager.registry['TerminalMagics'] + s = '''\ +a = """ +,1,2,3 +"""''' + ip.user_ns.pop("foo", None) + tm.store_or_execute(s, "foo") + self.assertIn("foo", ip.user_ns) + + def test_paste_trailing_question(self): + "Test pasting sources with trailing question marks" + tm = ip.magics_manager.registry['TerminalMagics'] + s = '''\ +def funcfoo(): + if True: #am i true? + return 'fooresult' +''' + ip.user_ns.pop('funcfoo', None) + self.paste(s) + self.assertEqual(ip.user_ns["funcfoo"](), "fooresult") diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_oinspect.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_oinspect.py new file mode 100644 index 00000000..ac7c3656 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_oinspect.py @@ -0,0 +1,589 @@ +"""Tests for the object inspection functionality. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +from contextlib import contextmanager +from inspect import signature, Signature, Parameter +import inspect +import os +import pytest +import re +import sys + +from .. import oinspect + +from decorator import decorator + +from IPython.testing.tools import AssertPrints, AssertNotPrints +from IPython.utils.path import compress_user + + +#----------------------------------------------------------------------------- +# Globals and constants +#----------------------------------------------------------------------------- + +inspector = None + +def setup_module(): + global inspector + inspector = oinspect.Inspector() + + +class SourceModuleMainTest: + __module__ = "__main__" + + +#----------------------------------------------------------------------------- +# Local utilities +#----------------------------------------------------------------------------- + +# WARNING: since this test checks the line number where a function is +# defined, if any code is inserted above, the following line will need to be +# updated. Do NOT insert any whitespace between the next line and the function +# definition below. +THIS_LINE_NUMBER = 47 # Put here the actual number of this line + + +def test_find_source_lines(): + assert oinspect.find_source_lines(test_find_source_lines) == THIS_LINE_NUMBER + 3 + assert oinspect.find_source_lines(type) is None + assert oinspect.find_source_lines(SourceModuleMainTest) is None + assert oinspect.find_source_lines(SourceModuleMainTest()) is None + + +def test_getsource(): + assert oinspect.getsource(type) is None + assert oinspect.getsource(SourceModuleMainTest) is None + assert oinspect.getsource(SourceModuleMainTest()) is None + + +def test_inspect_getfile_raises_exception(): + """Check oinspect.find_file/getsource/find_source_lines expectations""" + with pytest.raises(TypeError): + inspect.getfile(type) + with pytest.raises(OSError): + inspect.getfile(SourceModuleMainTest) + + +# A couple of utilities to ensure these tests work the same from a source or a +# binary install +def pyfile(fname): + return os.path.normcase(re.sub('.py[co]$', '.py', fname)) + + +def match_pyfiles(f1, f2): + assert pyfile(f1) == pyfile(f2) + + +def test_find_file(): + match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__)) + assert oinspect.find_file(type) is None + assert oinspect.find_file(SourceModuleMainTest) is None + assert oinspect.find_file(SourceModuleMainTest()) is None + + +def test_find_file_decorated1(): + + @decorator + def noop1(f): + def wrapper(*a, **kw): + return f(*a, **kw) + return wrapper + + @noop1 + def f(x): + "My docstring" + + match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) + assert f.__doc__ == "My docstring" + + +def test_find_file_decorated2(): + + @decorator + def noop2(f, *a, **kw): + return f(*a, **kw) + + @noop2 + @noop2 + @noop2 + def f(x): + "My docstring 2" + + match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) + assert f.__doc__ == "My docstring 2" + + +def test_find_file_magic(): + run = ip.find_line_magic('run') + assert oinspect.find_file(run) is not None + + +# A few generic objects we can then inspect in the tests below + +class Call(object): + """This is the class docstring.""" + + def __init__(self, x, y=1): + """This is the constructor docstring.""" + + def __call__(self, *a, **kw): + """This is the call docstring.""" + + def method(self, x, z=2): + """Some method's docstring""" + +class HasSignature(object): + """This is the class docstring.""" + __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)]) + + def __init__(self, *args): + """This is the init docstring""" + + +class SimpleClass(object): + def method(self, x, z=2): + """Some method's docstring""" + + +class Awkward(object): + def __getattr__(self, name): + raise Exception(name) + +class NoBoolCall: + """ + callable with `__bool__` raising should still be inspect-able. + """ + + def __call__(self): + """does nothing""" + pass + + def __bool__(self): + """just raise NotImplemented""" + raise NotImplementedError('Must be implemented') + + +class SerialLiar(object): + """Attribute accesses always get another copy of the same class. + + unittest.mock.call does something similar, but it's not ideal for testing + as the failure mode is to eat all your RAM. This gives up after 10k levels. + """ + def __init__(self, max_fibbing_twig, lies_told=0): + if lies_told > 10000: + raise RuntimeError('Nose too long, honesty is the best policy') + self.max_fibbing_twig = max_fibbing_twig + self.lies_told = lies_told + max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told) + + def __getattr__(self, item): + return SerialLiar(self.max_fibbing_twig, self.lies_told + 1) + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +def test_info(): + "Check that Inspector.info fills out various fields as expected." + i = inspector.info(Call, oname="Call") + assert i["type_name"] == "type" + expected_class = str(type(type)) # (Python 3) or + assert i["base_class"] == expected_class + assert re.search( + "", + i["string_form"], + ) + fname = __file__ + if fname.endswith(".pyc"): + fname = fname[:-1] + # case-insensitive comparison needed on some filesystems + # e.g. Windows: + assert i["file"].lower() == compress_user(fname).lower() + assert i["definition"] == None + assert i["docstring"] == Call.__doc__ + assert i["source"] == None + assert i["isclass"] is True + assert i["init_definition"] == "Call(x, y=1)" + assert i["init_docstring"] == Call.__init__.__doc__ + + i = inspector.info(Call, detail_level=1) + assert i["source"] is not None + assert i["docstring"] == None + + c = Call(1) + c.__doc__ = "Modified instance docstring" + i = inspector.info(c) + assert i["type_name"] == "Call" + assert i["docstring"] == "Modified instance docstring" + assert i["class_docstring"] == Call.__doc__ + assert i["init_docstring"] == Call.__init__.__doc__ + assert i["call_docstring"] == Call.__call__.__doc__ + + +def test_class_signature(): + info = inspector.info(HasSignature, "HasSignature") + assert info["init_definition"] == "HasSignature(test)" + assert info["init_docstring"] == HasSignature.__init__.__doc__ + + +def test_info_awkward(): + # Just test that this doesn't throw an error. + inspector.info(Awkward()) + +def test_bool_raise(): + inspector.info(NoBoolCall()) + +def test_info_serialliar(): + fib_tracker = [0] + inspector.info(SerialLiar(fib_tracker)) + + # Nested attribute access should be cut off at 100 levels deep to avoid + # infinite loops: https://github.com/ipython/ipython/issues/9122 + assert fib_tracker[0] < 9000 + +def support_function_one(x, y=2, *a, **kw): + """A simple function.""" + +def test_calldef_none(): + # We should ignore __call__ for all of these. + for obj in [support_function_one, SimpleClass().method, any, str.upper]: + i = inspector.info(obj) + assert i["call_def"] is None + + +def f_kwarg(pos, *, kwonly): + pass + +def test_definition_kwonlyargs(): + i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore + assert i["definition"] == "f_kwarg(pos, *, kwonly)" + + +def test_getdoc(): + class A(object): + """standard docstring""" + pass + + class B(object): + """standard docstring""" + def getdoc(self): + return "custom docstring" + + class C(object): + """standard docstring""" + def getdoc(self): + return None + + a = A() + b = B() + c = C() + + assert oinspect.getdoc(a) == "standard docstring" + assert oinspect.getdoc(b) == "custom docstring" + assert oinspect.getdoc(c) == "standard docstring" + + +def test_empty_property_has_no_source(): + i = inspector.info(property(), detail_level=1) + assert i["source"] is None + + +def test_property_sources(): + # A simple adder whose source and signature stays + # the same across Python distributions + def simple_add(a, b): + "Adds two numbers" + return a + b + + class A(object): + @property + def foo(self): + return 'bar' + + foo = foo.setter(lambda self, v: setattr(self, 'bar', v)) + + dname = property(oinspect.getdoc) + adder = property(simple_add) + + i = inspector.info(A.foo, detail_level=1) + assert "def foo(self):" in i["source"] + assert "lambda self, v:" in i["source"] + + i = inspector.info(A.dname, detail_level=1) + assert "def getdoc(obj)" in i["source"] + + i = inspector.info(A.adder, detail_level=1) + assert "def simple_add(a, b)" in i["source"] + + +def test_property_docstring_is_in_info_for_detail_level_0(): + class A(object): + @property + def foobar(self): + """This is `foobar` property.""" + pass + + ip.user_ns["a_obj"] = A() + assert ( + "This is `foobar` property." + == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"] + ) + + ip.user_ns["a_cls"] = A + assert ( + "This is `foobar` property." + == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"] + ) + + +def test_pdef(): + # See gh-1914 + def foo(): pass + inspector.pdef(foo, 'foo') + + +@contextmanager +def cleanup_user_ns(**kwargs): + """ + On exit delete all the keys that were not in user_ns before entering. + + It does not restore old values ! + + Parameters + ---------- + + **kwargs + used to update ip.user_ns + + """ + try: + known = set(ip.user_ns.keys()) + ip.user_ns.update(kwargs) + yield + finally: + added = set(ip.user_ns.keys()) - known + for k in added: + del ip.user_ns[k] + + +def test_pinfo_bool_raise(): + """ + Test that bool method is not called on parent. + """ + + class RaiseBool: + attr = None + + def __bool__(self): + raise ValueError("pinfo should not access this method") + + raise_bool = RaiseBool() + + with cleanup_user_ns(raise_bool=raise_bool): + ip._inspect("pinfo", "raise_bool.attr", detail_level=0) + + +def test_pinfo_getindex(): + def dummy(): + """ + MARKER + """ + + container = [dummy] + with cleanup_user_ns(container=container): + with AssertPrints("MARKER"): + ip._inspect("pinfo", "container[0]", detail_level=0) + assert "container" not in ip.user_ns.keys() + + +def test_qmark_getindex(): + def dummy(): + """ + MARKER 2 + """ + + container = [dummy] + with cleanup_user_ns(container=container): + with AssertPrints("MARKER 2"): + ip.run_cell("container[0]?") + assert "container" not in ip.user_ns.keys() + + +def test_qmark_getindex_negatif(): + def dummy(): + """ + MARKER 3 + """ + + container = [dummy] + with cleanup_user_ns(container=container): + with AssertPrints("MARKER 3"): + ip.run_cell("container[-1]?") + assert "container" not in ip.user_ns.keys() + + + +def test_pinfo_nonascii(): + # See gh-1177 + from . import nonascii2 + ip.user_ns['nonascii2'] = nonascii2 + ip._inspect('pinfo', 'nonascii2', detail_level=1) + +def test_pinfo_type(): + """ + type can fail in various edge case, for example `type.__subclass__()` + """ + ip._inspect('pinfo', 'type') + + +def test_pinfo_docstring_no_source(): + """Docstring should be included with detail_level=1 if there is no source""" + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'str.format', detail_level=0) + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'str.format', detail_level=1) + + +def test_pinfo_no_docstring_if_source(): + """Docstring should not be included with detail_level=1 if source is found""" + def foo(): + """foo has a docstring""" + + ip.user_ns['foo'] = foo + + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'foo', detail_level=0) + with AssertPrints('Source:'): + ip._inspect('pinfo', 'foo', detail_level=1) + with AssertNotPrints('Docstring:'): + ip._inspect('pinfo', 'foo', detail_level=1) + + +def test_pinfo_docstring_if_detail_and_no_source(): + """ Docstring should be displayed if source info not available """ + obj_def = '''class Foo(object): + """ This is a docstring for Foo """ + def bar(self): + """ This is a docstring for Foo.bar """ + pass + ''' + + ip.run_cell(obj_def) + ip.run_cell('foo = Foo()') + + with AssertNotPrints("Source:"): + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'foo', detail_level=0) + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'foo', detail_level=1) + with AssertPrints('Docstring:'): + ip._inspect('pinfo', 'foo.bar', detail_level=0) + + with AssertNotPrints('Docstring:'): + with AssertPrints('Source:'): + ip._inspect('pinfo', 'foo.bar', detail_level=1) + + +@pytest.mark.xfail( + sys.version_info.releaselevel not in ("final", "candidate"), + reason="fails on 3.13.dev", + strict=True, +) +def test_pinfo_docstring_dynamic(capsys): + obj_def = """class Bar: + __custom_documentations__ = { + "prop" : "cdoc for prop", + "non_exist" : "cdoc for non_exist", + } + @property + def prop(self): + ''' + Docstring for prop + ''' + return self._prop + + @prop.setter + def prop(self, v): + self._prop = v + """ + ip.run_cell(obj_def) + + ip.run_cell("b = Bar()") + + ip.run_line_magic("pinfo", "b.prop") + captured = capsys.readouterr() + assert re.search(r"Docstring:\s+cdoc for prop", captured.out) + + ip.run_line_magic("pinfo", "b.non_exist") + captured = capsys.readouterr() + assert re.search(r"Docstring:\s+cdoc for non_exist", captured.out) + + ip.run_cell("b.prop?") + captured = capsys.readouterr() + assert re.search(r"Docstring:\s+cdoc for prop", captured.out) + + ip.run_cell("b.non_exist?") + captured = capsys.readouterr() + assert re.search(r"Docstring:\s+cdoc for non_exist", captured.out) + + ip.run_cell("b.undefined?") + captured = capsys.readouterr() + assert re.search(r"Type:\s+NoneType", captured.out) + + +def test_pinfo_magic(): + with AssertPrints("Docstring:"): + ip._inspect("pinfo", "lsmagic", detail_level=0) + + with AssertPrints("Source:"): + ip._inspect("pinfo", "lsmagic", detail_level=1) + + +def test_init_colors(): + # ensure colors are not present in signature info + info = inspector.info(HasSignature) + init_def = info["init_definition"] + assert "[0m" not in init_def + + +def test_builtin_init(): + info = inspector.info(list) + init_def = info['init_definition'] + assert init_def is not None + + +def test_render_signature_short(): + def short_fun(a=1): pass + sig = oinspect._render_signature( + signature(short_fun), + short_fun.__name__, + ) + assert sig == "short_fun(a=1)" + + +def test_render_signature_long(): + from typing import Optional + + def long_function( + a_really_long_parameter: int, + and_another_long_one: bool = False, + let_us_make_sure_this_is_looong: Optional[str] = None, + ) -> bool: pass + + sig = oinspect._render_signature( + signature(long_function), + long_function.__name__, + ) + expected = """\ +long_function( + a_really_long_parameter: int, + and_another_long_one: bool = False, + let_us_make_sure_this_is_looong: Optional[str] = None, +) -> bool\ +""" + + assert sig == expected diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_page.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_page.py new file mode 100644 index 00000000..9f6a3742 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_page.py @@ -0,0 +1,20 @@ +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team. +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- +import io + +# N.B. For the test suite, page.page is overridden (see IPython.testing.globalipapp) +from IPython.core import page + +def test_detect_screen_size(): + """Simple smoketest for page._detect_screen_size.""" + try: + page._detect_screen_size(True, 25) + except (TypeError, io.UnsupportedOperation): + # This can happen in the test suite, because stdout may not have a + # fileno. + pass diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_paths.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_paths.py new file mode 100644 index 00000000..8e4a63fc --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_paths.py @@ -0,0 +1,202 @@ +import errno +import os +import shutil +import tempfile +import warnings +from unittest.mock import patch + +from tempfile import TemporaryDirectory +from testpath import assert_isdir, assert_isfile, modified_env + +from IPython import paths +from IPython.testing.decorators import skip_win32 + +TMP_TEST_DIR = os.path.realpath(tempfile.mkdtemp()) +HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir") +XDG_TEST_DIR = os.path.join(HOME_TEST_DIR, "xdg_test_dir") +XDG_CACHE_DIR = os.path.join(HOME_TEST_DIR, "xdg_cache_dir") +IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython') + +def setup_module(): + """Setup testenvironment for the module: + + - Adds dummy home dir tree + """ + # Do not mask exceptions here. In particular, catching WindowsError is a + # problem because that exception is only defined on Windows... + os.makedirs(IP_TEST_DIR) + os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython')) + os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython')) + + +def teardown_module(): + """Teardown testenvironment for the module: + + - Remove dummy home dir tree + """ + # Note: we remove the parent test dir, which is the root of all test + # subdirs we may have created. Use shutil instead of os.removedirs, so + # that non-empty directories are all recursively removed. + shutil.rmtree(TMP_TEST_DIR) + +def patch_get_home_dir(dirpath): + return patch.object(paths, 'get_home_dir', return_value=dirpath) + + +def test_get_ipython_dir_1(): + """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions.""" + env_ipdir = os.path.join("someplace", ".ipython") + with patch.object(paths, '_writable_dir', return_value=True), \ + modified_env({'IPYTHONDIR': env_ipdir}): + ipdir = paths.get_ipython_dir() + + assert ipdir == env_ipdir + +def test_get_ipython_dir_2(): + """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions.""" + with patch_get_home_dir('someplace'), \ + patch.object(paths, 'get_xdg_dir', return_value=None), \ + patch.object(paths, '_writable_dir', return_value=True), \ + patch('os.name', "posix"), \ + modified_env({'IPYTHON_DIR': None, + 'IPYTHONDIR': None, + 'XDG_CONFIG_HOME': None + }): + ipdir = paths.get_ipython_dir() + + assert ipdir == os.path.join("someplace", ".ipython") + +def test_get_ipython_dir_3(): + """test_get_ipython_dir_3, use XDG if defined and exists, and .ipython doesn't exist.""" + tmphome = TemporaryDirectory() + try: + with patch_get_home_dir(tmphome.name), \ + patch('os.name', 'posix'), \ + modified_env({ + 'IPYTHON_DIR': None, + 'IPYTHONDIR': None, + 'XDG_CONFIG_HOME': XDG_TEST_DIR, + }), warnings.catch_warnings(record=True) as w: + ipdir = paths.get_ipython_dir() + + assert ipdir == os.path.join(tmphome.name, XDG_TEST_DIR, "ipython") + assert len(w) == 0 + finally: + tmphome.cleanup() + +def test_get_ipython_dir_4(): + """test_get_ipython_dir_4, warn if XDG and home both exist.""" + with patch_get_home_dir(HOME_TEST_DIR), \ + patch('os.name', 'posix'): + try: + os.mkdir(os.path.join(XDG_TEST_DIR, 'ipython')) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + + with modified_env({ + 'IPYTHON_DIR': None, + 'IPYTHONDIR': None, + 'XDG_CONFIG_HOME': XDG_TEST_DIR, + }), warnings.catch_warnings(record=True) as w: + ipdir = paths.get_ipython_dir() + + assert len(w) == 1 + assert "Ignoring" in str(w[0]) + + +def test_get_ipython_dir_5(): + """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist.""" + with patch_get_home_dir(HOME_TEST_DIR), \ + patch('os.name', 'posix'): + try: + os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython')) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + with modified_env({ + 'IPYTHON_DIR': None, + 'IPYTHONDIR': None, + 'XDG_CONFIG_HOME': XDG_TEST_DIR, + }): + ipdir = paths.get_ipython_dir() + + assert ipdir == IP_TEST_DIR + +def test_get_ipython_dir_6(): + """test_get_ipython_dir_6, use home over XDG if defined and neither exist.""" + xdg = os.path.join(HOME_TEST_DIR, 'somexdg') + os.mkdir(xdg) + shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython')) + print(paths._writable_dir) + with patch_get_home_dir(HOME_TEST_DIR), \ + patch.object(paths, 'get_xdg_dir', return_value=xdg), \ + patch('os.name', 'posix'), \ + modified_env({ + 'IPYTHON_DIR': None, + 'IPYTHONDIR': None, + 'XDG_CONFIG_HOME': None, + }), warnings.catch_warnings(record=True) as w: + ipdir = paths.get_ipython_dir() + + assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython") + assert len(w) == 0 + +def test_get_ipython_dir_7(): + """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR""" + home_dir = os.path.normpath(os.path.expanduser('~')) + with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \ + patch.object(paths, '_writable_dir', return_value=True): + ipdir = paths.get_ipython_dir() + assert ipdir == os.path.join(home_dir, "somewhere") + + +@skip_win32 +def test_get_ipython_dir_8(): + """test_get_ipython_dir_8, test / home directory""" + if not os.access("/", os.W_OK): + # test only when HOME directory actually writable + return + + with ( + patch.object(paths, "_writable_dir", lambda path: bool(path)), + patch.object(paths, "get_xdg_dir", return_value=None), + modified_env( + { + "IPYTHON_DIR": None, + "IPYTHONDIR": None, + "HOME": "/", + } + ), + ): + assert paths.get_ipython_dir() == "/.ipython" + + +def test_get_ipython_cache_dir(): + with modified_env({'HOME': HOME_TEST_DIR}): + if os.name == "posix": + # test default + os.makedirs(os.path.join(HOME_TEST_DIR, ".cache")) + with modified_env({'XDG_CACHE_HOME': None}): + ipdir = paths.get_ipython_cache_dir() + assert os.path.join(HOME_TEST_DIR, ".cache", "ipython") == ipdir + assert_isdir(ipdir) + + # test env override + with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}): + ipdir = paths.get_ipython_cache_dir() + assert_isdir(ipdir) + assert ipdir == os.path.join(XDG_CACHE_DIR, "ipython") + else: + assert paths.get_ipython_cache_dir() == paths.get_ipython_dir() + +def test_get_ipython_package_dir(): + ipdir = paths.get_ipython_package_dir() + assert_isdir(ipdir) + + +def test_get_ipython_module_path(): + ipapp_path = paths.get_ipython_module_path('IPython.terminal.ipapp') + assert_isfile(ipapp_path) diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_prefilter.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_prefilter.py new file mode 100644 index 00000000..379a530e --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_prefilter.py @@ -0,0 +1,149 @@ +"""Tests for input manipulation machinery.""" + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +import pytest + +from IPython.core.prefilter import AutocallChecker + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +def test_prefilter(): + """Test user input conversions""" + + # pairs of (raw, expected correct) input + pairs = [ ('2+2','2+2'), + ] + + for raw, correct in pairs: + assert ip.prefilter(raw) == correct + +def test_prefilter_shadowed(): + def dummy_magic(line): pass + + prev_automagic_state = ip.automagic + ip.automagic = True + ip.autocall = 0 + + try: + # These should not be transformed - they are shadowed by other names + for name in ['if', 'zip', 'get_ipython']: # keyword, builtin, global + ip.register_magic_function(dummy_magic, magic_name=name) + res = ip.prefilter(name + " foo") + assert res == name + " foo" + del ip.magics_manager.magics["line"][name] + + # These should be transformed + for name in ['fi', 'piz', 'nohtypi_teg']: + ip.register_magic_function(dummy_magic, magic_name=name) + res = ip.prefilter(name + " foo") + assert res != name + " foo" + del ip.magics_manager.magics["line"][name] + + finally: + ip.automagic = prev_automagic_state + +def test_autocall_binops(): + """See https://github.com/ipython/ipython/issues/81""" + ip.run_line_magic("autocall", "2") + f = lambda x: x + ip.user_ns['f'] = f + try: + assert ip.prefilter("f 1") == "f(1)" + for t in ["f +1", "f -1"]: + assert ip.prefilter(t) == t + + # Run tests again with a more permissive exclude_regexp, which will + # allow transformation of binary operations ('f -1' -> 'f(-1)'). + pm = ip.prefilter_manager + ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm, + config=pm.config) + try: + ac.priority = 1 + ac.exclude_regexp = r'^[,&^\|\*/]|^is |^not |^in |^and |^or ' + pm.sort_checkers() + + assert ip.prefilter("f -1") == "f(-1)" + assert ip.prefilter("f +1") == "f(+1)" + finally: + pm.unregister_checker(ac) + finally: + ip.run_line_magic("autocall", "0") + del ip.user_ns["f"] + + +def test_issue_114(): + """Check that multiline string literals don't expand as magic + see http://github.com/ipython/ipython/issues/114""" + + template = '"""\n%s\n"""' + # Store the current value of multi_line_specials and turn it off before + # running test, since it could be true (case in which the test doesn't make + # sense, as multiline string literals *will* expand as magic in that case). + msp = ip.prefilter_manager.multi_line_specials + ip.prefilter_manager.multi_line_specials = False + try: + for mgk in ip.magics_manager.lsmagic()['line']: + raw = template % mgk + assert ip.prefilter(raw) == raw + finally: + ip.prefilter_manager.multi_line_specials = msp + + +def test_prefilter_attribute_errors(): + """Capture exceptions thrown by user objects on attribute access. + + See http://github.com/ipython/ipython/issues/988.""" + + class X(object): + def __getattr__(self, k): + raise ValueError('broken object') + def __call__(self, x): + return x + + # Create a callable broken object + ip.user_ns["x"] = X() + ip.run_line_magic("autocall", "2") + try: + # Even if x throws an attribute error when looking at its rewrite + # attribute, we should not crash. So the test here is simply making + # the prefilter call and not having an exception. + ip.prefilter('x 1') + finally: + del ip.user_ns["x"] + ip.run_line_magic("autocall", "0") + + +def test_autocall_type_ann(): + ip.run_cell("import collections.abc") + ip.run_line_magic("autocall", "1") + try: + assert ( + ip.prefilter("collections.abc.Callable[[int], None]") + == "collections.abc.Callable[[int], None]" + ) + finally: + ip.run_line_magic("autocall", "0") + + +def test_autocall_should_support_unicode(): + ip.run_line_magic("autocall", "2") + ip.user_ns["π"] = lambda x: x + try: + assert ip.prefilter("π 3") == "π(3)" + finally: + ip.run_line_magic("autocall", "0") + del ip.user_ns["π"] + + +def test_autocall_regression_gh_14513(): + ip.run_line_magic("autocall", "2") + ip.user_ns["foo"] = dict() + try: + assert ip.prefilter("foo") == "foo" + finally: + ip.run_line_magic("autocall", "0") + del ip.user_ns["foo"] diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_profile.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_profile.py new file mode 100644 index 00000000..8e03b5a1 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_profile.py @@ -0,0 +1,161 @@ +# coding: utf-8 +"""Tests for profile-related functions. + +Currently only the startup-dir functionality is tested, but more tests should +be added for: + + * ipython profile create + * ipython profile list + * ipython profile create --parallel + * security dir permissions + +Authors +------- + +* MinRK + +""" + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import shutil +import sys +import tempfile +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest import TestCase + +import pytest + +from IPython.core.profileapp import list_bundled_profiles, list_profiles_in +from IPython.core.profiledir import ProfileDir +from IPython.testing import decorators as dec +from IPython.testing import tools as tt +from IPython.utils.process import getoutput + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- +TMP_TEST_DIR = Path(tempfile.mkdtemp()) +HOME_TEST_DIR = TMP_TEST_DIR / "home_test_dir" +IP_TEST_DIR = HOME_TEST_DIR / ".ipython" + +# +# Setup/teardown functions/decorators +# + +def setup_module(): + """Setup test environment for the module: + + - Adds dummy home dir tree + """ + # Do not mask exceptions here. In particular, catching WindowsError is a + # problem because that exception is only defined on Windows... + (Path.cwd() / IP_TEST_DIR).mkdir(parents=True) + + +def teardown_module(): + """Teardown test environment for the module: + + - Remove dummy home dir tree + """ + # Note: we remove the parent test dir, which is the root of all test + # subdirs we may have created. Use shutil instead of os.removedirs, so + # that non-empty directories are all recursively removed. + shutil.rmtree(TMP_TEST_DIR) + + +#----------------------------------------------------------------------------- +# Test functions +#----------------------------------------------------------------------------- +class ProfileStartupTest(TestCase): + def setUp(self): + # create profile dir + self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, "test") + self.options = ["--ipython-dir", IP_TEST_DIR, "--profile", "test"] + self.fname = TMP_TEST_DIR / "test.py" + + def tearDown(self): + # We must remove this profile right away so its presence doesn't + # confuse other tests. + shutil.rmtree(self.pd.location) + + def init(self, startup_file, startup, test): + # write startup python file + with open(Path(self.pd.startup_dir) / startup_file, "w", encoding="utf-8") as f: + f.write(startup) + # write simple test file, to check that the startup file was run + with open(self.fname, "w", encoding="utf-8") as f: + f.write(test) + + def validate(self, output): + tt.ipexec_validate(self.fname, output, "", options=self.options) + + def test_startup_py(self): + self.init('00-start.py', 'zzz=123\n', 'print(zzz)\n') + self.validate('123') + + def test_startup_ipy(self): + self.init('00-start.ipy', '%xmode plain\n', '') + self.validate('Exception reporting mode: Plain') + + +@pytest.mark.skipif( + sys.implementation.name == "pypy" + and ((7, 3, 13) < sys.implementation.version < (7, 3, 16)), + reason="Unicode issues with scandir on PyPy, see https://github.com/pypy/pypy/issues/4860", +) +def test_list_profiles_in(): + # No need to remove these directories and files, as they will get nuked in + # the module-level teardown. + td = Path(tempfile.mkdtemp(dir=TMP_TEST_DIR)) + for name in ("profile_foo", "profile_hello", "not_a_profile"): + Path(td / name).mkdir(parents=True) + if dec.unicode_paths: + Path(td / "profile_ünicode").mkdir(parents=True) + + with open(td / "profile_file", "w", encoding="utf-8") as f: + f.write("I am not a profile directory") + profiles = list_profiles_in(td) + + # unicode normalization can turn u'ünicode' into u'u\0308nicode', + # so only check for *nicode, and that creating a ProfileDir from the + # name remains valid + found_unicode = False + for p in list(profiles): + if p.endswith('nicode'): + pd = ProfileDir.find_profile_dir_by_name(td, p) + profiles.remove(p) + found_unicode = True + break + if dec.unicode_paths: + assert found_unicode is True + assert set(profiles) == {"foo", "hello"} + + +def test_list_bundled_profiles(): + # This variable will need to be updated when a new profile gets bundled + bundled = sorted(list_bundled_profiles()) + assert bundled == [] + + +def test_profile_create_ipython_dir(): + """ipython profile create respects --ipython-dir""" + with TemporaryDirectory() as td: + getoutput( + [ + sys.executable, + "-m", + "IPython", + "profile", + "create", + "foo", + "--ipython-dir=%s" % td, + ] + ) + profile_dir = Path(td) / "profile_foo" + assert Path(profile_dir).exists() + ipython_config = profile_dir / "ipython_config.py" + assert Path(ipython_config).exists() diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_prompts.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_prompts.py new file mode 100644 index 00000000..95e6163b --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_prompts.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 +"""Tests for prompt generation.""" + +import unittest + +from IPython.core.prompts import LazyEvaluate + +class PromptTests(unittest.TestCase): + def test_lazy_eval_unicode(self): + u = u'ünicødé' + lz = LazyEvaluate(lambda : u) + self.assertEqual(str(lz), u) + self.assertEqual(format(lz), u) + + def test_lazy_eval_nonascii_bytes(self): + u = u'ünicødé' + b = u.encode('utf8') + lz = LazyEvaluate(lambda : b) + # unicode(lz) would fail + self.assertEqual(str(lz), str(b)) + self.assertEqual(format(lz), str(b)) + + def test_lazy_eval_float(self): + f = 0.503 + lz = LazyEvaluate(lambda : f) + + self.assertEqual(str(lz), str(f)) + self.assertEqual(format(lz), str(f)) + self.assertEqual(format(lz, '.1'), '0.5') + diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_pylabtools.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_pylabtools.py new file mode 100644 index 00000000..31d3dbe2 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_pylabtools.py @@ -0,0 +1,375 @@ +"""Tests for pylab tools module. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +from binascii import a2b_base64 +from io import BytesIO + +import pytest + +matplotlib = pytest.importorskip("matplotlib") +matplotlib.use('Agg') +from matplotlib.figure import Figure + +from matplotlib import pyplot as plt +from matplotlib_inline import backend_inline +import numpy as np + +from IPython.core.getipython import get_ipython +from IPython.core.interactiveshell import InteractiveShell +from IPython.core.display import _PNG, _JPEG +from .. import pylabtools as pt + +from IPython.testing import decorators as dec + + +def test_figure_to_svg(): + # simple empty-figure test + fig = plt.figure() + assert pt.print_figure(fig, "svg") is None + + plt.close('all') + + # simple check for at least svg-looking output + fig = plt.figure() + ax = fig.add_subplot(1,1,1) + ax.plot([1,2,3]) + plt.draw() + svg = pt.print_figure(fig, "svg")[:100].lower() + assert "doctype svg" in svg + + +def _check_pil_jpeg_bytes(): + """Skip if PIL can't write JPEGs to BytesIO objects""" + # PIL's JPEG plugin can't write to BytesIO objects + # Pillow fixes this + from PIL import Image + buf = BytesIO() + img = Image.new("RGB", (4,4)) + try: + img.save(buf, 'jpeg') + except Exception as e: + ename = e.__class__.__name__ + raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e + +@dec.skip_without("PIL.Image") +def test_figure_to_jpeg(): + _check_pil_jpeg_bytes() + # simple check for at least jpeg-looking output + fig = plt.figure() + ax = fig.add_subplot(1,1,1) + ax.plot([1,2,3]) + plt.draw() + jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower() + assert jpeg.startswith(_JPEG) + +def test_retina_figure(): + # simple empty-figure test + fig = plt.figure() + assert pt.retina_figure(fig) == None + plt.close('all') + + fig = plt.figure() + ax = fig.add_subplot(1,1,1) + ax.plot([1,2,3]) + plt.draw() + png, md = pt.retina_figure(fig) + assert png.startswith(_PNG) + assert "width" in md + assert "height" in md + + +_fmt_mime_map = { + 'png': 'image/png', + 'jpeg': 'image/jpeg', + 'pdf': 'application/pdf', + 'retina': 'image/png', + 'svg': 'image/svg+xml', +} + +def test_select_figure_formats_str(): + ip = get_ipython() + for fmt, active_mime in _fmt_mime_map.items(): + pt.select_figure_formats(ip, fmt) + for mime, f in ip.display_formatter.formatters.items(): + if mime == active_mime: + assert Figure in f + else: + assert Figure not in f + +def test_select_figure_formats_kwargs(): + ip = get_ipython() + kwargs = dict(bbox_inches="tight") + pt.select_figure_formats(ip, "png", **kwargs) + formatter = ip.display_formatter.formatters["image/png"] + f = formatter.lookup_by_type(Figure) + cell = f.keywords + expected = kwargs + expected["base64"] = True + expected["fmt"] = "png" + assert cell == expected + + # check that the formatter doesn't raise + fig = plt.figure() + ax = fig.add_subplot(1,1,1) + ax.plot([1,2,3]) + plt.draw() + formatter.enabled = True + png = formatter(fig) + assert isinstance(png, str) + png_bytes = a2b_base64(png) + assert png_bytes.startswith(_PNG) + +def test_select_figure_formats_set(): + ip = get_ipython() + for fmts in [ + {'png', 'svg'}, + ['png'], + ('jpeg', 'pdf', 'retina'), + {'svg'}, + ]: + active_mimes = {_fmt_mime_map[fmt] for fmt in fmts} + pt.select_figure_formats(ip, fmts) + for mime, f in ip.display_formatter.formatters.items(): + if mime in active_mimes: + assert Figure in f + else: + assert Figure not in f + +def test_select_figure_formats_bad(): + ip = get_ipython() + with pytest.raises(ValueError): + pt.select_figure_formats(ip, 'foo') + with pytest.raises(ValueError): + pt.select_figure_formats(ip, {'png', 'foo'}) + with pytest.raises(ValueError): + pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad']) + +def test_import_pylab(): + ns = {} + pt.import_pylab(ns, import_all=False) + assert "plt" in ns + assert ns["np"] == np + + +class TestPylabSwitch(object): + class Shell(InteractiveShell): + def init_history(self): + """Sets up the command history, and starts regular autosaves.""" + self.config.HistoryManager.hist_file = ":memory:" + super().init_history() + + def enable_gui(self, gui): + pass + + def setup_method(self): + import matplotlib + def act_mpl(backend): + matplotlib.rcParams['backend'] = backend + + # Save rcParams since they get modified + self._saved_rcParams = matplotlib.rcParams + self._saved_rcParamsOrig = matplotlib.rcParamsOrig + matplotlib.rcParams = dict(backend="QtAgg") + matplotlib.rcParamsOrig = dict(backend="QtAgg") + + # Mock out functions + self._save_am = pt.activate_matplotlib + pt.activate_matplotlib = act_mpl + self._save_ip = pt.import_pylab + pt.import_pylab = lambda *a,**kw:None + self._save_cis = backend_inline.configure_inline_support + backend_inline.configure_inline_support = lambda *a, **kw: None + + def teardown_method(self): + pt.activate_matplotlib = self._save_am + pt.import_pylab = self._save_ip + backend_inline.configure_inline_support = self._save_cis + import matplotlib + matplotlib.rcParams = self._saved_rcParams + matplotlib.rcParamsOrig = self._saved_rcParamsOrig + + def test_qt(self): + s = self.Shell() + gui, backend = s.enable_matplotlib(None) + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + gui, backend = s.enable_matplotlib("inline") + assert gui is None + assert s.pylab_gui_select == "qt" + + gui, backend = s.enable_matplotlib("qt") + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + gui, backend = s.enable_matplotlib("inline") + assert gui is None + assert s.pylab_gui_select == "qt" + + gui, backend = s.enable_matplotlib() + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + def test_inline(self): + s = self.Shell() + gui, backend = s.enable_matplotlib("inline") + assert gui is None + assert s.pylab_gui_select == None + + gui, backend = s.enable_matplotlib("inline") + assert gui is None + assert s.pylab_gui_select == None + + gui, backend = s.enable_matplotlib("qt") + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + def test_inline_twice(self): + "Using '%matplotlib inline' twice should not reset formatters" + + ip = self.Shell() + gui, backend = ip.enable_matplotlib("inline") + assert gui is None + + fmts = {'png'} + active_mimes = {_fmt_mime_map[fmt] for fmt in fmts} + pt.select_figure_formats(ip, fmts) + + gui, backend = ip.enable_matplotlib("inline") + assert gui is None + + for mime, f in ip.display_formatter.formatters.items(): + if mime in active_mimes: + assert Figure in f + else: + assert Figure not in f + + def test_qt_gtk(self): + s = self.Shell() + gui, backend = s.enable_matplotlib("qt") + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + gui, backend = s.enable_matplotlib("gtk3") + assert gui == "qt" + assert s.pylab_gui_select == "qt" + + @dec.skipif(not pt._matplotlib_manages_backends()) + def test_backend_module_name_case_sensitive(self): + # Matplotlib backend names are case insensitive unless explicitly specified using + # "module://some_module.some_name" syntax which are case sensitive for mpl >= 3.9.1 + all_lowercase = "module://matplotlib_inline.backend_inline" + some_uppercase = "module://matplotlib_inline.Backend_inline" + mpl3_9_1 = matplotlib.__version_info__ >= (3, 9, 1) + + s = self.Shell() + s.enable_matplotlib(all_lowercase) + if mpl3_9_1: + with pytest.raises(RuntimeError): + s.enable_matplotlib(some_uppercase) + else: + s.enable_matplotlib(some_uppercase) + + s.run_line_magic("matplotlib", all_lowercase) + if mpl3_9_1: + with pytest.raises(RuntimeError): + s.run_line_magic("matplotlib", some_uppercase) + else: + s.run_line_magic("matplotlib", some_uppercase) + + +def test_no_gui_backends(): + for k in ['agg', 'svg', 'pdf', 'ps']: + assert k not in pt.backend2gui + + +def test_figure_no_canvas(): + fig = Figure() + fig.canvas = None + pt.print_figure(fig) + + +@pytest.mark.parametrize( + "name, expected_gui, expected_backend", + [ + # name is gui + ("gtk3", "gtk3", "gtk3agg"), + ("gtk4", "gtk4", "gtk4agg"), + ("headless", None, "agg"), + ("osx", "osx", "macosx"), + ("qt", "qt", "qtagg"), + ("qt5", "qt5", "qt5agg"), + ("qt6", "qt6", "qtagg"), + ("tk", "tk", "tkagg"), + ("wx", "wx", "wxagg"), + # name is backend + ("agg", None, "agg"), + ("cairo", None, "cairo"), + ("pdf", None, "pdf"), + ("ps", None, "ps"), + ("svg", None, "svg"), + ("template", None, "template"), + ("gtk3agg", "gtk3", "gtk3agg"), + ("gtk3cairo", "gtk3", "gtk3cairo"), + ("gtk4agg", "gtk4", "gtk4agg"), + ("gtk4cairo", "gtk4", "gtk4cairo"), + ("macosx", "osx", "macosx"), + ("nbagg", "nbagg", "nbagg"), + ("notebook", "nbagg", "notebook"), + ("qtagg", "qt", "qtagg"), + ("qtcairo", "qt", "qtcairo"), + ("qt5agg", "qt5", "qt5agg"), + ("qt5cairo", "qt5", "qt5cairo"), + ("tkagg", "tk", "tkagg"), + ("tkcairo", "tk", "tkcairo"), + ("webagg", "webagg", "webagg"), + ("wxagg", "wx", "wxagg"), + ("wxcairo", "wx", "wxcairo"), + ], +) +def test_backend_builtin(name, expected_gui, expected_backend): + # Test correct identification of Matplotlib built-in backends without importing and using them, + # otherwise we would need to ensure all the complex dependencies such as windowing toolkits are + # installed. + + mpl_manages_backends = pt._matplotlib_manages_backends() + if not mpl_manages_backends: + # Backends not supported before _matplotlib_manages_backends or supported + # but with different expected_gui or expected_backend. + if ( + name.endswith("agg") + or name.endswith("cairo") + or name in ("headless", "macosx", "pdf", "ps", "svg", "template") + ): + pytest.skip() + elif name == "qt6": + expected_backend = "qtagg" + elif name == "notebook": + expected_backend, expected_gui = expected_gui, expected_backend + + gui, backend = pt.find_gui_and_backend(name) + if not mpl_manages_backends: + gui = gui.lower() if gui else None + backend = backend.lower() if backend else None + assert gui == expected_gui + assert backend == expected_backend + + +def test_backend_entry_point(): + gui, backend = pt.find_gui_and_backend("inline") + assert gui is None + expected_backend = ( + "inline" + if pt._matplotlib_manages_backends() + else "module://matplotlib_inline.backend_inline" + ) + assert backend == expected_backend + + +def test_backend_unknown(): + with pytest.raises(RuntimeError if pt._matplotlib_manages_backends() else KeyError): + pt.find_gui_and_backend("name-does-not-exist") diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_run.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_run.py new file mode 100644 index 00000000..971f8e30 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_run.py @@ -0,0 +1,626 @@ +# encoding: utf-8 +"""Tests for code execution (%run and related), which is particularly tricky. + +Because of how %run manages namespaces, and the fact that we are trying here to +verify subtle object deletion and reference counting issues, the %run tests +will be kept in this separate file. This makes it easier to aggregate in one +place the tricks needed to handle it; most other magics are much easier to test +and we do so in a common test_magic file. + +Note that any test using `run -i` should make sure to do a `reset` afterwards, +as otherwise it may influence later tests. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + + +import functools +import os +import platform +import random +import string +import sys +import textwrap +import unittest +from os.path import join as pjoin +from unittest.mock import patch + +import pytest +from tempfile import TemporaryDirectory + +from IPython.core import debugger +from IPython.testing import decorators as dec +from IPython.testing import tools as tt +from IPython.utils.io import capture_output + + +def doctest_refbug(): + """Very nasty problem with references held by multiple runs of a script. + See: https://github.com/ipython/ipython/issues/141 + + In [1]: _ip.clear_main_mod_cache() + # random + + In [2]: %run refbug + + In [3]: call_f() + lowercased: hello + + In [4]: %run refbug + + In [5]: call_f() + lowercased: hello + lowercased: hello + """ + + +def doctest_run_builtins(): + r"""Check that %run doesn't damage __builtins__. + + In [1]: import tempfile + + In [2]: bid1 = id(__builtins__) + + In [3]: fname = tempfile.mkstemp('.py')[1] + + In [3]: f = open(fname, 'w', encoding='utf-8') + + In [4]: dummy= f.write('pass\n') + + In [5]: f.flush() + + In [6]: t1 = type(__builtins__) + + In [7]: %run $fname + + In [7]: f.close() + + In [8]: bid2 = id(__builtins__) + + In [9]: t2 = type(__builtins__) + + In [10]: t1 == t2 + Out[10]: True + + In [10]: bid1 == bid2 + Out[10]: True + + In [12]: try: + ....: os.unlink(fname) + ....: except: + ....: pass + ....: + """ + + +def doctest_run_option_parser(): + r"""Test option parser in %run. + + In [1]: %run print_argv.py + [] + + In [2]: %run print_argv.py print*.py + ['print_argv.py'] + + In [3]: %run -G print_argv.py print*.py + ['print*.py'] + + """ + + +@dec.skip_win32 +def doctest_run_option_parser_for_posix(): + r"""Test option parser in %run (Linux/OSX specific). + + You need double quote to escape glob in POSIX systems: + + In [1]: %run print_argv.py print\\*.py + ['print*.py'] + + You can't use quote to escape glob in POSIX systems: + + In [2]: %run print_argv.py 'print*.py' + ['print_argv.py'] + + """ + + +doctest_run_option_parser_for_posix.__skip_doctest__ = sys.platform == "win32" + + +@dec.skip_if_not_win32 +def doctest_run_option_parser_for_windows(): + r"""Test option parser in %run (Windows specific). + + In Windows, you can't escape ``*` `by backslash: + + In [1]: %run print_argv.py print\\*.py + ['print\\\\*.py'] + + You can use quote to escape glob: + + In [2]: %run print_argv.py 'print*.py' + ["'print*.py'"] + + """ + + +doctest_run_option_parser_for_windows.__skip_doctest__ = sys.platform != "win32" + + +def doctest_reset_del(): + """Test that resetting doesn't cause errors in __del__ methods. + + In [2]: class A(object): + ...: def __del__(self): + ...: print(str("Hi")) + ...: + + In [3]: a = A() + + In [4]: get_ipython().reset(); import gc; x = gc.collect(0) + Hi + + In [5]: 1+1 + Out[5]: 2 + """ + +# For some tests, it will be handy to organize them in a class with a common +# setup that makes a temp file + +class TestMagicRunPass(tt.TempFileMixin): + + def setUp(self): + content = "a = [1,2,3]\nb = 1" + self.mktmp(content) + + def run_tmpfile(self): + _ip = get_ipython() + # This fails on Windows if self.tmpfile.name has spaces or "~" in it. + # See below and ticket https://bugs.launchpad.net/bugs/366353 + _ip.run_line_magic("run", self.fname) + + def run_tmpfile_p(self): + _ip = get_ipython() + # This fails on Windows if self.tmpfile.name has spaces or "~" in it. + # See below and ticket https://bugs.launchpad.net/bugs/366353 + _ip.run_line_magic("run", "-p %s" % self.fname) + + def test_builtins_id(self): + """Check that %run doesn't damage __builtins__ """ + _ip = get_ipython() + # Test that the id of __builtins__ is not modified by %run + bid1 = id(_ip.user_ns['__builtins__']) + self.run_tmpfile() + bid2 = id(_ip.user_ns['__builtins__']) + assert bid1 == bid2 + + def test_builtins_type(self): + """Check that the type of __builtins__ doesn't change with %run. + + However, the above could pass if __builtins__ was already modified to + be a dict (it should be a module) by a previous use of %run. So we + also check explicitly that it really is a module: + """ + _ip = get_ipython() + self.run_tmpfile() + assert type(_ip.user_ns["__builtins__"]) == type(sys) + + def test_run_profile(self): + """Test that the option -p, which invokes the profiler, do not + crash by invoking execfile""" + self.run_tmpfile_p() + + def test_run_debug_twice(self): + # https://github.com/ipython/ipython/issues/10028 + _ip = get_ipython() + with tt.fake_input(["c"]): + _ip.run_line_magic("run", "-d %s" % self.fname) + with tt.fake_input(["c"]): + _ip.run_line_magic("run", "-d %s" % self.fname) + + def test_run_debug_twice_with_breakpoint(self): + """Make a valid python temp file.""" + _ip = get_ipython() + with tt.fake_input(["b 2", "c", "c"]): + _ip.run_line_magic("run", "-d %s" % self.fname) + + with tt.fake_input(["c"]): + with tt.AssertNotPrints("KeyError"): + _ip.run_line_magic("run", "-d %s" % self.fname) + + +class TestMagicRunSimple(tt.TempFileMixin): + + def test_simpledef(self): + """Test that simple class definitions work.""" + src = ("class foo: pass\n" + "def f(): return foo()") + self.mktmp(src) + _ip.run_line_magic("run", str(self.fname)) + _ip.run_cell("t = isinstance(f(), foo)") + assert _ip.user_ns["t"] is True + + @pytest.mark.xfail( + platform.python_implementation() == "PyPy", + reason="expecting __del__ call on exit is unreliable and doesn't happen on PyPy", + ) + def test_obj_del(self): + """Test that object's __del__ methods are called on exit.""" + src = ("class A(object):\n" + " def __del__(self):\n" + " print('object A deleted')\n" + "a = A()\n") + self.mktmp(src) + err = None + tt.ipexec_validate(self.fname, 'object A deleted', err) + + def test_aggressive_namespace_cleanup(self): + """Test that namespace cleanup is not too aggressive GH-238 + + Returning from another run magic deletes the namespace""" + # see ticket https://github.com/ipython/ipython/issues/238 + + with tt.TempFileMixin() as empty: + empty.mktmp("") + # On Windows, the filename will have \users in it, so we need to use the + # repr so that the \u becomes \\u. + src = ( + "ip = get_ipython()\n" + "for i in range(5):\n" + " try:\n" + " ip.run_line_magic(%r, %r)\n" + " except NameError as e:\n" + " print(i)\n" + " break\n" % ("run", empty.fname) + ) + self.mktmp(src) + _ip.run_line_magic("run", str(self.fname)) + _ip.run_cell("ip == get_ipython()") + assert _ip.user_ns["i"] == 4 + + def test_run_second(self): + """Test that running a second file doesn't clobber the first, gh-3547""" + self.mktmp("avar = 1\n" "def afunc():\n" " return avar\n") + + with tt.TempFileMixin() as empty: + empty.mktmp("") + + _ip.run_line_magic("run", self.fname) + _ip.run_line_magic("run", empty.fname) + assert _ip.user_ns["afunc"]() == 1 + + def test_tclass(self): + mydir = os.path.dirname(__file__) + tc = os.path.join(mydir, "tclass") + src = f"""\ +import gc +%run "{tc}" C-first +gc.collect(0) +%run "{tc}" C-second +gc.collect(0) +%run "{tc}" C-third +gc.collect(0) +%reset -f +""" + self.mktmp(src, ".ipy") + out = """\ +ARGV 1-: ['C-first'] +ARGV 1-: ['C-second'] +tclass.py: deleting object: C-first +ARGV 1-: ['C-third'] +tclass.py: deleting object: C-second +tclass.py: deleting object: C-third +""" + err = None + tt.ipexec_validate(self.fname, out, err) + + def test_run_i_after_reset(self): + """Check that %run -i still works after %reset (gh-693)""" + src = "yy = zz\n" + self.mktmp(src) + _ip.run_cell("zz = 23") + try: + _ip.run_line_magic("run", "-i %s" % self.fname) + assert _ip.user_ns["yy"] == 23 + finally: + _ip.run_line_magic("reset", "-f") + + _ip.run_cell("zz = 23") + try: + _ip.run_line_magic("run", "-i %s" % self.fname) + assert _ip.user_ns["yy"] == 23 + finally: + _ip.run_line_magic("reset", "-f") + + def test_unicode(self): + """Check that files in odd encodings are accepted.""" + mydir = os.path.dirname(__file__) + na = os.path.join(mydir, "nonascii.py") + _ip.run_line_magic("run", na) + assert _ip.user_ns["u"] == "Ўт№Ф" + + def test_run_py_file_attribute(self): + """Test handling of `__file__` attribute in `%run .py`.""" + src = "t = __file__\n" + self.mktmp(src) + _missing = object() + file1 = _ip.user_ns.get("__file__", _missing) + _ip.run_line_magic("run", self.fname) + file2 = _ip.user_ns.get("__file__", _missing) + + # Check that __file__ was equal to the filename in the script's + # namespace. + assert _ip.user_ns["t"] == self.fname + + # Check that __file__ was not leaked back into user_ns. + assert file1 == file2 + + def test_run_ipy_file_attribute(self): + """Test handling of `__file__` attribute in `%run `.""" + src = "t = __file__\n" + self.mktmp(src, ext='.ipy') + _missing = object() + file1 = _ip.user_ns.get("__file__", _missing) + _ip.run_line_magic("run", self.fname) + file2 = _ip.user_ns.get("__file__", _missing) + + # Check that __file__ was equal to the filename in the script's + # namespace. + assert _ip.user_ns["t"] == self.fname + + # Check that __file__ was not leaked back into user_ns. + assert file1 == file2 + + def test_run_formatting(self): + """ Test that %run -t -N does not raise a TypeError for N > 1.""" + src = "pass" + self.mktmp(src) + _ip.run_line_magic("run", "-t -N 1 %s" % self.fname) + _ip.run_line_magic("run", "-t -N 10 %s" % self.fname) + + def test_ignore_sys_exit(self): + """Test the -e option to ignore sys.exit()""" + src = "import sys; sys.exit(1)" + self.mktmp(src) + with tt.AssertPrints("SystemExit"): + _ip.run_line_magic("run", self.fname) + + with tt.AssertNotPrints("SystemExit"): + _ip.run_line_magic("run", "-e %s" % self.fname) + + def test_run_nb(self): + """Test %run notebook.ipynb""" + pytest.importorskip("nbformat") + from nbformat import v4, writes + nb = v4.new_notebook( + cells=[ + v4.new_markdown_cell("The Ultimate Question of Everything"), + v4.new_code_cell("answer=42") + ] + ) + src = writes(nb, version=4) + self.mktmp(src, ext='.ipynb') + + _ip.run_line_magic("run", self.fname) + + assert _ip.user_ns["answer"] == 42 + + def test_run_nb_error(self): + """Test %run notebook.ipynb error""" + pytest.importorskip("nbformat") + from nbformat import v4, writes + + # %run when a file name isn't provided + pytest.raises(Exception, _ip.magic, "run") + + # %run when a file doesn't exist + pytest.raises(Exception, _ip.magic, "run foobar.ipynb") + + # %run on a notebook with an error + nb = v4.new_notebook( + cells=[ + v4.new_code_cell("0/0") + ] + ) + src = writes(nb, version=4) + self.mktmp(src, ext='.ipynb') + pytest.raises(Exception, _ip.magic, "run %s" % self.fname) + + def test_file_options(self): + src = ('import sys\n' + 'a = " ".join(sys.argv[1:])\n') + self.mktmp(src) + test_opts = "-x 3 --verbose" + _ip.run_line_magic("run", "{0} {1}".format(self.fname, test_opts)) + assert _ip.user_ns["a"] == test_opts + + +class TestMagicRunWithPackage(unittest.TestCase): + + def writefile(self, name, content): + path = os.path.join(self.tempdir.name, name) + d = os.path.dirname(path) + if not os.path.isdir(d): + os.makedirs(d) + with open(path, "w", encoding="utf-8") as f: + f.write(textwrap.dedent(content)) + + def setUp(self): + self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)])) + """Temporary (probably) valid python package name.""" + + self.value = int(random.random() * 10000) + + self.tempdir = TemporaryDirectory() + self.__orig_cwd = os.getcwd() + sys.path.insert(0, self.tempdir.name) + + self.writefile(os.path.join(package, '__init__.py'), '') + self.writefile(os.path.join(package, 'sub.py'), """ + x = {0!r} + """.format(self.value)) + self.writefile(os.path.join(package, 'relative.py'), """ + from .sub import x + """) + self.writefile(os.path.join(package, 'absolute.py'), """ + from {0}.sub import x + """.format(package)) + self.writefile(os.path.join(package, 'args.py'), """ + import sys + a = " ".join(sys.argv[1:]) + """.format(package)) + + def tearDown(self): + os.chdir(self.__orig_cwd) + sys.path[:] = [p for p in sys.path if p != self.tempdir.name] + self.tempdir.cleanup() + + def check_run_submodule(self, submodule, opts=""): + _ip.user_ns.pop("x", None) + _ip.run_line_magic( + "run", "{2} -m {0}.{1}".format(self.package, submodule, opts) + ) + self.assertEqual( + _ip.user_ns["x"], + self.value, + "Variable `x` is not loaded from module `{0}`.".format(submodule), + ) + + def test_run_submodule_with_absolute_import(self): + self.check_run_submodule('absolute') + + def test_run_submodule_with_relative_import(self): + """Run submodule that has a relative import statement (#2727).""" + self.check_run_submodule('relative') + + def test_prun_submodule_with_absolute_import(self): + self.check_run_submodule('absolute', '-p') + + def test_prun_submodule_with_relative_import(self): + self.check_run_submodule('relative', '-p') + + def with_fake_debugger(func): + @functools.wraps(func) + def wrapper(*args, **kwds): + with patch.object(debugger.Pdb, 'run', staticmethod(eval)): + return func(*args, **kwds) + return wrapper + + @with_fake_debugger + def test_debug_run_submodule_with_absolute_import(self): + self.check_run_submodule('absolute', '-d') + + @with_fake_debugger + def test_debug_run_submodule_with_relative_import(self): + self.check_run_submodule('relative', '-d') + + def test_module_options(self): + _ip.user_ns.pop("a", None) + test_opts = "-x abc -m test" + _ip.run_line_magic("run", "-m {0}.args {1}".format(self.package, test_opts)) + assert _ip.user_ns["a"] == test_opts + + def test_module_options_with_separator(self): + _ip.user_ns.pop("a", None) + test_opts = "-x abc -m test" + _ip.run_line_magic("run", "-m {0}.args -- {1}".format(self.package, test_opts)) + assert _ip.user_ns["a"] == test_opts + + +def test_run__name__(): + with TemporaryDirectory() as td: + path = pjoin(td, "foo.py") + with open(path, "w", encoding="utf-8") as f: + f.write("q = __name__") + + _ip.user_ns.pop("q", None) + _ip.run_line_magic("run", "{}".format(path)) + assert _ip.user_ns.pop("q") == "__main__" + + _ip.run_line_magic("run", "-n {}".format(path)) + assert _ip.user_ns.pop("q") == "foo" + + try: + _ip.run_line_magic("run", "-i -n {}".format(path)) + assert _ip.user_ns.pop("q") == "foo" + finally: + _ip.run_line_magic("reset", "-f") + + +def test_run_tb(): + """Test traceback offset in %run""" + with TemporaryDirectory() as td: + path = pjoin(td, "foo.py") + with open(path, "w", encoding="utf-8") as f: + f.write( + "\n".join( + [ + "def foo():", + " return bar()", + "def bar():", + " raise RuntimeError('hello!')", + "foo()", + ] + ) + ) + with capture_output() as io: + _ip.run_line_magic("run", "{}".format(path)) + out = io.stdout + assert "execfile" not in out + assert "RuntimeError" in out + assert out.count("---->") == 3 + del ip.user_ns['bar'] + del ip.user_ns['foo'] + + +def test_multiprocessing_run(): + """Set we can run mutiprocesgin without messing up up main namespace + + Note that import `nose.tools as nt` modify the values + sys.module['__mp_main__'] so we need to temporarily set it to None to test + the issue. + """ + with TemporaryDirectory() as td: + mpm = sys.modules.get('__mp_main__') + sys.modules['__mp_main__'] = None + try: + path = pjoin(td, "test.py") + with open(path, "w", encoding="utf-8") as f: + f.write("import multiprocessing\nprint('hoy')") + with capture_output() as io: + _ip.run_line_magic('run', path) + _ip.run_cell("i_m_undefined") + out = io.stdout + assert "hoy" in out + assert "AttributeError" not in out + assert "NameError" in out + assert out.count("---->") == 1 + except: + raise + finally: + sys.modules['__mp_main__'] = mpm + + +def test_script_tb(): + """Test traceback offset in `ipython script.py`""" + with TemporaryDirectory() as td: + path = pjoin(td, "foo.py") + with open(path, "w", encoding="utf-8") as f: + f.write( + "\n".join( + [ + "def foo():", + " return bar()", + "def bar():", + " raise RuntimeError('hello!')", + "foo()", + ] + ) + ) + out, err = tt.ipexec(path) + assert "execfile" not in out + assert "RuntimeError" in out + assert out.count("---->") == 3 diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_shellapp.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_shellapp.py new file mode 100644 index 00000000..9f4f87b8 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_shellapp.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +"""Tests for shellapp module. + +Authors +------- +* Bradley Froehle +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2012 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +import unittest + +from IPython.testing import decorators as dec +from IPython.testing import tools as tt + + +class TestFileToRun(tt.TempFileMixin, unittest.TestCase): + """Test the behavior of the file_to_run parameter.""" + + def test_py_script_file_attribute(self): + """Test that `__file__` is set when running `ipython file.py`""" + src = "print(__file__)\n" + self.mktmp(src) + + err = None + tt.ipexec_validate(self.fname, self.fname, err) + + def test_ipy_script_file_attribute(self): + """Test that `__file__` is set when running `ipython file.ipy`""" + src = "print(__file__)\n" + self.mktmp(src, ext='.ipy') + + err = None + tt.ipexec_validate(self.fname, self.fname, err) + + # The commands option to ipexec_validate doesn't work on Windows, and it + # doesn't seem worth fixing + @dec.skip_win32 + def test_py_script_file_attribute_interactively(self): + """Test that `__file__` is not set after `ipython -i file.py`""" + src = "True\n" + self.mktmp(src) + + out, err = tt.ipexec( + self.fname, + options=["-i"], + commands=['"__file__" in globals()', "print(123)", "exit()"], + ) + assert "False" in out, f"Subprocess stderr:\n{err}\n-----" diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_splitinput.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_splitinput.py new file mode 100644 index 00000000..f5fc53fa --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_splitinput.py @@ -0,0 +1,41 @@ +# coding: utf-8 + +from IPython.core.splitinput import split_user_input, LineInfo + +import pytest + +tests = [ + ("x=1", ("", "", "x", "=1")), + ("?", ("", "?", "", "")), + ("??", ("", "??", "", "")), + (" ?", (" ", "?", "", "")), + (" ??", (" ", "??", "", "")), + ("??x", ("", "??", "x", "")), + ("?x=1", ("", "?", "x", "=1")), + ("!ls", ("", "!", "ls", "")), + (" !ls", (" ", "!", "ls", "")), + ("!!ls", ("", "!!", "ls", "")), + (" !!ls", (" ", "!!", "ls", "")), + (",ls", ("", ",", "ls", "")), + (";ls", ("", ";", "ls", "")), + (" ;ls", (" ", ";", "ls", "")), + ("f.g(x)", ("", "", "f.g", "(x)")), + ("f.g (x)", ("", "", "f.g", " (x)")), + ("?%hist1", ("", "?", "%hist1", "")), + ("?%%hist2", ("", "?", "%%hist2", "")), + ("??%hist3", ("", "??", "%hist3", "")), + ("??%%hist4", ("", "??", "%%hist4", "")), + ("?x*", ("", "?", "x*", "")), + ("Pérez Fernando", ("", "", "Pérez", " Fernando")), +] + + +@pytest.mark.parametrize("input, output", tests) +def test_split_user_input(input, output): + assert split_user_input(input) == output + + +def test_LineInfo(): + """Simple test for LineInfo construction and str()""" + linfo = LineInfo(" %cd /home") + assert str(linfo) == "LineInfo [ |%|cd|/home]" diff --git a/myenv/lib/python3.12/site-packages/IPython/core/tests/test_ultratb.py b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_ultratb.py new file mode 100644 index 00000000..c8f52710 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/tests/test_ultratb.py @@ -0,0 +1,457 @@ +# encoding: utf-8 +"""Tests for IPython.core.ultratb +""" +import io +import os.path +import platform +import re +import sys +import traceback +import unittest +from textwrap import dedent + +from tempfile import TemporaryDirectory + +from IPython.core.ultratb import ColorTB, VerboseTB +from IPython.testing import tools as tt +from IPython.testing.decorators import onlyif_unicode_paths, skip_without +from IPython.utils.syspathcontext import prepended_to_syspath + +file_1 = """1 +2 +3 +def f(): + 1/0 +""" + +file_2 = """def f(): + 1/0 +""" + + +def recursionlimit(frames): + """ + decorator to set the recursion limit temporarily + """ + + def inner(test_function): + def wrapper(*args, **kwargs): + rl = sys.getrecursionlimit() + sys.setrecursionlimit(frames) + try: + return test_function(*args, **kwargs) + finally: + sys.setrecursionlimit(rl) + + return wrapper + + return inner + + +class ChangedPyFileTest(unittest.TestCase): + def test_changing_py_file(self): + """Traceback produced if the line where the error occurred is missing? + + https://github.com/ipython/ipython/issues/1456 + """ + with TemporaryDirectory() as td: + fname = os.path.join(td, "foo.py") + with open(fname, "w", encoding="utf-8") as f: + f.write(file_1) + + with prepended_to_syspath(td): + ip.run_cell("import foo") + + with tt.AssertPrints("ZeroDivisionError"): + ip.run_cell("foo.f()") + + # Make the file shorter, so the line of the error is missing. + with open(fname, "w", encoding="utf-8") as f: + f.write(file_2) + + # For some reason, this was failing on the *second* call after + # changing the file, so we call f() twice. + with tt.AssertNotPrints("Internal Python error", channel='stderr'): + with tt.AssertPrints("ZeroDivisionError"): + ip.run_cell("foo.f()") + with tt.AssertPrints("ZeroDivisionError"): + ip.run_cell("foo.f()") + +iso_8859_5_file = u'''# coding: iso-8859-5 + +def fail(): + """дбИЖ""" + 1/0 # дбИЖ +''' + +class NonAsciiTest(unittest.TestCase): + @onlyif_unicode_paths + def test_nonascii_path(self): + # Non-ascii directory name as well. + with TemporaryDirectory(suffix=u'é') as td: + fname = os.path.join(td, u"fooé.py") + with open(fname, "w", encoding="utf-8") as f: + f.write(file_1) + + with prepended_to_syspath(td): + ip.run_cell("import foo") + + with tt.AssertPrints("ZeroDivisionError"): + ip.run_cell("foo.f()") + + def test_iso8859_5(self): + with TemporaryDirectory() as td: + fname = os.path.join(td, 'dfghjkl.py') + + with io.open(fname, 'w', encoding='iso-8859-5') as f: + f.write(iso_8859_5_file) + + with prepended_to_syspath(td): + ip.run_cell("from dfghjkl import fail") + + with tt.AssertPrints("ZeroDivisionError"): + with tt.AssertPrints(u'дбИЖ', suppress=False): + ip.run_cell('fail()') + + def test_nonascii_msg(self): + cell = u"raise Exception('é')" + expected = u"Exception('é')" + ip.run_cell("%xmode plain") + with tt.AssertPrints(expected): + ip.run_cell(cell) + + ip.run_cell("%xmode verbose") + with tt.AssertPrints(expected): + ip.run_cell(cell) + + ip.run_cell("%xmode context") + with tt.AssertPrints(expected): + ip.run_cell(cell) + + ip.run_cell("%xmode minimal") + with tt.AssertPrints(u"Exception: é"): + ip.run_cell(cell) + + # Put this back into Context mode for later tests. + ip.run_cell("%xmode context") + +class NestedGenExprTestCase(unittest.TestCase): + """ + Regression test for the following issues: + https://github.com/ipython/ipython/issues/8293 + https://github.com/ipython/ipython/issues/8205 + """ + def test_nested_genexpr(self): + code = dedent( + """\ + class SpecificException(Exception): + pass + + def foo(x): + raise SpecificException("Success!") + + sum(sum(foo(x) for _ in [0]) for x in [0]) + """ + ) + with tt.AssertPrints('SpecificException: Success!', suppress=False): + ip.run_cell(code) + + +indentationerror_file = """if True: +zoom() +""" + +class IndentationErrorTest(unittest.TestCase): + def test_indentationerror_shows_line(self): + # See issue gh-2398 + with tt.AssertPrints("IndentationError"): + with tt.AssertPrints("zoom()", suppress=False): + ip.run_cell(indentationerror_file) + + with TemporaryDirectory() as td: + fname = os.path.join(td, "foo.py") + with open(fname, "w", encoding="utf-8") as f: + f.write(indentationerror_file) + + with tt.AssertPrints("IndentationError"): + with tt.AssertPrints("zoom()", suppress=False): + ip.run_line_magic("run", fname) + + +@skip_without("pandas") +def test_dynamic_code(): + code = """ + import pandas + df = pandas.DataFrame([]) + + # Important: only fails inside of an "exec" call: + exec("df.foobarbaz()") + """ + + with tt.AssertPrints("Could not get source"): + ip.run_cell(code) + + +se_file_1 = """1 +2 +7/ +""" + +se_file_2 = """7/ +""" + +class SyntaxErrorTest(unittest.TestCase): + + def test_syntaxerror_no_stacktrace_at_compile_time(self): + syntax_error_at_compile_time = """ +def foo(): + .. +""" + with tt.AssertPrints("SyntaxError"): + ip.run_cell(syntax_error_at_compile_time) + + with tt.AssertNotPrints("foo()"): + ip.run_cell(syntax_error_at_compile_time) + + def test_syntaxerror_stacktrace_when_running_compiled_code(self): + syntax_error_at_runtime = """ +def foo(): + eval("..") + +def bar(): + foo() + +bar() +""" + with tt.AssertPrints("SyntaxError"): + ip.run_cell(syntax_error_at_runtime) + # Assert syntax error during runtime generate stacktrace + with tt.AssertPrints(["foo()", "bar()"]): + ip.run_cell(syntax_error_at_runtime) + del ip.user_ns['bar'] + del ip.user_ns['foo'] + + def test_changing_py_file(self): + with TemporaryDirectory() as td: + fname = os.path.join(td, "foo.py") + with open(fname, "w", encoding="utf-8") as f: + f.write(se_file_1) + + with tt.AssertPrints(["7/", "SyntaxError"]): + ip.run_line_magic("run", fname) + + # Modify the file + with open(fname, "w", encoding="utf-8") as f: + f.write(se_file_2) + + # The SyntaxError should point to the correct line + with tt.AssertPrints(["7/", "SyntaxError"]): + ip.run_line_magic("run", fname) + + def test_non_syntaxerror(self): + # SyntaxTB may be called with an error other than a SyntaxError + # See e.g. gh-4361 + try: + raise ValueError('QWERTY') + except ValueError: + with tt.AssertPrints('QWERTY'): + ip.showsyntaxerror() + +import sys + +if platform.python_implementation() != "PyPy": + """ + New 3.9 Pgen Parser does not raise Memory error, except on failed malloc. + """ + class MemoryErrorTest(unittest.TestCase): + def test_memoryerror(self): + memoryerror_code = "(" * 200 + ")" * 200 + ip.run_cell(memoryerror_code) + + +class Python3ChainedExceptionsTest(unittest.TestCase): + DIRECT_CAUSE_ERROR_CODE = """ +try: + x = 1 + 2 + print(not_defined_here) +except Exception as e: + x += 55 + x - 1 + y = {} + raise KeyError('uh') from e + """ + + EXCEPTION_DURING_HANDLING_CODE = """ +try: + x = 1 + 2 + print(not_defined_here) +except Exception as e: + x += 55 + x - 1 + y = {} + raise KeyError('uh') + """ + + SUPPRESS_CHAINING_CODE = """ +try: + 1/0 +except Exception: + raise ValueError("Yikes") from None + """ + + SYS_EXIT_WITH_CONTEXT_CODE = """ +try: + 1/0 +except Exception as e: + raise SystemExit(1) + """ + + def test_direct_cause_error(self): + with tt.AssertPrints(["KeyError", "NameError", "direct cause"]): + ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE) + + def test_exception_during_handling_error(self): + with tt.AssertPrints(["KeyError", "NameError", "During handling"]): + ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) + + def test_sysexit_while_handling_error(self): + with tt.AssertPrints(["SystemExit", "to see the full traceback"]): + with tt.AssertNotPrints(["another exception"], suppress=False): + ip.run_cell(self.SYS_EXIT_WITH_CONTEXT_CODE) + + def test_suppress_exception_chaining(self): + with tt.AssertNotPrints("ZeroDivisionError"), \ + tt.AssertPrints("ValueError", suppress=False): + ip.run_cell(self.SUPPRESS_CHAINING_CODE) + + def test_plain_direct_cause_error(self): + with tt.AssertPrints(["KeyError", "NameError", "direct cause"]): + ip.run_cell("%xmode Plain") + ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE) + ip.run_cell("%xmode Verbose") + + def test_plain_exception_during_handling_error(self): + with tt.AssertPrints(["KeyError", "NameError", "During handling"]): + ip.run_cell("%xmode Plain") + ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE) + ip.run_cell("%xmode Verbose") + + def test_plain_suppress_exception_chaining(self): + with tt.AssertNotPrints("ZeroDivisionError"), \ + tt.AssertPrints("ValueError", suppress=False): + ip.run_cell("%xmode Plain") + ip.run_cell(self.SUPPRESS_CHAINING_CODE) + ip.run_cell("%xmode Verbose") + + +class RecursionTest(unittest.TestCase): + DEFINITIONS = """ +def non_recurs(): + 1/0 + +def r1(): + r1() + +def r3a(): + r3b() + +def r3b(): + r3c() + +def r3c(): + r3a() + +def r3o1(): + r3a() + +def r3o2(): + r3o1() +""" + def setUp(self): + ip.run_cell(self.DEFINITIONS) + + def test_no_recursion(self): + with tt.AssertNotPrints("skipping similar frames"): + ip.run_cell("non_recurs()") + + @recursionlimit(200) + def test_recursion_one_frame(self): + with tt.AssertPrints(re.compile( + r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]") + ): + ip.run_cell("r1()") + + @recursionlimit(160) + def test_recursion_three_frames(self): + with tt.AssertPrints("[... skipping similar frames: "), \ + tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \ + tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \ + tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False): + ip.run_cell("r3o2()") + + +class PEP678NotesReportingTest(unittest.TestCase): + ERROR_WITH_NOTE = """ +try: + raise AssertionError("Message") +except Exception as e: + try: + e.add_note("This is a PEP-678 note.") + except AttributeError: # Python <= 3.10 + e.__notes__ = ("This is a PEP-678 note.",) + raise + """ + + def test_verbose_reports_notes(self): + with tt.AssertPrints(["AssertionError", "Message", "This is a PEP-678 note."]): + ip.run_cell(self.ERROR_WITH_NOTE) + + def test_plain_reports_notes(self): + with tt.AssertPrints(["AssertionError", "Message", "This is a PEP-678 note."]): + ip.run_cell("%xmode Plain") + ip.run_cell(self.ERROR_WITH_NOTE) + ip.run_cell("%xmode Verbose") + + +#---------------------------------------------------------------------------- + +# module testing (minimal) +def test_handlers(): + def spam(c, d_e): + (d, e) = d_e + x = c + d + y = c * d + foo(x, y) + + def foo(a, b, bar=1): + eggs(a, b + bar) + + def eggs(f, g, z=globals()): + h = f + g + i = f - g + return h / i + + buff = io.StringIO() + + buff.write('') + buff.write('*** Before ***') + try: + buff.write(spam(1, (2, 3))) + except: + traceback.print_exc(file=buff) + + handler = ColorTB(ostream=buff) + buff.write('*** ColorTB ***') + try: + buff.write(spam(1, (2, 3))) + except: + handler(*sys.exc_info()) + buff.write('') + + handler = VerboseTB(ostream=buff) + buff.write('*** VerboseTB ***') + try: + buff.write(spam(1, (2, 3))) + except: + handler(*sys.exc_info()) + buff.write('') diff --git a/myenv/lib/python3.12/site-packages/IPython/core/ultratb.py b/myenv/lib/python3.12/site-packages/IPython/core/ultratb.py new file mode 100644 index 00000000..a8a38f16 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/ultratb.py @@ -0,0 +1,1547 @@ +# -*- coding: utf-8 -*- +""" +Verbose and colourful traceback formatting. + +**ColorTB** + +I've always found it a bit hard to visually parse tracebacks in Python. The +ColorTB class is a solution to that problem. It colors the different parts of a +traceback in a manner similar to what you would expect from a syntax-highlighting +text editor. + +Installation instructions for ColorTB:: + + import sys,ultratb + sys.excepthook = ultratb.ColorTB() + +**VerboseTB** + +I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds +of useful info when a traceback occurs. Ping originally had it spit out HTML +and intended it for CGI programmers, but why should they have all the fun? I +altered it to spit out colored text to the terminal. It's a bit overwhelming, +but kind of neat, and maybe useful for long-running programs that you believe +are bug-free. If a crash *does* occur in that type of program you want details. +Give it a shot--you'll love it or you'll hate it. + +.. note:: + + The Verbose mode prints the variables currently visible where the exception + happened (shortening their strings if too long). This can potentially be + very slow, if you happen to have a huge data structure whose string + representation is complex to compute. Your computer may appear to freeze for + a while with cpu usage at 100%. If this occurs, you can cancel the traceback + with Ctrl-C (maybe hitting it more than once). + + If you encounter this kind of situation often, you may want to use the + Verbose_novars mode instead of the regular Verbose, which avoids formatting + variables (but otherwise includes the information and context given by + Verbose). + +.. note:: + + The verbose mode print all variables in the stack, which means it can + potentially leak sensitive information like access keys, or unencrypted + password. + +Installation instructions for VerboseTB:: + + import sys,ultratb + sys.excepthook = ultratb.VerboseTB() + +Note: Much of the code in this module was lifted verbatim from the standard +library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'. + +Color schemes +------------- + +The colors are defined in the class TBTools through the use of the +ColorSchemeTable class. Currently the following exist: + + - NoColor: allows all of this module to be used in any terminal (the color + escapes are just dummy blank strings). + + - Linux: is meant to look good in a terminal like the Linux console (black + or very dark background). + + - LightBG: similar to Linux but swaps dark/light colors to be more readable + in light background terminals. + + - Neutral: a neutral color scheme that should be readable on both light and + dark background + +You can implement other color schemes easily, the syntax is fairly +self-explanatory. Please send back new schemes you develop to the author for +possible inclusion in future releases. + +Inheritance diagram: + +.. inheritance-diagram:: IPython.core.ultratb + :parts: 3 +""" + +#***************************************************************************** +# Copyright (C) 2001 Nathaniel Gray +# Copyright (C) 2001-2004 Fernando Perez +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#***************************************************************************** + + +from collections.abc import Sequence +import functools +import inspect +import linecache +import pydoc +import sys +import time +import traceback +import types +from types import TracebackType +from typing import Any, List, Optional, Tuple + +import stack_data +from pygments.formatters.terminal256 import Terminal256Formatter +from pygments.styles import get_style_by_name + +import IPython.utils.colorable as colorable +# IPython's own modules +from IPython import get_ipython +from IPython.core import debugger +from IPython.core.display_trap import DisplayTrap +from IPython.core.excolors import exception_colors +from IPython.utils import PyColorize +from IPython.utils import path as util_path +from IPython.utils import py3compat +from IPython.utils.terminal import get_terminal_size + +# Globals +# amount of space to put line numbers before verbose tracebacks +INDENT_SIZE = 8 + +# Default color scheme. This is used, for example, by the traceback +# formatter. When running in an actual IPython instance, the user's rc.colors +# value is used, but having a module global makes this functionality available +# to users of ultratb who are NOT running inside ipython. +DEFAULT_SCHEME = 'NoColor' +FAST_THRESHOLD = 10_000 + +# --------------------------------------------------------------------------- +# Code begins + +# Helper function -- largely belongs to VerboseTB, but we need the same +# functionality to produce a pseudo verbose TB for SyntaxErrors, so that they +# can be recognized properly by ipython.el's py-traceback-line-re +# (SyntaxErrors have to be treated specially because they have no traceback) + + +@functools.lru_cache() +def count_lines_in_py_file(filename: str) -> int: + """ + Given a filename, returns the number of lines in the file + if it ends with the extension ".py". Otherwise, returns 0. + """ + if not filename.endswith(".py"): + return 0 + else: + try: + with open(filename, "r") as file: + s = sum(1 for line in file) + except UnicodeError: + return 0 + return s + + """ + Given a frame object, returns the total number of lines in the file + if the filename ends with the extension ".py". Otherwise, returns 0. + """ + + +def get_line_number_of_frame(frame: types.FrameType) -> int: + """ + Given a frame object, returns the total number of lines in the file + containing the frame's code object, or the number of lines in the + frame's source code if the file is not available. + + Parameters + ---------- + frame : FrameType + The frame object whose line number is to be determined. + + Returns + ------- + int + The total number of lines in the file containing the frame's + code object, or the number of lines in the frame's source code + if the file is not available. + """ + filename = frame.f_code.co_filename + if filename is None: + print("No file....") + lines, first = inspect.getsourcelines(frame) + return first + len(lines) + return count_lines_in_py_file(filename) + + +def _safe_string(value, what, func=str): + # Copied from cpython/Lib/traceback.py + try: + return func(value) + except: + return f"<{what} {func.__name__}() failed>" + + +def _format_traceback_lines(lines, Colors, has_colors: bool, lvals): + """ + Format tracebacks lines with pointing arrow, leading numbers... + + Parameters + ---------- + lines : list[Line] + Colors + ColorScheme used. + lvals : str + Values of local variables, already colored, to inject just after the error line. + """ + numbers_width = INDENT_SIZE - 1 + res = [] + + for stack_line in lines: + if stack_line is stack_data.LINE_GAP: + res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal)) + continue + + line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n' + lineno = stack_line.lineno + if stack_line.is_current: + # This is the line with the error + pad = numbers_width - len(str(lineno)) + num = '%s%s' % (debugger.make_arrow(pad), str(lineno)) + start_color = Colors.linenoEm + else: + num = '%*s' % (numbers_width, lineno) + start_color = Colors.lineno + + line = '%s%s%s %s' % (start_color, num, Colors.Normal, line) + + res.append(line) + if lvals and stack_line.is_current: + res.append(lvals + '\n') + return res + +def _simple_format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format): + """ + Format tracebacks lines with pointing arrow, leading numbers... + + Parameters + ========== + + lnum: int + number of the target line of code. + index: int + which line in the list should be highlighted. + lines: list[string] + Colors: + ColorScheme used. + lvals: bytes + Values of local variables, already colored, to inject just after the error line. + _line_format: f (str) -> (str, bool) + return (colorized version of str, failure to do so) + """ + numbers_width = INDENT_SIZE - 1 + res = [] + for i, line in enumerate(lines, lnum - index): + # assert isinstance(line, str) + line = py3compat.cast_unicode(line) + + new_line, err = _line_format(line, "str") + if not err: + line = new_line + + if i == lnum: + # This is the line with the error + pad = numbers_width - len(str(i)) + num = "%s%s" % (debugger.make_arrow(pad), str(lnum)) + line = "%s%s%s %s%s" % ( + Colors.linenoEm, + num, + Colors.line, + line, + Colors.Normal, + ) + else: + num = "%*s" % (numbers_width, i) + line = "%s%s%s %s" % (Colors.lineno, num, Colors.Normal, line) + + res.append(line) + if lvals and i == lnum: + res.append(lvals + "\n") + return res + + +def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None): + """ + Format filename lines with custom formatting from caching compiler or `File *.py` by default + + Parameters + ---------- + file : str + ColorFilename + ColorScheme's filename coloring to be used. + ColorNormal + ColorScheme's normal coloring to be used. + """ + ipinst = get_ipython() + if ( + ipinst is not None + and (data := ipinst.compile.format_code_name(file)) is not None + ): + label, name = data + if lineno is None: + tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}" + else: + tpl_link = ( + f"{{label}} {ColorFilename}{{name}}, line {{lineno}}{ColorNormal}" + ) + else: + label = "File" + name = util_path.compress_user( + py3compat.cast_unicode(file, util_path.fs_encoding) + ) + if lineno is None: + tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}" + else: + # can we make this the more friendly ", line {{lineno}}", or do we need to preserve the formatting with the colon? + tpl_link = f"{{label}} {ColorFilename}{{name}}:{{lineno}}{ColorNormal}" + + return tpl_link.format(label=label, name=name, lineno=lineno) + +#--------------------------------------------------------------------------- +# Module classes +class TBTools(colorable.Colorable): + """Basic tools used by all traceback printer classes.""" + + # Number of frames to skip when reporting tracebacks + tb_offset = 0 + + def __init__( + self, + color_scheme="NoColor", + call_pdb=False, + ostream=None, + parent=None, + config=None, + *, + debugger_cls=None, + ): + # Whether to call the interactive pdb debugger after printing + # tracebacks or not + super(TBTools, self).__init__(parent=parent, config=config) + self.call_pdb = call_pdb + + # Output stream to write to. Note that we store the original value in + # a private attribute and then make the public ostream a property, so + # that we can delay accessing sys.stdout until runtime. The way + # things are written now, the sys.stdout object is dynamically managed + # so a reference to it should NEVER be stored statically. This + # property approach confines this detail to a single location, and all + # subclasses can simply access self.ostream for writing. + self._ostream = ostream + + # Create color table + self.color_scheme_table = exception_colors() + + self.set_colors(color_scheme) + self.old_scheme = color_scheme # save initial value for toggles + self.debugger_cls = debugger_cls or debugger.Pdb + + if call_pdb: + self.pdb = self.debugger_cls() + else: + self.pdb = None + + def _get_ostream(self): + """Output stream that exceptions are written to. + + Valid values are: + + - None: the default, which means that IPython will dynamically resolve + to sys.stdout. This ensures compatibility with most tools, including + Windows (where plain stdout doesn't recognize ANSI escapes). + + - Any object with 'write' and 'flush' attributes. + """ + return sys.stdout if self._ostream is None else self._ostream + + def _set_ostream(self, val): + assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush')) + self._ostream = val + + ostream = property(_get_ostream, _set_ostream) + + @staticmethod + def _get_chained_exception(exception_value): + cause = getattr(exception_value, "__cause__", None) + if cause: + return cause + if getattr(exception_value, "__suppress_context__", False): + return None + return getattr(exception_value, "__context__", None) + + def get_parts_of_chained_exception( + self, evalue + ) -> Optional[Tuple[type, BaseException, TracebackType]]: + chained_evalue = self._get_chained_exception(evalue) + + if chained_evalue: + return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__ + return None + + def prepare_chained_exception_message(self, cause) -> List[Any]: + direct_cause = "\nThe above exception was the direct cause of the following exception:\n" + exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n" + + if cause: + message = [[direct_cause]] + else: + message = [[exception_during_handling]] + return message + + @property + def has_colors(self) -> bool: + return self.color_scheme_table.active_scheme_name.lower() != "nocolor" + + def set_colors(self, *args, **kw): + """Shorthand access to the color table scheme selector method.""" + + # Set own color table + self.color_scheme_table.set_active_scheme(*args, **kw) + # for convenience, set Colors to the active scheme + self.Colors = self.color_scheme_table.active_colors + # Also set colors of debugger + if hasattr(self, 'pdb') and self.pdb is not None: + self.pdb.set_colors(*args, **kw) + + def color_toggle(self): + """Toggle between the currently active color scheme and NoColor.""" + + if self.color_scheme_table.active_scheme_name == 'NoColor': + self.color_scheme_table.set_active_scheme(self.old_scheme) + self.Colors = self.color_scheme_table.active_colors + else: + self.old_scheme = self.color_scheme_table.active_scheme_name + self.color_scheme_table.set_active_scheme('NoColor') + self.Colors = self.color_scheme_table.active_colors + + def stb2text(self, stb): + """Convert a structured traceback (a list) to a string.""" + return '\n'.join(stb) + + def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5): + """Return formatted traceback. + + Subclasses may override this if they add extra arguments. + """ + tb_list = self.structured_traceback(etype, value, tb, + tb_offset, context) + return self.stb2text(tb_list) + + def structured_traceback( + self, + etype: type, + evalue: Optional[BaseException], + etb: Optional[TracebackType] = None, + tb_offset: Optional[int] = None, + number_of_lines_of_context: int = 5, + ): + """Return a list of traceback frames. + + Must be implemented by each class. + """ + raise NotImplementedError() + + +#--------------------------------------------------------------------------- +class ListTB(TBTools): + """Print traceback information from a traceback list, with optional color. + + Calling requires 3 arguments: (etype, evalue, elist) + as would be obtained by:: + + etype, evalue, tb = sys.exc_info() + if tb: + elist = traceback.extract_tb(tb) + else: + elist = None + + It can thus be used by programs which need to process the traceback before + printing (such as console replacements based on the code module from the + standard library). + + Because they are meant to be called without a full traceback (only a + list), instances of this class can't call the interactive pdb debugger.""" + + + def __call__(self, etype, value, elist): + self.ostream.flush() + self.ostream.write(self.text(etype, value, elist)) + self.ostream.write('\n') + + def _extract_tb(self, tb): + if tb: + return traceback.extract_tb(tb) + else: + return None + + def structured_traceback( + self, + etype: type, + evalue: Optional[BaseException], + etb: Optional[TracebackType] = None, + tb_offset: Optional[int] = None, + context=5, + ): + """Return a color formatted string with the traceback info. + + Parameters + ---------- + etype : exception type + Type of the exception raised. + evalue : object + Data stored in the exception + etb : list | TracebackType | None + If list: List of frames, see class docstring for details. + If Traceback: Traceback of the exception. + tb_offset : int, optional + Number of frames in the traceback to skip. If not given, the + instance evalue is used (set in constructor). + context : int, optional + Number of lines of context information to print. + + Returns + ------- + String with formatted exception. + """ + # This is a workaround to get chained_exc_ids in recursive calls + # etb should not be a tuple if structured_traceback is not recursive + if isinstance(etb, tuple): + etb, chained_exc_ids = etb + else: + chained_exc_ids = set() + + if isinstance(etb, list): + elist = etb + elif etb is not None: + elist = self._extract_tb(etb) + else: + elist = [] + tb_offset = self.tb_offset if tb_offset is None else tb_offset + assert isinstance(tb_offset, int) + Colors = self.Colors + out_list = [] + if elist: + + if tb_offset and len(elist) > tb_offset: + elist = elist[tb_offset:] + + out_list.append('Traceback %s(most recent call last)%s:' % + (Colors.normalEm, Colors.Normal) + '\n') + out_list.extend(self._format_list(elist)) + # The exception info should be a single entry in the list. + lines = ''.join(self._format_exception_only(etype, evalue)) + out_list.append(lines) + + # Find chained exceptions if we have a traceback (not for exception-only mode) + if etb is not None: + exception = self.get_parts_of_chained_exception(evalue) + + if exception and (id(exception[1]) not in chained_exc_ids): + chained_exception_message = ( + self.prepare_chained_exception_message(evalue.__cause__)[0] + if evalue is not None + else "" + ) + etype, evalue, etb = exception + # Trace exception to avoid infinite 'cause' loop + chained_exc_ids.add(id(exception[1])) + chained_exceptions_tb_offset = 0 + out_list = ( + self.structured_traceback( + etype, + evalue, + (etb, chained_exc_ids), # type: ignore + chained_exceptions_tb_offset, + context, + ) + + chained_exception_message + + out_list + ) + + return out_list + + def _format_list(self, extracted_list): + """Format a list of traceback entry tuples for printing. + + Given a list of tuples as returned by extract_tb() or + extract_stack(), return a list of strings ready for printing. + Each string in the resulting list corresponds to the item with the + same index in the argument list. Each string ends in a newline; + the strings may contain internal newlines as well, for those items + whose source text line is not None. + + Lifted almost verbatim from traceback.py + """ + + Colors = self.Colors + output_list = [] + for ind, (filename, lineno, name, line) in enumerate(extracted_list): + normalCol, nameCol, fileCol, lineCol = ( + # Emphasize the last entry + (Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line) + if ind == len(extracted_list) - 1 + else (Colors.Normal, Colors.name, Colors.filename, "") + ) + + fns = _format_filename(filename, fileCol, normalCol, lineno=lineno) + item = f"{normalCol} {fns}" + + if name != "": + item += f" in {nameCol}{name}{normalCol}\n" + else: + item += "\n" + if line: + item += f"{lineCol} {line.strip()}{normalCol}\n" + output_list.append(item) + + return output_list + + def _format_exception_only(self, etype, value): + """Format the exception part of a traceback. + + The arguments are the exception type and value such as given by + sys.exc_info()[:2]. The return value is a list of strings, each ending + in a newline. Normally, the list contains a single string; however, + for SyntaxError exceptions, it contains several lines that (when + printed) display detailed information about where the syntax error + occurred. The message indicating which exception occurred is the + always last string in the list. + + Also lifted nearly verbatim from traceback.py + """ + have_filedata = False + Colors = self.Colors + output_list = [] + stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal) + if value is None: + # Not sure if this can still happen in Python 2.6 and above + output_list.append(stype + "\n") + else: + if issubclass(etype, SyntaxError): + have_filedata = True + if not value.filename: value.filename = "" + if value.lineno: + lineno = value.lineno + textline = linecache.getline(value.filename, value.lineno) + else: + lineno = "unknown" + textline = "" + output_list.append( + "%s %s%s\n" + % ( + Colors.normalEm, + _format_filename( + value.filename, + Colors.filenameEm, + Colors.normalEm, + lineno=(None if lineno == "unknown" else lineno), + ), + Colors.Normal, + ) + ) + if textline == "": + textline = py3compat.cast_unicode(value.text, "utf-8") + + if textline is not None: + i = 0 + while i < len(textline) and textline[i].isspace(): + i += 1 + output_list.append( + "%s %s%s\n" % (Colors.line, textline.strip(), Colors.Normal) + ) + if value.offset is not None: + s = ' ' + for c in textline[i:value.offset - 1]: + if c.isspace(): + s += c + else: + s += " " + output_list.append( + "%s%s^%s\n" % (Colors.caret, s, Colors.Normal) + ) + + try: + s = value.msg + except Exception: + s = self._some_str(value) + if s: + output_list.append( + "%s%s:%s %s\n" % (stype, Colors.excName, Colors.Normal, s) + ) + else: + output_list.append("%s\n" % stype) + + # PEP-678 notes + output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", [])) + + # sync with user hooks + if have_filedata: + ipinst = get_ipython() + if ipinst is not None: + ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0) + + return output_list + + def get_exception_only(self, etype, value): + """Only print the exception type and message, without a traceback. + + Parameters + ---------- + etype : exception type + value : exception value + """ + return ListTB.structured_traceback(self, etype, value) + + def show_exception_only(self, etype, evalue): + """Only print the exception type and message, without a traceback. + + Parameters + ---------- + etype : exception type + evalue : exception value + """ + # This method needs to use __call__ from *this* class, not the one from + # a subclass whose signature or behavior may be different + ostream = self.ostream + ostream.flush() + ostream.write('\n'.join(self.get_exception_only(etype, evalue))) + ostream.flush() + + def _some_str(self, value): + # Lifted from traceback.py + try: + return py3compat.cast_unicode(str(value)) + except: + return u'' % type(value).__name__ + + +class FrameInfo: + """ + Mirror of stack data's FrameInfo, but so that we can bypass highlighting on + really long frames. + """ + + description: Optional[str] + filename: Optional[str] + lineno: Tuple[int] + # number of context lines to use + context: Optional[int] + raw_lines: List[str] + + @classmethod + def _from_stack_data_FrameInfo(cls, frame_info): + return cls( + getattr(frame_info, "description", None), + getattr(frame_info, "filename", None), # type: ignore[arg-type] + getattr(frame_info, "lineno", None), # type: ignore[arg-type] + getattr(frame_info, "frame", None), + getattr(frame_info, "code", None), + sd=frame_info, + context=None, + ) + + def __init__( + self, + description: Optional[str], + filename: str, + lineno: Tuple[int], + frame, + code, + *, + sd=None, + context=None, + ): + self.description = description + self.filename = filename + self.lineno = lineno + self.frame = frame + self.code = code + self._sd = sd + self.context = context + + # self.lines = [] + if sd is None: + try: + # return a list of source lines and a starting line number + self.raw_lines = inspect.getsourcelines(frame)[0] + except OSError: + self.raw_lines = [ + "'Could not get source, probably due dynamically evaluated source code.'" + ] + + @property + def variables_in_executing_piece(self): + if self._sd: + return self._sd.variables_in_executing_piece + else: + return [] + + @property + def lines(self): + from executing.executing import NotOneValueFound + + try: + return self._sd.lines + except NotOneValueFound: + + class Dummy: + lineno = 0 + is_current = False + + def render(self, *, pygmented): + return "" + + return [Dummy()] + + @property + def executing(self): + if self._sd: + return self._sd.executing + else: + return None + + +# ---------------------------------------------------------------------------- +class VerboseTB(TBTools): + """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead + of HTML. Requires inspect and pydoc. Crazy, man. + + Modified version which optionally strips the topmost entries from the + traceback, to be used with alternate interpreters (because their own code + would appear in the traceback).""" + + tb_highlight = "bg:ansiyellow" + tb_highlight_style = "default" + + def __init__( + self, + color_scheme: str = "Linux", + call_pdb: bool = False, + ostream=None, + tb_offset: int = 0, + long_header: bool = False, + include_vars: bool = True, + check_cache=None, + debugger_cls=None, + parent=None, + config=None, + ): + """Specify traceback offset, headers and color scheme. + + Define how many frames to drop from the tracebacks. Calling it with + tb_offset=1 allows use of this handler in interpreters which will have + their own code at the top of the traceback (VerboseTB will first + remove that frame before printing the traceback info).""" + TBTools.__init__( + self, + color_scheme=color_scheme, + call_pdb=call_pdb, + ostream=ostream, + parent=parent, + config=config, + debugger_cls=debugger_cls, + ) + self.tb_offset = tb_offset + self.long_header = long_header + self.include_vars = include_vars + # By default we use linecache.checkcache, but the user can provide a + # different check_cache implementation. This was formerly used by the + # IPython kernel for interactive code, but is no longer necessary. + if check_cache is None: + check_cache = linecache.checkcache + self.check_cache = check_cache + + self.skip_hidden = True + + def format_record(self, frame_info: FrameInfo): + """Format a single stack frame""" + assert isinstance(frame_info, FrameInfo) + Colors = self.Colors # just a shorthand + quicker name lookup + ColorsNormal = Colors.Normal # used a lot + + if isinstance(frame_info._sd, stack_data.RepeatedFrames): + return ' %s[... skipping similar frames: %s]%s\n' % ( + Colors.excName, frame_info.description, ColorsNormal) + + indent = " " * INDENT_SIZE + em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal) + tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}" + tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % ( + Colors.vName, + Colors.valEm, + ColorsNormal, + ) + tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal) + + link = _format_filename( + frame_info.filename, + Colors.filenameEm, + ColorsNormal, + lineno=frame_info.lineno, + ) + args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame) + if frame_info.executing is not None: + func = frame_info.executing.code_qualname() + else: + func = "?" + if func == "": + call = "" + else: + # Decide whether to include variable details or not + var_repr = eqrepr if self.include_vars else nullrepr + try: + scope = inspect.formatargvalues( + args, varargs, varkw, locals_, formatvalue=var_repr + ) + call = tpl_call.format(file=func, scope=scope) + except KeyError: + # This happens in situations like errors inside generator + # expressions, where local variables are listed in the + # line, but can't be extracted from the frame. I'm not + # 100% sure this isn't actually a bug in inspect itself, + # but since there's no info for us to compute with, the + # best we can do is report the failure and move on. Here + # we must *not* call any traceback construction again, + # because that would mess up use of %debug later on. So we + # simply report the failure and move on. The only + # limitation will be that this frame won't have locals + # listed in the call signature. Quite subtle problem... + # I can't think of a good way to validate this in a unit + # test, but running a script consisting of: + # dict( (k,v.strip()) for (k,v) in range(10) ) + # will illustrate the error, if this exception catch is + # disabled. + call = tpl_call_fail % func + + lvals = '' + lvals_list = [] + if self.include_vars: + try: + # we likely want to fix stackdata at some point, but + # still need a workaround. + fibp = frame_info.variables_in_executing_piece + for var in fibp: + lvals_list.append(tpl_name_val % (var.name, repr(var.value))) + except Exception: + lvals_list.append( + "Exception trying to inspect frame. No more locals available." + ) + if lvals_list: + lvals = '%s%s' % (indent, em_normal.join(lvals_list)) + + result = f'{link}{", " if call else ""}{call}\n' + if frame_info._sd is None: + # fast fallback if file is too long + tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal) + link = tpl_link % util_path.compress_user(frame_info.filename) + level = "%s %s\n" % (link, call) + _line_format = PyColorize.Parser( + style=self.color_scheme_table.active_scheme_name, parent=self + ).format2 + first_line = frame_info.code.co_firstlineno + current_line = frame_info.lineno[0] + raw_lines = frame_info.raw_lines + index = current_line - first_line + + if index >= frame_info.context: + start = max(index - frame_info.context, 0) + stop = index + frame_info.context + index = frame_info.context + else: + start = 0 + stop = index + frame_info.context + raw_lines = raw_lines[start:stop] + + return "%s%s" % ( + level, + "".join( + _simple_format_traceback_lines( + current_line, + index, + raw_lines, + Colors, + lvals, + _line_format, + ) + ), + ) + # result += "\n".join(frame_info.raw_lines) + else: + result += "".join( + _format_traceback_lines( + frame_info.lines, Colors, self.has_colors, lvals + ) + ) + return result + + def prepare_header(self, etype: str, long_version: bool = False): + colors = self.Colors # just a shorthand + quicker name lookup + colorsnormal = colors.Normal # used a lot + exc = '%s%s%s' % (colors.excName, etype, colorsnormal) + width = min(75, get_terminal_size()[0]) + if long_version: + # Header with the exception type, python version, and date + pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable + date = time.ctime(time.time()) + + head = "%s%s%s\n%s%s%s\n%s" % ( + colors.topline, + "-" * width, + colorsnormal, + exc, + " " * (width - len(etype) - len(pyver)), + pyver, + date.rjust(width), + ) + head += ( + "\nA problem occurred executing Python code. Here is the sequence of function" + "\ncalls leading up to the error, with the most recent (innermost) call last." + ) + else: + # Simplified header + head = "%s%s" % ( + exc, + "Traceback (most recent call last)".rjust(width - len(etype)), + ) + + return head + + def format_exception(self, etype, evalue): + colors = self.Colors # just a shorthand + quicker name lookup + colorsnormal = colors.Normal # used a lot + # Get (safely) a string form of the exception info + try: + etype_str, evalue_str = map(str, (etype, evalue)) + except: + # User exception is improperly defined. + etype, evalue = str, sys.exc_info()[:2] + etype_str, evalue_str = map(str, (etype, evalue)) + + # PEP-678 notes + notes = getattr(evalue, "__notes__", []) + if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)): + notes = [_safe_string(notes, "__notes__", func=repr)] + + # ... and format it + return [ + "{}{}{}: {}".format( + colors.excName, + etype_str, + colorsnormal, + py3compat.cast_unicode(evalue_str), + ), + *( + "{}{}".format( + colorsnormal, _safe_string(py3compat.cast_unicode(n), "note") + ) + for n in notes + ), + ] + + def format_exception_as_a_whole( + self, + etype: type, + evalue: Optional[BaseException], + etb: Optional[TracebackType], + number_of_lines_of_context, + tb_offset: Optional[int], + ): + """Formats the header, traceback and exception message for a single exception. + + This may be called multiple times by Python 3 exception chaining + (PEP 3134). + """ + # some locals + orig_etype = etype + try: + etype = etype.__name__ # type: ignore + except AttributeError: + pass + + tb_offset = self.tb_offset if tb_offset is None else tb_offset + assert isinstance(tb_offset, int) + head = self.prepare_header(str(etype), self.long_header) + records = ( + self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else [] + ) + + frames = [] + skipped = 0 + lastrecord = len(records) - 1 + for i, record in enumerate(records): + if ( + not isinstance(record._sd, stack_data.RepeatedFrames) + and self.skip_hidden + ): + if ( + record.frame.f_locals.get("__tracebackhide__", 0) + and i != lastrecord + ): + skipped += 1 + continue + if skipped: + Colors = self.Colors # just a shorthand + quicker name lookup + ColorsNormal = Colors.Normal # used a lot + frames.append( + " %s[... skipping hidden %s frame]%s\n" + % (Colors.excName, skipped, ColorsNormal) + ) + skipped = 0 + frames.append(self.format_record(record)) + if skipped: + Colors = self.Colors # just a shorthand + quicker name lookup + ColorsNormal = Colors.Normal # used a lot + frames.append( + " %s[... skipping hidden %s frame]%s\n" + % (Colors.excName, skipped, ColorsNormal) + ) + + formatted_exception = self.format_exception(etype, evalue) + if records: + frame_info = records[-1] + ipinst = get_ipython() + if ipinst is not None: + ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0) + + return [[head] + frames + formatted_exception] + + def get_records( + self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int + ): + assert etb is not None + context = number_of_lines_of_context - 1 + after = context // 2 + before = context - after + if self.has_colors: + style = get_style_by_name(self.tb_highlight_style) + style = stack_data.style_with_executing_node(style, self.tb_highlight) + formatter = Terminal256Formatter(style=style) + else: + formatter = None + options = stack_data.Options( + before=before, + after=after, + pygments_formatter=formatter, + ) + + # Let's estimate the amount of code we will have to parse/highlight. + cf: Optional[TracebackType] = etb + max_len = 0 + tbs = [] + while cf is not None: + try: + mod = inspect.getmodule(cf.tb_frame) + if mod is not None: + mod_name = mod.__name__ + root_name, *_ = mod_name.split(".") + if root_name == "IPython": + cf = cf.tb_next + continue + max_len = get_line_number_of_frame(cf.tb_frame) + + except OSError: + max_len = 0 + max_len = max(max_len, max_len) + tbs.append(cf) + cf = getattr(cf, "tb_next", None) + + if max_len > FAST_THRESHOLD: + FIs = [] + for tb in tbs: + frame = tb.tb_frame # type: ignore + lineno = (frame.f_lineno,) + code = frame.f_code + filename = code.co_filename + # TODO: Here we need to use before/after/ + FIs.append( + FrameInfo( + "Raw frame", filename, lineno, frame, code, context=context + ) + ) + return FIs + res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:] + res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res] + return res + + def structured_traceback( + self, + etype: type, + evalue: Optional[BaseException], + etb: Optional[TracebackType] = None, + tb_offset: Optional[int] = None, + number_of_lines_of_context: int = 5, + ): + """Return a nice text document describing the traceback.""" + formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context, + tb_offset) + + colors = self.Colors # just a shorthand + quicker name lookup + colorsnormal = colors.Normal # used a lot + head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal) + structured_traceback_parts = [head] + chained_exceptions_tb_offset = 0 + lines_of_context = 3 + formatted_exceptions = formatted_exception + exception = self.get_parts_of_chained_exception(evalue) + if exception: + assert evalue is not None + formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__) + etype, evalue, etb = exception + else: + evalue = None + chained_exc_ids = set() + while evalue: + formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context, + chained_exceptions_tb_offset) + exception = self.get_parts_of_chained_exception(evalue) + + if exception and not id(exception[1]) in chained_exc_ids: + chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop + formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__) + etype, evalue, etb = exception + else: + evalue = None + + # we want to see exceptions in a reversed order: + # the first exception should be on top + for formatted_exception in reversed(formatted_exceptions): + structured_traceback_parts += formatted_exception + + return structured_traceback_parts + + def debugger(self, force: bool = False): + """Call up the pdb debugger if desired, always clean up the tb + reference. + + Keywords: + + - force(False): by default, this routine checks the instance call_pdb + flag and does not actually invoke the debugger if the flag is false. + The 'force' option forces the debugger to activate even if the flag + is false. + + If the call_pdb flag is set, the pdb interactive debugger is + invoked. In all cases, the self.tb reference to the current traceback + is deleted to prevent lingering references which hamper memory + management. + + Note that each call to pdb() does an 'import readline', so if your app + requires a special setup for the readline completers, you'll have to + fix that by hand after invoking the exception handler.""" + + if force or self.call_pdb: + if self.pdb is None: + self.pdb = self.debugger_cls() + # the system displayhook may have changed, restore the original + # for pdb + display_trap = DisplayTrap(hook=sys.__displayhook__) + with display_trap: + self.pdb.reset() + # Find the right frame so we don't pop up inside ipython itself + if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type] + etb = self.tb # type: ignore[has-type] + else: + etb = self.tb = sys.last_traceback + while self.tb is not None and self.tb.tb_next is not None: + assert self.tb.tb_next is not None + self.tb = self.tb.tb_next + if etb and etb.tb_next: + etb = etb.tb_next + self.pdb.botframe = etb.tb_frame + # last_value should be deprecated, but last-exc sometimme not set + # please check why later and remove the getattr. + exc = sys.last_value if sys.version_info < (3, 12) else getattr(sys, "last_exc", sys.last_value) # type: ignore[attr-defined] + if exc: + self.pdb.interaction(None, exc) + else: + self.pdb.interaction(None, etb) + + if hasattr(self, 'tb'): + del self.tb + + def handler(self, info=None): + (etype, evalue, etb) = info or sys.exc_info() + self.tb = etb + ostream = self.ostream + ostream.flush() + ostream.write(self.text(etype, evalue, etb)) + ostream.write('\n') + ostream.flush() + + # Changed so an instance can just be called as VerboseTB_inst() and print + # out the right info on its own. + def __call__(self, etype=None, evalue=None, etb=None): + """This hook can replace sys.excepthook (for Python 2.1 or higher).""" + if etb is None: + self.handler() + else: + self.handler((etype, evalue, etb)) + try: + self.debugger() + except KeyboardInterrupt: + print("\nKeyboardInterrupt") + + +#---------------------------------------------------------------------------- +class FormattedTB(VerboseTB, ListTB): + """Subclass ListTB but allow calling with a traceback. + + It can thus be used as a sys.excepthook for Python > 2.1. + + Also adds 'Context' and 'Verbose' modes, not available in ListTB. + + Allows a tb_offset to be specified. This is useful for situations where + one needs to remove a number of topmost frames from the traceback (such as + occurs with python programs that themselves execute other python code, + like Python shells). """ + + mode: str + + def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False, + ostream=None, + tb_offset=0, long_header=False, include_vars=False, + check_cache=None, debugger_cls=None, + parent=None, config=None): + + # NEVER change the order of this list. Put new modes at the end: + self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal'] + self.verbose_modes = self.valid_modes[1:3] + + VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb, + ostream=ostream, tb_offset=tb_offset, + long_header=long_header, include_vars=include_vars, + check_cache=check_cache, debugger_cls=debugger_cls, + parent=parent, config=config) + + # Different types of tracebacks are joined with different separators to + # form a single string. They are taken from this dict + self._join_chars = dict(Plain='', Context='\n', Verbose='\n', + Minimal='') + # set_mode also sets the tb_join_char attribute + self.set_mode(mode) + + def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5): + tb_offset = self.tb_offset if tb_offset is None else tb_offset + mode = self.mode + if mode in self.verbose_modes: + # Verbose modes need a full traceback + return VerboseTB.structured_traceback( + self, etype, value, tb, tb_offset, number_of_lines_of_context + ) + elif mode == 'Minimal': + return ListTB.get_exception_only(self, etype, value) + else: + # We must check the source cache because otherwise we can print + # out-of-date source code. + self.check_cache() + # Now we can extract and format the exception + return ListTB.structured_traceback( + self, etype, value, tb, tb_offset, number_of_lines_of_context + ) + + def stb2text(self, stb): + """Convert a structured traceback (a list) to a string.""" + return self.tb_join_char.join(stb) + + def set_mode(self, mode: Optional[str] = None): + """Switch to the desired mode. + + If mode is not specified, cycles through the available modes.""" + + if not mode: + new_idx = (self.valid_modes.index(self.mode) + 1 ) % \ + len(self.valid_modes) + self.mode = self.valid_modes[new_idx] + elif mode not in self.valid_modes: + raise ValueError( + "Unrecognized mode in FormattedTB: <" + mode + ">\n" + "Valid modes: " + str(self.valid_modes) + ) + else: + assert isinstance(mode, str) + self.mode = mode + # include variable details only in 'Verbose' mode + self.include_vars = (self.mode == self.valid_modes[2]) + # Set the join character for generating text tracebacks + self.tb_join_char = self._join_chars[self.mode] + + # some convenient shortcuts + def plain(self): + self.set_mode(self.valid_modes[0]) + + def context(self): + self.set_mode(self.valid_modes[1]) + + def verbose(self): + self.set_mode(self.valid_modes[2]) + + def minimal(self): + self.set_mode(self.valid_modes[3]) + + +#---------------------------------------------------------------------------- +class AutoFormattedTB(FormattedTB): + """A traceback printer which can be called on the fly. + + It will find out about exceptions by itself. + + A brief example:: + + AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux') + try: + ... + except: + AutoTB() # or AutoTB(out=logfile) where logfile is an open file object + """ + + def __call__(self, etype=None, evalue=None, etb=None, + out=None, tb_offset=None): + """Print out a formatted exception traceback. + + Optional arguments: + - out: an open file-like object to direct output to. + + - tb_offset: the number of frames to skip over in the stack, on a + per-call basis (this overrides temporarily the instance's tb_offset + given at initialization time.""" + + if out is None: + out = self.ostream + out.flush() + out.write(self.text(etype, evalue, etb, tb_offset)) + out.write('\n') + out.flush() + # FIXME: we should remove the auto pdb behavior from here and leave + # that to the clients. + try: + self.debugger() + except KeyboardInterrupt: + print("\nKeyboardInterrupt") + + def structured_traceback( + self, + etype: type, + evalue: Optional[BaseException], + etb: Optional[TracebackType] = None, + tb_offset: Optional[int] = None, + number_of_lines_of_context: int = 5, + ): + # tb: TracebackType or tupleof tb types ? + if etype is None: + etype, evalue, etb = sys.exc_info() + if isinstance(etb, tuple): + # tb is a tuple if this is a chained exception. + self.tb = etb[0] + else: + self.tb = etb + return FormattedTB.structured_traceback( + self, etype, evalue, etb, tb_offset, number_of_lines_of_context + ) + + +#--------------------------------------------------------------------------- + +# A simple class to preserve Nathan's original functionality. +class ColorTB(FormattedTB): + """Shorthand to initialize a FormattedTB in Linux colors mode.""" + + def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs): + FormattedTB.__init__(self, color_scheme=color_scheme, + call_pdb=call_pdb, **kwargs) + + +class SyntaxTB(ListTB): + """Extension which holds some state: the last exception value""" + + def __init__(self, color_scheme='NoColor', parent=None, config=None): + ListTB.__init__(self, color_scheme, parent=parent, config=config) + self.last_syntax_error = None + + def __call__(self, etype, value, elist): + self.last_syntax_error = value + + ListTB.__call__(self, etype, value, elist) + + def structured_traceback(self, etype, value, elist, tb_offset=None, + context=5): + # If the source file has been edited, the line in the syntax error can + # be wrong (retrieved from an outdated cache). This replaces it with + # the current value. + if isinstance(value, SyntaxError) \ + and isinstance(value.filename, str) \ + and isinstance(value.lineno, int): + linecache.checkcache(value.filename) + newtext = linecache.getline(value.filename, value.lineno) + if newtext: + value.text = newtext + self.last_syntax_error = value + return super(SyntaxTB, self).structured_traceback(etype, value, elist, + tb_offset=tb_offset, context=context) + + def clear_err_state(self): + """Return the current error state and clear it""" + e = self.last_syntax_error + self.last_syntax_error = None + return e + + def stb2text(self, stb): + """Convert a structured traceback (a list) to a string.""" + return ''.join(stb) + + +# some internal-use functions +def text_repr(value): + """Hopefully pretty robust repr equivalent.""" + # this is pretty horrible but should always return *something* + try: + return pydoc.text.repr(value) # type: ignore[call-arg] + except KeyboardInterrupt: + raise + except: + try: + return repr(value) + except KeyboardInterrupt: + raise + except: + try: + # all still in an except block so we catch + # getattr raising + name = getattr(value, '__name__', None) + if name: + # ick, recursion + return text_repr(name) + klass = getattr(value, '__class__', None) + if klass: + return '%s instance' % text_repr(klass) + except KeyboardInterrupt: + raise + except: + return 'UNRECOVERABLE REPR FAILURE' + + +def eqrepr(value, repr=text_repr): + return '=%s' % repr(value) + + +def nullrepr(value, repr=text_repr): + return '' diff --git a/myenv/lib/python3.12/site-packages/IPython/core/usage.py b/myenv/lib/python3.12/site-packages/IPython/core/usage.py new file mode 100644 index 00000000..53219bce --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/core/usage.py @@ -0,0 +1,341 @@ +# -*- coding: utf-8 -*- +"""Usage information for the main IPython applications. +""" +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# Copyright (C) 2001-2007 Fernando Perez. +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +import sys +from IPython.core import release + +cl_usage = """\ +========= + IPython +========= + +Tools for Interactive Computing in Python +========================================= + + A Python shell with automatic history (input and output), dynamic object + introspection, easier configuration, command completion, access to the + system shell and more. IPython can also be embedded in running programs. + + +Usage + + ipython [subcommand] [options] [-c cmd | -m mod | file] [--] [arg] ... + + If invoked with no options, it executes the file and exits, passing the + remaining arguments to the script, just as if you had specified the same + command with python. You may need to specify `--` before args to be passed + to the script, to prevent IPython from attempting to parse them. If you + specify the option `-i` before the filename, it will enter an interactive + IPython session after running the script, rather than exiting. Files ending + in .py will be treated as normal Python, but files ending in .ipy can + contain special IPython syntax (magic commands, shell expansions, etc.). + + Almost all configuration in IPython is available via the command-line. Do + `ipython --help-all` to see all available options. For persistent + configuration, look into your `ipython_config.py` configuration file for + details. + + This file is typically installed in the `IPYTHONDIR` directory, and there + is a separate configuration directory for each profile. The default profile + directory will be located in $IPYTHONDIR/profile_default. IPYTHONDIR + defaults to to `$HOME/.ipython`. For Windows users, $HOME resolves to + C:\\Users\\YourUserName in most instances. + + To initialize a profile with the default configuration file, do:: + + $> ipython profile create + + and start editing `IPYTHONDIR/profile_default/ipython_config.py` + + In IPython's documentation, we will refer to this directory as + `IPYTHONDIR`, you can change its default location by creating an + environment variable with this name and setting it to the desired path. + + For more information, see the manual available in HTML and PDF in your + installation, or online at https://ipython.org/documentation.html. +""" + +interactive_usage = """ +IPython -- An enhanced Interactive Python +========================================= + +IPython offers a fully compatible replacement for the standard Python +interpreter, with convenient shell features, special commands, command +history mechanism and output results caching. + +At your system command line, type 'ipython -h' to see the command line +options available. This document only describes interactive features. + +GETTING HELP +------------ + +Within IPython you have various way to access help: + + ? -> Introduction and overview of IPython's features (this screen). + object? -> Details about 'object'. + object?? -> More detailed, verbose information about 'object'. + %quickref -> Quick reference of all IPython specific syntax and magics. + help -> Access Python's own help system. + +If you are in terminal IPython you can quit this screen by pressing `q`. + + +MAIN FEATURES +------------- + +* Access to the standard Python help with object docstrings and the Python + manuals. Simply type 'help' (no quotes) to invoke it. + +* Magic commands: type %magic for information on the magic subsystem. + +* System command aliases, via the %alias command or the configuration file(s). + +* Dynamic object information: + + Typing ?word or word? prints detailed information about an object. Certain + long strings (code, etc.) get snipped in the center for brevity. + + Typing ??word or word?? gives access to the full information without + snipping long strings. Strings that are longer than the screen are printed + through the less pager. + + The ?/?? system gives access to the full source code for any object (if + available), shows function prototypes and other useful information. + + If you just want to see an object's docstring, type '%pdoc object' (without + quotes, and without % if you have automagic on). + +* Tab completion in the local namespace: + + At any time, hitting tab will complete any available python commands or + variable names, and show you a list of the possible completions if there's + no unambiguous one. It will also complete filenames in the current directory. + +* Search previous command history in multiple ways: + + - Start typing, and then use arrow keys up/down or (Ctrl-p/Ctrl-n) to search + through the history items that match what you've typed so far. + + - Hit Ctrl-r: opens a search prompt. Begin typing and the system searches + your history for lines that match what you've typed so far, completing as + much as it can. + + - %hist: search history by index. + +* Persistent command history across sessions. + +* Logging of input with the ability to save and restore a working session. + +* System shell with !. Typing !ls will run 'ls' in the current directory. + +* The reload command does a 'deep' reload of a module: changes made to the + module since you imported will actually be available without having to exit. + +* Verbose and colored exception traceback printouts. See the magic xmode and + xcolor functions for details (just type %magic). + +* Input caching system: + + IPython offers numbered prompts (In/Out) with input and output caching. All + input is saved and can be retrieved as variables (besides the usual arrow + key recall). + + The following GLOBAL variables always exist (so don't overwrite them!): + _i: stores previous input. + _ii: next previous. + _iii: next-next previous. + _ih : a list of all input _ih[n] is the input from line n. + + Additionally, global variables named _i are dynamically created ( + being the prompt counter), such that _i == _ih[] + + For example, what you typed at prompt 14 is available as _i14 and _ih[14]. + + You can create macros which contain multiple input lines from this history, + for later re-execution, with the %macro function. + + The history function %hist allows you to see any part of your input history + by printing a range of the _i variables. Note that inputs which contain + magic functions (%) appear in the history with a prepended comment. This is + because they aren't really valid Python code, so you can't exec them. + +* Output caching system: + + For output that is returned from actions, a system similar to the input + cache exists but using _ instead of _i. Only actions that produce a result + (NOT assignments, for example) are cached. If you are familiar with + Mathematica, IPython's _ variables behave exactly like Mathematica's % + variables. + + The following GLOBAL variables always exist (so don't overwrite them!): + _ (one underscore): previous output. + __ (two underscores): next previous. + ___ (three underscores): next-next previous. + + Global variables named _ are dynamically created ( being the prompt + counter), such that the result of output is always available as _. + + Finally, a global dictionary named _oh exists with entries for all lines + which generated output. + +* Directory history: + + Your history of visited directories is kept in the global list _dh, and the + magic %cd command can be used to go to any entry in that list. + +* Auto-parentheses and auto-quotes (adapted from Nathan Gray's LazyPython) + + 1. Auto-parentheses + + Callable objects (i.e. functions, methods, etc) can be invoked like + this (notice the commas between the arguments):: + + In [1]: callable_ob arg1, arg2, arg3 + + and the input will be translated to this:: + + callable_ob(arg1, arg2, arg3) + + This feature is off by default (in rare cases it can produce + undesirable side-effects), but you can activate it at the command-line + by starting IPython with `--autocall 1`, set it permanently in your + configuration file, or turn on at runtime with `%autocall 1`. + + You can force auto-parentheses by using '/' as the first character + of a line. For example:: + + In [1]: /globals # becomes 'globals()' + + Note that the '/' MUST be the first character on the line! This + won't work:: + + In [2]: print /globals # syntax error + + In most cases the automatic algorithm should work, so you should + rarely need to explicitly invoke /. One notable exception is if you + are trying to call a function with a list of tuples as arguments (the + parenthesis will confuse IPython):: + + In [1]: zip (1,2,3),(4,5,6) # won't work + + but this will work:: + + In [2]: /zip (1,2,3),(4,5,6) + ------> zip ((1,2,3),(4,5,6)) + Out[2]= [(1, 4), (2, 5), (3, 6)] + + IPython tells you that it has altered your command line by + displaying the new command line preceded by -->. e.g.:: + + In [18]: callable list + -------> callable (list) + + 2. Auto-Quoting + + You can force auto-quoting of a function's arguments by using ',' as + the first character of a line. For example:: + + In [1]: ,my_function /home/me # becomes my_function("/home/me") + + If you use ';' instead, the whole argument is quoted as a single + string (while ',' splits on whitespace):: + + In [2]: ,my_function a b c # becomes my_function("a","b","c") + In [3]: ;my_function a b c # becomes my_function("a b c") + + Note that the ',' MUST be the first character on the line! This + won't work:: + + In [4]: x = ,my_function /home/me # syntax error +""" + +interactive_usage_min = """\ +An enhanced console for Python. +Some of its features are: +- Tab completion in the local namespace. +- Logging of input, see command-line options. +- System shell escape via ! , eg !ls. +- Magic commands, starting with a % (like %ls, %pwd, %cd, etc.) +- Keeps track of locally defined variables via %who, %whos. +- Show object information with a ? eg ?x or x? (use ?? for more info). +""" + +quick_reference = r""" +IPython -- An enhanced Interactive Python - Quick Reference Card +================================================================ + +obj?, obj?? : Get help, or more help for object (also works as + ?obj, ??obj). +?foo.*abc* : List names in 'foo' containing 'abc' in them. +%magic : Information about IPython's 'magic' % functions. + +Magic functions are prefixed by % or %%, and typically take their arguments +without parentheses, quotes or even commas for convenience. Line magics take a +single % and cell magics are prefixed with two %%. + +Example magic function calls: + +%alias d ls -F : 'd' is now an alias for 'ls -F' +alias d ls -F : Works if 'alias' not a python name +alist = %alias : Get list of aliases to 'alist' +cd /usr/share : Obvious. cd - to choose from visited dirs. +%cd?? : See help AND source for magic %cd +%timeit x=10 : time the 'x=10' statement with high precision. +%%timeit x=2**100 +x**100 : time 'x**100' with a setup of 'x=2**100'; setup code is not + counted. This is an example of a cell magic. + +System commands: + +!cp a.txt b/ : System command escape, calls os.system() +cp a.txt b/ : after %rehashx, most system commands work without ! +cp ${f}.txt $bar : Variable expansion in magics and system commands +files = !ls /usr : Capture system command output +files.s, files.l, files.n: "a b c", ['a','b','c'], 'a\nb\nc' + +History: + +_i, _ii, _iii : Previous, next previous, next next previous input +_i4, _ih[2:5] : Input history line 4, lines 2-4 +exec(_i81) : Execute input history line #81 again +%rep 81 : Edit input history line #81 +_, __, ___ : previous, next previous, next next previous output +_dh : Directory history +_oh : Output history +%hist : Command history of current session. +%hist -g foo : Search command history of (almost) all sessions for 'foo'. +%hist -g : Command history of (almost) all sessions. +%hist 1/2-8 : Command history containing lines 2-8 of session 1. +%hist 1/ ~2/ : Command history of session 1 and 2 sessions before current. +%hist ~8/1-~6/5 : Command history from line 1 of 8 sessions ago to + line 5 of 6 sessions ago. +%edit 0/ : Open editor to execute code with history of current session. + +Autocall: + +f 1,2 : f(1,2) # Off by default, enable with %autocall magic. +/f 1,2 : f(1,2) (forced autoparen) +,f 1 2 : f("1","2") +;f 1 2 : f("1 2") + +Remember: TAB completion works in many contexts, not just file names +or python names. + +The following magic functions are currently available: + +""" + +default_banner_parts = ["Python %s\n"%sys.version.split("\n")[0], + "Type 'copyright', 'credits' or 'license' for more information\n" , + "IPython {version} -- An enhanced Interactive Python. Type '?' for help.\n".format(version=release.version), +] + +default_banner = ''.join(default_banner_parts) diff --git a/myenv/lib/python3.12/site-packages/IPython/display.py b/myenv/lib/python3.12/site-packages/IPython/display.py new file mode 100644 index 00000000..3ccbc486 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/display.py @@ -0,0 +1,44 @@ +"""Public API for display tools in IPython. +""" + +# ----------------------------------------------------------------------------- +# Copyright (C) 2012 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- + +from IPython.core.display_functions import * +from IPython.core.display import ( + display_pretty as display_pretty, + display_html as display_html, + display_markdown as display_markdown, + display_svg as display_svg, + display_png as display_png, + display_jpeg as display_jpeg, + display_latex as display_latex, + display_json as display_json, + display_javascript as display_javascript, + display_pdf as display_pdf, + DisplayObject as DisplayObject, + TextDisplayObject as TextDisplayObject, + Pretty as Pretty, + HTML as HTML, + Markdown as Markdown, + Math as Math, + Latex as Latex, + SVG as SVG, + ProgressBar as ProgressBar, + JSON as JSON, + GeoJSON as GeoJSON, + Javascript as Javascript, + Image as Image, + set_matplotlib_formats as set_matplotlib_formats, + set_matplotlib_close as set_matplotlib_close, + Video as Video, +) +from IPython.lib.display import * diff --git a/myenv/lib/python3.12/site-packages/IPython/extensions/__init__.py b/myenv/lib/python3.12/site-packages/IPython/extensions/__init__.py new file mode 100644 index 00000000..db7f79fc --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/extensions/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +"""This directory is meant for IPython extensions.""" diff --git a/myenv/lib/python3.12/site-packages/IPython/extensions/autoreload.py b/myenv/lib/python3.12/site-packages/IPython/extensions/autoreload.py new file mode 100644 index 00000000..98c9a0d0 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/extensions/autoreload.py @@ -0,0 +1,727 @@ +"""IPython extension to reload modules before executing user code. + +``autoreload`` reloads modules automatically before entering the execution of +code typed at the IPython prompt. + +This makes for example the following workflow possible: + +.. sourcecode:: ipython + + In [1]: %load_ext autoreload + + In [2]: %autoreload 2 + + In [3]: from foo import some_function + + In [4]: some_function() + Out[4]: 42 + + In [5]: # open foo.py in an editor and change some_function to return 43 + + In [6]: some_function() + Out[6]: 43 + +The module was reloaded without reloading it explicitly, and the object +imported with ``from foo import ...`` was also updated. + +Usage +===== + +The following magic commands are provided: + +``%autoreload``, ``%autoreload now`` + + Reload all modules (except those excluded by ``%aimport``) + automatically now. + +``%autoreload 0``, ``%autoreload off`` + + Disable automatic reloading. + +``%autoreload 1``, ``%autoreload explicit`` + + Reload all modules imported with ``%aimport`` every time before + executing the Python code typed. + +``%autoreload 2``, ``%autoreload all`` + + Reload all modules (except those excluded by ``%aimport``) every + time before executing the Python code typed. + +``%autoreload 3``, ``%autoreload complete`` + + Same as 2/all, but also adds any new objects in the module. See + unit test at IPython/extensions/tests/test_autoreload.py::test_autoload_newly_added_objects + + Adding ``--print`` or ``-p`` to the ``%autoreload`` line will print autoreload activity to + standard out. ``--log`` or ``-l`` will do it to the log at INFO level; both can be used + simultaneously. + +``%aimport`` + + List modules which are to be automatically imported or not to be imported. + +``%aimport foo`` + + Import module 'foo' and mark it to be autoreloaded for ``%autoreload 1`` + +``%aimport foo, bar`` + + Import modules 'foo', 'bar' and mark them to be autoreloaded for ``%autoreload 1`` + +``%aimport -foo`` + + Mark module 'foo' to not be autoreloaded. + +Caveats +======= + +Reloading Python modules in a reliable way is in general difficult, +and unexpected things may occur. ``%autoreload`` tries to work around +common pitfalls by replacing function code objects and parts of +classes previously in the module with new versions. This makes the +following things to work: + +- Functions and classes imported via 'from xxx import foo' are upgraded + to new versions when 'xxx' is reloaded. + +- Methods and properties of classes are upgraded on reload, so that + calling 'c.foo()' on an object 'c' created before the reload causes + the new code for 'foo' to be executed. + +Some of the known remaining caveats are: + +- Replacing code objects does not always succeed: changing a @property + in a class to an ordinary method or a method to a member variable + can cause problems (but in old objects only). + +- Functions that are removed (eg. via monkey-patching) from a module + before it is reloaded are not upgraded. + +- C extension modules cannot be reloaded, and so cannot be autoreloaded. + +- While comparing Enum and Flag, the 'is' Identity Operator is used (even in the case '==' has been used (Similar to the 'None' keyword)). + +- Reloading a module, or importing the same module by a different name, creates new Enums. These may look the same, but are not. +""" + +from IPython.core import magic_arguments +from IPython.core.magic import Magics, magics_class, line_magic + +__skip_doctest__ = True + +# ----------------------------------------------------------------------------- +# Copyright (C) 2000 Thomas Heller +# Copyright (C) 2008 Pauli Virtanen +# Copyright (C) 2012 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +# ----------------------------------------------------------------------------- +# +# This IPython module is written by Pauli Virtanen, based on the autoreload +# code by Thomas Heller. + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- + +import os +import sys +import traceback +import types +import weakref +import gc +import logging +from importlib import import_module, reload +from importlib.util import source_from_cache + +# ------------------------------------------------------------------------------ +# Autoreload functionality +# ------------------------------------------------------------------------------ + + +class ModuleReloader: + enabled = False + """Whether this reloader is enabled""" + + check_all = True + """Autoreload all modules, not just those listed in 'modules'""" + + autoload_obj = False + """Autoreload all modules AND autoload all new objects""" + + def __init__(self, shell=None): + # Modules that failed to reload: {module: mtime-on-failed-reload, ...} + self.failed = {} + # Modules specially marked as autoreloadable. + self.modules = {} + # Modules specially marked as not autoreloadable. + self.skip_modules = {} + # (module-name, name) -> weakref, for replacing old code objects + self.old_objects = {} + # Module modification timestamps + self.modules_mtimes = {} + self.shell = shell + + # Reporting callable for verbosity + self._report = lambda msg: None # by default, be quiet. + + # Cache module modification times + self.check(check_all=True, do_reload=False) + + # To hide autoreload errors + self.hide_errors = False + + def mark_module_skipped(self, module_name): + """Skip reloading the named module in the future""" + try: + del self.modules[module_name] + except KeyError: + pass + self.skip_modules[module_name] = True + + def mark_module_reloadable(self, module_name): + """Reload the named module in the future (if it is imported)""" + try: + del self.skip_modules[module_name] + except KeyError: + pass + self.modules[module_name] = True + + def aimport_module(self, module_name): + """Import a module, and mark it reloadable + + Returns + ------- + top_module : module + The imported module if it is top-level, or the top-level + top_name : module + Name of top_module + + """ + self.mark_module_reloadable(module_name) + + import_module(module_name) + top_name = module_name.split(".")[0] + top_module = sys.modules[top_name] + return top_module, top_name + + def filename_and_mtime(self, module): + if not hasattr(module, "__file__") or module.__file__ is None: + return None, None + + if getattr(module, "__name__", None) in [None, "__mp_main__", "__main__"]: + # we cannot reload(__main__) or reload(__mp_main__) + return None, None + + filename = module.__file__ + path, ext = os.path.splitext(filename) + + if ext.lower() == ".py": + py_filename = filename + else: + try: + py_filename = source_from_cache(filename) + except ValueError: + return None, None + + try: + pymtime = os.stat(py_filename).st_mtime + except OSError: + return None, None + + return py_filename, pymtime + + def check(self, check_all=False, do_reload=True): + """Check whether some modules need to be reloaded.""" + + if not self.enabled and not check_all: + return + + if check_all or self.check_all: + modules = list(sys.modules.keys()) + else: + modules = list(self.modules.keys()) + + for modname in modules: + m = sys.modules.get(modname, None) + + if modname in self.skip_modules: + continue + + py_filename, pymtime = self.filename_and_mtime(m) + if py_filename is None: + continue + + try: + if pymtime <= self.modules_mtimes[modname]: + continue + except KeyError: + self.modules_mtimes[modname] = pymtime + continue + else: + if self.failed.get(py_filename, None) == pymtime: + continue + + self.modules_mtimes[modname] = pymtime + + # If we've reached this point, we should try to reload the module + if do_reload: + self._report(f"Reloading '{modname}'.") + try: + if self.autoload_obj: + superreload(m, reload, self.old_objects, self.shell) + else: + superreload(m, reload, self.old_objects) + if py_filename in self.failed: + del self.failed[py_filename] + except: + if not self.hide_errors: + print( + "[autoreload of {} failed: {}]".format( + modname, traceback.format_exc(10) + ), + file=sys.stderr, + ) + self.failed[py_filename] = pymtime + + +# ------------------------------------------------------------------------------ +# superreload +# ------------------------------------------------------------------------------ + + +func_attrs = [ + "__code__", + "__defaults__", + "__doc__", + "__closure__", + "__globals__", + "__dict__", +] + + +def update_function(old, new): + """Upgrade the code object of a function""" + for name in func_attrs: + try: + setattr(old, name, getattr(new, name)) + except (AttributeError, TypeError): + pass + + +def update_instances(old, new): + """Use garbage collector to find all instances that refer to the old + class definition and update their __class__ to point to the new class + definition""" + + refs = gc.get_referrers(old) + + for ref in refs: + if type(ref) is old: + object.__setattr__(ref, "__class__", new) + + +def update_class(old, new): + """Replace stuff in the __dict__ of a class, and upgrade + method code objects, and add new methods, if any""" + for key in list(old.__dict__.keys()): + old_obj = getattr(old, key) + try: + new_obj = getattr(new, key) + # explicitly checking that comparison returns True to handle + # cases where `==` doesn't return a boolean. + if (old_obj == new_obj) is True: + continue + except AttributeError: + # obsolete attribute: remove it + try: + delattr(old, key) + except (AttributeError, TypeError): + pass + continue + except ValueError: + # can't compare nested structures containing + # numpy arrays using `==` + pass + + if update_generic(old_obj, new_obj): + continue + + try: + setattr(old, key, getattr(new, key)) + except (AttributeError, TypeError): + pass # skip non-writable attributes + + for key in list(new.__dict__.keys()): + if key not in list(old.__dict__.keys()): + try: + setattr(old, key, getattr(new, key)) + except (AttributeError, TypeError): + pass # skip non-writable attributes + + # update all instances of class + update_instances(old, new) + + +def update_property(old, new): + """Replace get/set/del functions of a property""" + update_generic(old.fdel, new.fdel) + update_generic(old.fget, new.fget) + update_generic(old.fset, new.fset) + + +def isinstance2(a, b, typ): + return isinstance(a, typ) and isinstance(b, typ) + + +UPDATE_RULES = [ + (lambda a, b: isinstance2(a, b, type), update_class), + (lambda a, b: isinstance2(a, b, types.FunctionType), update_function), + (lambda a, b: isinstance2(a, b, property), update_property), +] +UPDATE_RULES.extend( + [ + ( + lambda a, b: isinstance2(a, b, types.MethodType), + lambda a, b: update_function(a.__func__, b.__func__), + ), + ] +) + + +def update_generic(a, b): + for type_check, update in UPDATE_RULES: + if type_check(a, b): + update(a, b) + return True + return False + + +class StrongRef: + def __init__(self, obj): + self.obj = obj + + def __call__(self): + return self.obj + + +mod_attrs = [ + "__name__", + "__doc__", + "__package__", + "__loader__", + "__spec__", + "__file__", + "__cached__", + "__builtins__", +] + + +def append_obj(module, d, name, obj, autoload=False): + in_module = hasattr(obj, "__module__") and obj.__module__ == module.__name__ + if autoload: + # check needed for module global built-ins + if not in_module and name in mod_attrs: + return False + else: + if not in_module: + return False + + key = (module.__name__, name) + try: + d.setdefault(key, []).append(weakref.ref(obj)) + except TypeError: + pass + return True + + +def superreload(module, reload=reload, old_objects=None, shell=None): + """Enhanced version of the builtin reload function. + + superreload remembers objects previously in the module, and + + - upgrades the class dictionary of every old class in the module + - upgrades the code object of every old function and method + - clears the module's namespace before reloading + + """ + if old_objects is None: + old_objects = {} + + # collect old objects in the module + for name, obj in list(module.__dict__.items()): + if not append_obj(module, old_objects, name, obj): + continue + key = (module.__name__, name) + try: + old_objects.setdefault(key, []).append(weakref.ref(obj)) + except TypeError: + pass + + # reload module + try: + # clear namespace first from old cruft + old_dict = module.__dict__.copy() + old_name = module.__name__ + module.__dict__.clear() + module.__dict__["__name__"] = old_name + module.__dict__["__loader__"] = old_dict["__loader__"] + except (TypeError, AttributeError, KeyError): + pass + + try: + module = reload(module) + except: + # restore module dictionary on failed reload + module.__dict__.update(old_dict) + raise + + # iterate over all objects and update functions & classes + for name, new_obj in list(module.__dict__.items()): + key = (module.__name__, name) + if key not in old_objects: + # here 'shell' acts both as a flag and as an output var + if ( + shell is None + or name == "Enum" + or not append_obj(module, old_objects, name, new_obj, True) + ): + continue + shell.user_ns[name] = new_obj + + new_refs = [] + for old_ref in old_objects[key]: + old_obj = old_ref() + if old_obj is None: + continue + new_refs.append(old_ref) + update_generic(old_obj, new_obj) + + if new_refs: + old_objects[key] = new_refs + else: + del old_objects[key] + + return module + + +# ------------------------------------------------------------------------------ +# IPython connectivity +# ------------------------------------------------------------------------------ + + +@magics_class +class AutoreloadMagics(Magics): + def __init__(self, *a, **kw): + super().__init__(*a, **kw) + self._reloader = ModuleReloader(self.shell) + self._reloader.check_all = False + self._reloader.autoload_obj = False + self.loaded_modules = set(sys.modules) + + @line_magic + @magic_arguments.magic_arguments() + @magic_arguments.argument( + "mode", + type=str, + default="now", + nargs="?", + help="""blank or 'now' - Reload all modules (except those excluded by %%aimport) + automatically now. + + '0' or 'off' - Disable automatic reloading. + + '1' or 'explicit' - Reload only modules imported with %%aimport every + time before executing the Python code typed. + + '2' or 'all' - Reload all modules (except those excluded by %%aimport) + every time before executing the Python code typed. + + '3' or 'complete' - Same as 2/all, but also but also adds any new + objects in the module. + """, + ) + @magic_arguments.argument( + "-p", + "--print", + action="store_true", + default=False, + help="Show autoreload activity using `print` statements", + ) + @magic_arguments.argument( + "-l", + "--log", + action="store_true", + default=False, + help="Show autoreload activity using the logger", + ) + @magic_arguments.argument( + "--hide-errors", + action="store_true", + default=False, + help="Hide autoreload errors", + ) + def autoreload(self, line=""): + r"""%autoreload => Reload modules automatically + + %autoreload or %autoreload now + Reload all modules (except those excluded by %aimport) automatically + now. + + %autoreload 0 or %autoreload off + Disable automatic reloading. + + %autoreload 1 or %autoreload explicit + Reload only modules imported with %aimport every time before executing + the Python code typed. + + %autoreload 2 or %autoreload all + Reload all modules (except those excluded by %aimport) every time + before executing the Python code typed. + + %autoreload 3 or %autoreload complete + Same as 2/all, but also but also adds any new objects in the module. See + unit test at IPython/extensions/tests/test_autoreload.py::test_autoload_newly_added_objects + + The optional arguments --print and --log control display of autoreload activity. The default + is to act silently; --print (or -p) will print out the names of modules that are being + reloaded, and --log (or -l) outputs them to the log at INFO level. + + The optional argument --hide-errors hides any errors that can happen when trying to + reload code. + + Reloading Python modules in a reliable way is in general + difficult, and unexpected things may occur. %autoreload tries to + work around common pitfalls by replacing function code objects and + parts of classes previously in the module with new versions. This + makes the following things to work: + + - Functions and classes imported via 'from xxx import foo' are upgraded + to new versions when 'xxx' is reloaded. + + - Methods and properties of classes are upgraded on reload, so that + calling 'c.foo()' on an object 'c' created before the reload causes + the new code for 'foo' to be executed. + + Some of the known remaining caveats are: + + - Replacing code objects does not always succeed: changing a @property + in a class to an ordinary method or a method to a member variable + can cause problems (but in old objects only). + + - Functions that are removed (eg. via monkey-patching) from a module + before it is reloaded are not upgraded. + + - C extension modules cannot be reloaded, and so cannot be + autoreloaded. + + """ + args = magic_arguments.parse_argstring(self.autoreload, line) + mode = args.mode.lower() + + p = print + + logger = logging.getLogger("autoreload") + + l = logger.info + + def pl(msg): + p(msg) + l(msg) + + if args.print is False and args.log is False: + self._reloader._report = lambda msg: None + elif args.print is True: + if args.log is True: + self._reloader._report = pl + else: + self._reloader._report = p + elif args.log is True: + self._reloader._report = l + + self._reloader.hide_errors = args.hide_errors + + if mode == "" or mode == "now": + self._reloader.check(True) + elif mode == "0" or mode == "off": + self._reloader.enabled = False + elif mode == "1" or mode == "explicit": + self._reloader.enabled = True + self._reloader.check_all = False + self._reloader.autoload_obj = False + elif mode == "2" or mode == "all": + self._reloader.enabled = True + self._reloader.check_all = True + self._reloader.autoload_obj = False + elif mode == "3" or mode == "complete": + self._reloader.enabled = True + self._reloader.check_all = True + self._reloader.autoload_obj = True + else: + raise ValueError(f'Unrecognized autoreload mode "{mode}".') + + @line_magic + def aimport(self, parameter_s="", stream=None): + """%aimport => Import modules for automatic reloading. + + %aimport + List modules to automatically import and not to import. + + %aimport foo + Import module 'foo' and mark it to be autoreloaded for %autoreload explicit + + %aimport foo, bar + Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload explicit + + %aimport -foo, bar + Mark module 'foo' to not be autoreloaded for %autoreload explicit, all, or complete, and 'bar' + to be autoreloaded for mode explicit. + """ + modname = parameter_s + if not modname: + to_reload = sorted(self._reloader.modules.keys()) + to_skip = sorted(self._reloader.skip_modules.keys()) + if stream is None: + stream = sys.stdout + if self._reloader.check_all: + stream.write("Modules to reload:\nall-except-skipped\n") + else: + stream.write("Modules to reload:\n%s\n" % " ".join(to_reload)) + stream.write("\nModules to skip:\n%s\n" % " ".join(to_skip)) + else: + for _module in [_.strip() for _ in modname.split(",")]: + if _module.startswith("-"): + _module = _module[1:].strip() + self._reloader.mark_module_skipped(_module) + else: + top_module, top_name = self._reloader.aimport_module(_module) + + # Inject module to user namespace + self.shell.push({top_name: top_module}) + + def pre_run_cell(self, info): + if self._reloader.enabled: + try: + self._reloader.check() + except: + pass + + def post_execute_hook(self): + """Cache the modification times of any modules imported in this execution""" + newly_loaded_modules = set(sys.modules) - self.loaded_modules + for modname in newly_loaded_modules: + _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname]) + if pymtime is not None: + self._reloader.modules_mtimes[modname] = pymtime + + self.loaded_modules.update(newly_loaded_modules) + + +def load_ipython_extension(ip): + """Load the extension in IPython.""" + auto_reload = AutoreloadMagics(ip) + ip.register_magics(auto_reload) + ip.events.register("pre_run_cell", auto_reload.pre_run_cell) + ip.events.register("post_execute", auto_reload.post_execute_hook) diff --git a/myenv/lib/python3.12/site-packages/IPython/extensions/storemagic.py b/myenv/lib/python3.12/site-packages/IPython/extensions/storemagic.py new file mode 100644 index 00000000..1ab56f72 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/extensions/storemagic.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +""" +%store magic for lightweight persistence. + +Stores variables, aliases and macros in IPython's database. + +To automatically restore stored variables at startup, add this to your +:file:`ipython_config.py` file:: + + c.StoreMagics.autorestore = True +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import inspect, os, sys, textwrap + +from IPython.core.error import UsageError +from IPython.core.magic import Magics, magics_class, line_magic +from IPython.testing.skipdoctest import skip_doctest +from traitlets import Bool + + +def restore_aliases(ip, alias=None): + staliases = ip.db.get('stored_aliases', {}) + if alias is None: + for k,v in staliases.items(): + # print("restore alias",k,v) # dbg + #self.alias_table[k] = v + ip.alias_manager.define_alias(k,v) + else: + ip.alias_manager.define_alias(alias, staliases[alias]) + + +def refresh_variables(ip): + db = ip.db + for key in db.keys('autorestore/*'): + # strip autorestore + justkey = os.path.basename(key) + try: + obj = db[key] + except KeyError: + print("Unable to restore variable '%s', ignoring (use %%store -d to forget!)" % justkey) + print("The error was:", sys.exc_info()[0]) + else: + # print("restored",justkey,"=",obj) # dbg + ip.user_ns[justkey] = obj + + +def restore_dhist(ip): + ip.user_ns['_dh'] = ip.db.get('dhist',[]) + + +def restore_data(ip): + refresh_variables(ip) + restore_aliases(ip) + restore_dhist(ip) + + +@magics_class +class StoreMagics(Magics): + """Lightweight persistence for python variables. + + Provides the %store magic.""" + + autorestore = Bool(False, help= + """If True, any %store-d variables will be automatically restored + when IPython starts. + """ + ).tag(config=True) + + def __init__(self, shell): + super(StoreMagics, self).__init__(shell=shell) + self.shell.configurables.append(self) + if self.autorestore: + restore_data(self.shell) + + @skip_doctest + @line_magic + def store(self, parameter_s=''): + """Lightweight persistence for python variables. + + Example:: + + In [1]: l = ['hello',10,'world'] + In [2]: %store l + Stored 'l' (list) + In [3]: exit + + (IPython session is closed and started again...) + + ville@badger:~$ ipython + In [1]: l + NameError: name 'l' is not defined + In [2]: %store -r + In [3]: l + Out[3]: ['hello', 10, 'world'] + + Usage: + + * ``%store`` - Show list of all variables and their current + values + * ``%store spam bar`` - Store the *current* value of the variables spam + and bar to disk + * ``%store -d spam`` - Remove the variable and its value from storage + * ``%store -z`` - Remove all variables from storage + * ``%store -r`` - Refresh all variables, aliases and directory history + from store (overwrite current vals) + * ``%store -r spam bar`` - Refresh specified variables and aliases from store + (delete current val) + * ``%store foo >a.txt`` - Store value of foo to new file a.txt + * ``%store foo >>a.txt`` - Append value of foo to file a.txt + + It should be noted that if you change the value of a variable, you + need to %store it again if you want to persist the new value. + + Note also that the variables will need to be pickleable; most basic + python types can be safely %store'd. + + Also aliases can be %store'd across sessions. + To remove an alias from the storage, use the %unalias magic. + """ + + opts,argsl = self.parse_options(parameter_s,'drz',mode='string') + args = argsl.split() + ip = self.shell + db = ip.db + # delete + if 'd' in opts: + try: + todel = args[0] + except IndexError as e: + raise UsageError('You must provide the variable to forget') from e + else: + try: + del db['autorestore/' + todel] + except BaseException as e: + raise UsageError("Can't delete variable '%s'" % todel) from e + # reset + elif 'z' in opts: + for k in db.keys('autorestore/*'): + del db[k] + + elif 'r' in opts: + if args: + for arg in args: + try: + obj = db["autorestore/" + arg] + except KeyError: + try: + restore_aliases(ip, alias=arg) + except KeyError: + print("no stored variable or alias %s" % arg) + else: + ip.user_ns[arg] = obj + else: + restore_data(ip) + + # run without arguments -> list variables & values + elif not args: + vars = db.keys('autorestore/*') + vars.sort() + if vars: + size = max(map(len, vars)) + else: + size = 0 + + print('Stored variables and their in-db values:') + fmt = '%-'+str(size)+'s -> %s' + get = db.get + for var in vars: + justkey = os.path.basename(var) + # print 30 first characters from every var + print(fmt % (justkey, repr(get(var, ''))[:50])) + + # default action - store the variable + else: + # %store foo >file.txt or >>file.txt + if len(args) > 1 and args[1].startswith(">"): + fnam = os.path.expanduser(args[1].lstrip(">").lstrip()) + if args[1].startswith(">>"): + fil = open(fnam, "a", encoding="utf-8") + else: + fil = open(fnam, "w", encoding="utf-8") + with fil: + obj = ip.ev(args[0]) + print("Writing '%s' (%s) to file '%s'." % (args[0], + obj.__class__.__name__, fnam)) + + if not isinstance (obj, str): + from pprint import pprint + pprint(obj, fil) + else: + fil.write(obj) + if not obj.endswith('\n'): + fil.write('\n') + + return + + # %store foo + for arg in args: + try: + obj = ip.user_ns[arg] + except KeyError: + # it might be an alias + name = arg + try: + cmd = ip.alias_manager.retrieve_alias(name) + except ValueError as e: + raise UsageError("Unknown variable '%s'" % name) from e + + staliases = db.get('stored_aliases',{}) + staliases[name] = cmd + db['stored_aliases'] = staliases + print("Alias stored: %s (%s)" % (name, cmd)) + return + + else: + modname = getattr(inspect.getmodule(obj), '__name__', '') + if modname == '__main__': + print(textwrap.dedent("""\ + Warning:%s is %s + Proper storage of interactively declared classes (or instances + of those classes) is not possible! Only instances + of classes in real modules on file system can be %%store'd. + """ % (arg, obj) )) + return + #pickled = pickle.dumps(obj) + db[ 'autorestore/' + arg ] = obj + print("Stored '%s' (%s)" % (arg, obj.__class__.__name__)) + + +def load_ipython_extension(ip): + """Load the extension in IPython.""" + ip.register_magics(StoreMagics) + diff --git a/myenv/lib/python3.12/site-packages/IPython/extensions/tests/__init__.py b/myenv/lib/python3.12/site-packages/IPython/extensions/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/myenv/lib/python3.12/site-packages/IPython/extensions/tests/test_autoreload.py b/myenv/lib/python3.12/site-packages/IPython/extensions/tests/test_autoreload.py new file mode 100644 index 00000000..47f3e2d6 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/extensions/tests/test_autoreload.py @@ -0,0 +1,712 @@ +"""Tests for autoreload extension. +""" + +# ----------------------------------------------------------------------------- +# Copyright (c) 2012 IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- + +import os +import platform +import pytest +import sys +import tempfile +import textwrap +import shutil +import random +import time +import traceback +from io import StringIO +from dataclasses import dataclass + +import IPython.testing.tools as tt + +from unittest import TestCase + +from IPython.extensions.autoreload import AutoreloadMagics +from IPython.core.events import EventManager, pre_run_cell +from IPython.testing.decorators import skipif_not_numpy +from IPython.core.interactiveshell import ExecutionInfo + +if platform.python_implementation() == "PyPy": + pytest.skip( + "Current autoreload implementation is extremely slow on PyPy", + allow_module_level=True, + ) + +# ----------------------------------------------------------------------------- +# Test fixture +# ----------------------------------------------------------------------------- + +noop = lambda *a, **kw: None + + +class FakeShell: + def __init__(self): + self.ns = {} + self.user_ns = self.ns + self.user_ns_hidden = {} + self.events = EventManager(self, {"pre_run_cell", pre_run_cell}) + self.auto_magics = AutoreloadMagics(shell=self) + self.events.register("pre_run_cell", self.auto_magics.pre_run_cell) + + register_magics = set_hook = noop + + def showtraceback( + self, + exc_tuple=None, + filename=None, + tb_offset=None, + exception_only=False, + running_compiled_code=False, + ): + traceback.print_exc() + + def run_code(self, code): + self.events.trigger( + "pre_run_cell", + ExecutionInfo( + raw_cell="", + store_history=False, + silent=False, + shell_futures=False, + cell_id=None, + ), + ) + exec(code, self.user_ns) + self.auto_magics.post_execute_hook() + + def push(self, items): + self.ns.update(items) + + def magic_autoreload(self, parameter): + self.auto_magics.autoreload(parameter) + + def magic_aimport(self, parameter, stream=None): + self.auto_magics.aimport(parameter, stream=stream) + self.auto_magics.post_execute_hook() + + +class Fixture(TestCase): + """Fixture for creating test module files""" + + test_dir = None + old_sys_path = None + filename_chars = "abcdefghijklmopqrstuvwxyz0123456789" + + def setUp(self): + self.test_dir = tempfile.mkdtemp() + self.old_sys_path = list(sys.path) + sys.path.insert(0, self.test_dir) + self.shell = FakeShell() + + def tearDown(self): + shutil.rmtree(self.test_dir) + sys.path = self.old_sys_path + + self.test_dir = None + self.old_sys_path = None + self.shell = None + + def get_module(self): + module_name = "tmpmod_" + "".join(random.sample(self.filename_chars, 20)) + if module_name in sys.modules: + del sys.modules[module_name] + file_name = os.path.join(self.test_dir, module_name + ".py") + return module_name, file_name + + def write_file(self, filename, content): + """ + Write a file, and force a timestamp difference of at least one second + + Notes + ----- + Python's .pyc files record the timestamp of their compilation + with a time resolution of one second. + + Therefore, we need to force a timestamp difference between .py + and .pyc, without having the .py file be timestamped in the + future, and without changing the timestamp of the .pyc file + (because that is stored in the file). The only reliable way + to achieve this seems to be to sleep. + """ + content = textwrap.dedent(content) + # Sleep one second + eps + time.sleep(1.05) + + # Write + with open(filename, "w", encoding="utf-8") as f: + f.write(content) + + def new_module(self, code): + code = textwrap.dedent(code) + mod_name, mod_fn = self.get_module() + with open(mod_fn, "w", encoding="utf-8") as f: + f.write(code) + return mod_name, mod_fn + + +# ----------------------------------------------------------------------------- +# Test automatic reloading +# ----------------------------------------------------------------------------- + + +def pickle_get_current_class(obj): + """ + Original issue comes from pickle; hence the name. + """ + name = obj.__class__.__name__ + module_name = getattr(obj, "__module__", None) + obj2 = sys.modules[module_name] + for subpath in name.split("."): + obj2 = getattr(obj2, subpath) + return obj2 + + +class TestAutoreload(Fixture): + def test_reload_enums(self): + mod_name, mod_fn = self.new_module( + textwrap.dedent( + """ + from enum import Enum + class MyEnum(Enum): + A = 'A' + B = 'B' + """ + ) + ) + self.shell.magic_autoreload("2") + self.shell.magic_aimport(mod_name) + self.write_file( + mod_fn, + textwrap.dedent( + """ + from enum import Enum + class MyEnum(Enum): + A = 'A' + B = 'B' + C = 'C' + """ + ), + ) + with tt.AssertNotPrints( + ("[autoreload of %s failed:" % mod_name), channel="stderr" + ): + self.shell.run_code("pass") # trigger another reload + + def test_reload_class_type(self): + self.shell.magic_autoreload("2") + mod_name, mod_fn = self.new_module( + """ + class Test(): + def meth(self): + return "old" + """ + ) + assert "test" not in self.shell.ns + assert "result" not in self.shell.ns + + self.shell.run_code("from %s import Test" % mod_name) + self.shell.run_code("test = Test()") + + self.write_file( + mod_fn, + """ + class Test(): + def meth(self): + return "new" + """, + ) + + test_object = self.shell.ns["test"] + + # important to trigger autoreload logic ! + self.shell.run_code("pass") + + test_class = pickle_get_current_class(test_object) + assert isinstance(test_object, test_class) + + # extra check. + self.shell.run_code("import pickle") + self.shell.run_code("p = pickle.dumps(test)") + + def test_reload_class_attributes(self): + self.shell.magic_autoreload("2") + mod_name, mod_fn = self.new_module( + textwrap.dedent( + """ + class MyClass: + + def __init__(self, a=10): + self.a = a + self.b = 22 + # self.toto = 33 + + def square(self): + print('compute square') + return self.a*self.a + """ + ) + ) + self.shell.run_code("from %s import MyClass" % mod_name) + self.shell.run_code("first = MyClass(5)") + self.shell.run_code("first.square()") + with self.assertRaises(AttributeError): + self.shell.run_code("first.cube()") + with self.assertRaises(AttributeError): + self.shell.run_code("first.power(5)") + self.shell.run_code("first.b") + with self.assertRaises(AttributeError): + self.shell.run_code("first.toto") + + # remove square, add power + + self.write_file( + mod_fn, + textwrap.dedent( + """ + class MyClass: + + def __init__(self, a=10): + self.a = a + self.b = 11 + + def power(self, p): + print('compute power '+str(p)) + return self.a**p + """ + ), + ) + + self.shell.run_code("second = MyClass(5)") + + for object_name in {"first", "second"}: + self.shell.run_code(f"{object_name}.power(5)") + with self.assertRaises(AttributeError): + self.shell.run_code(f"{object_name}.cube()") + with self.assertRaises(AttributeError): + self.shell.run_code(f"{object_name}.square()") + self.shell.run_code(f"{object_name}.b") + self.shell.run_code(f"{object_name}.a") + with self.assertRaises(AttributeError): + self.shell.run_code(f"{object_name}.toto") + + @skipif_not_numpy + def test_comparing_numpy_structures(self): + self.shell.magic_autoreload("2") + self.shell.run_code("1+1") + mod_name, mod_fn = self.new_module( + textwrap.dedent( + """ + import numpy as np + class MyClass: + a = (np.array((.1, .2)), + np.array((.2, .3))) + """ + ) + ) + self.shell.run_code("from %s import MyClass" % mod_name) + self.shell.run_code("first = MyClass()") + + # change property `a` + self.write_file( + mod_fn, + textwrap.dedent( + """ + import numpy as np + class MyClass: + a = (np.array((.3, .4)), + np.array((.5, .6))) + """ + ), + ) + + with tt.AssertNotPrints( + ("[autoreload of %s failed:" % mod_name), channel="stderr" + ): + self.shell.run_code("pass") # trigger another reload + + def test_autoload_newly_added_objects(self): + # All of these fail with %autoreload 2 + self.shell.magic_autoreload("3") + mod_code = """ + def func1(): pass + """ + mod_name, mod_fn = self.new_module(textwrap.dedent(mod_code)) + self.shell.run_code(f"from {mod_name} import *") + self.shell.run_code("func1()") + with self.assertRaises(NameError): + self.shell.run_code("func2()") + with self.assertRaises(NameError): + self.shell.run_code("t = Test()") + with self.assertRaises(NameError): + self.shell.run_code("number") + + # ----------- TEST NEW OBJ LOADED -------------------------- + + new_code = """ + def func1(): pass + def func2(): pass + class Test: pass + number = 0 + from enum import Enum + class TestEnum(Enum): + A = 'a' + """ + self.write_file(mod_fn, textwrap.dedent(new_code)) + + # test function now exists in shell's namespace namespace + self.shell.run_code("func2()") + # test function now exists in module's dict + self.shell.run_code(f"import sys; sys.modules['{mod_name}'].func2()") + # test class now exists + self.shell.run_code("t = Test()") + # test global built-in var now exists + self.shell.run_code("number") + # test the enumerations gets loaded successfully + self.shell.run_code("TestEnum.A") + + # ----------- TEST NEW OBJ CAN BE CHANGED -------------------- + + new_code = """ + def func1(): return 'changed' + def func2(): return 'changed' + class Test: + def new_func(self): + return 'changed' + number = 1 + from enum import Enum + class TestEnum(Enum): + A = 'a' + B = 'added' + """ + self.write_file(mod_fn, textwrap.dedent(new_code)) + self.shell.run_code("assert func1() == 'changed'") + self.shell.run_code("assert func2() == 'changed'") + self.shell.run_code("t = Test(); assert t.new_func() == 'changed'") + self.shell.run_code("assert number == 1") + if sys.version_info < (3, 12): + self.shell.run_code("assert TestEnum.B.value == 'added'") + + # ----------- TEST IMPORT FROM MODULE -------------------------- + + new_mod_code = """ + from enum import Enum + class Ext(Enum): + A = 'ext' + def ext_func(): + return 'ext' + class ExtTest: + def meth(self): + return 'ext' + ext_int = 2 + """ + new_mod_name, new_mod_fn = self.new_module(textwrap.dedent(new_mod_code)) + current_mod_code = f""" + from {new_mod_name} import * + """ + self.write_file(mod_fn, textwrap.dedent(current_mod_code)) + self.shell.run_code("assert Ext.A.value == 'ext'") + self.shell.run_code("assert ext_func() == 'ext'") + self.shell.run_code("t = ExtTest(); assert t.meth() == 'ext'") + self.shell.run_code("assert ext_int == 2") + + def test_verbose_names(self): + # Asserts correspondense between original mode names and their verbose equivalents. + @dataclass + class AutoreloadSettings: + check_all: bool + enabled: bool + autoload_obj: bool + + def gather_settings(mode): + self.shell.magic_autoreload(mode) + module_reloader = self.shell.auto_magics._reloader + return AutoreloadSettings( + module_reloader.check_all, + module_reloader.enabled, + module_reloader.autoload_obj, + ) + + assert gather_settings("0") == gather_settings("off") + assert gather_settings("0") == gather_settings("OFF") # Case insensitive + assert gather_settings("1") == gather_settings("explicit") + assert gather_settings("2") == gather_settings("all") + assert gather_settings("3") == gather_settings("complete") + + # And an invalid mode name raises an exception. + with self.assertRaises(ValueError): + self.shell.magic_autoreload("4") + + def test_aimport_parsing(self): + # Modules can be included or excluded all in one line. + module_reloader = self.shell.auto_magics._reloader + self.shell.magic_aimport("os") # import and mark `os` for auto-reload. + assert module_reloader.modules["os"] is True + assert "os" not in module_reloader.skip_modules.keys() + + self.shell.magic_aimport("-math") # forbid autoreloading of `math` + assert module_reloader.skip_modules["math"] is True + assert "math" not in module_reloader.modules.keys() + + self.shell.magic_aimport( + "-os, math" + ) # Can do this all in one line; wasn't possible before. + assert module_reloader.modules["math"] is True + assert "math" not in module_reloader.skip_modules.keys() + assert module_reloader.skip_modules["os"] is True + assert "os" not in module_reloader.modules.keys() + + def test_autoreload_output(self): + self.shell.magic_autoreload("complete") + mod_code = """ + def func1(): pass + """ + mod_name, mod_fn = self.new_module(mod_code) + self.shell.run_code(f"import {mod_name}") + with tt.AssertPrints("", channel="stdout"): # no output; this is default + self.shell.run_code("pass") + + self.shell.magic_autoreload("complete --print") + self.write_file(mod_fn, mod_code) # "modify" the module + with tt.AssertPrints( + f"Reloading '{mod_name}'.", channel="stdout" + ): # see something printed out + self.shell.run_code("pass") + + self.shell.magic_autoreload("complete -p") + self.write_file(mod_fn, mod_code) # "modify" the module + with tt.AssertPrints( + f"Reloading '{mod_name}'.", channel="stdout" + ): # see something printed out + self.shell.run_code("pass") + + self.shell.magic_autoreload("complete --print --log") + self.write_file(mod_fn, mod_code) # "modify" the module + with tt.AssertPrints( + f"Reloading '{mod_name}'.", channel="stdout" + ): # see something printed out + self.shell.run_code("pass") + + self.shell.magic_autoreload("complete --print --log") + self.write_file(mod_fn, mod_code) # "modify" the module + with self.assertLogs(logger="autoreload") as lo: # see something printed out + self.shell.run_code("pass") + assert lo.output == [f"INFO:autoreload:Reloading '{mod_name}'."] + + self.shell.magic_autoreload("complete -l") + self.write_file(mod_fn, mod_code) # "modify" the module + with self.assertLogs(logger="autoreload") as lo: # see something printed out + self.shell.run_code("pass") + assert lo.output == [f"INFO:autoreload:Reloading '{mod_name}'."] + + def _check_smoketest(self, use_aimport=True): + """ + Functional test for the automatic reloader using either + '%autoreload 1' or '%autoreload 2' + """ + + mod_name, mod_fn = self.new_module( + """ +x = 9 + +z = 123 # this item will be deleted + +def foo(y): + return y + 3 + +class Baz(object): + def __init__(self, x): + self.x = x + def bar(self, y): + return self.x + y + @property + def quux(self): + return 42 + def zzz(self): + '''This method will be deleted below''' + return 99 + +class Bar: # old-style class: weakref doesn't work for it on Python < 2.7 + def foo(self): + return 1 +""" + ) + + # + # Import module, and mark for reloading + # + if use_aimport: + self.shell.magic_autoreload("1") + self.shell.magic_aimport(mod_name) + stream = StringIO() + self.shell.magic_aimport("", stream=stream) + self.assertIn(("Modules to reload:\n%s" % mod_name), stream.getvalue()) + + with self.assertRaises(ImportError): + self.shell.magic_aimport("tmpmod_as318989e89ds") + else: + self.shell.magic_autoreload("2") + self.shell.run_code("import %s" % mod_name) + stream = StringIO() + self.shell.magic_aimport("", stream=stream) + self.assertTrue( + "Modules to reload:\nall-except-skipped" in stream.getvalue() + ) + self.assertIn(mod_name, self.shell.ns) + + mod = sys.modules[mod_name] + + # + # Test module contents + # + old_foo = mod.foo + old_obj = mod.Baz(9) + old_obj2 = mod.Bar() + + def check_module_contents(): + self.assertEqual(mod.x, 9) + self.assertEqual(mod.z, 123) + + self.assertEqual(old_foo(0), 3) + self.assertEqual(mod.foo(0), 3) + + obj = mod.Baz(9) + self.assertEqual(old_obj.bar(1), 10) + self.assertEqual(obj.bar(1), 10) + self.assertEqual(obj.quux, 42) + self.assertEqual(obj.zzz(), 99) + + obj2 = mod.Bar() + self.assertEqual(old_obj2.foo(), 1) + self.assertEqual(obj2.foo(), 1) + + check_module_contents() + + # + # Simulate a failed reload: no reload should occur and exactly + # one error message should be printed + # + self.write_file( + mod_fn, + """ +a syntax error +""", + ) + + with tt.AssertPrints( + ("[autoreload of %s failed:" % mod_name), channel="stderr" + ): + self.shell.run_code("pass") # trigger reload + with tt.AssertNotPrints( + ("[autoreload of %s failed:" % mod_name), channel="stderr" + ): + self.shell.run_code("pass") # trigger another reload + check_module_contents() + + # + # Rewrite module (this time reload should succeed) + # + self.write_file( + mod_fn, + """ +x = 10 + +def foo(y): + return y + 4 + +class Baz(object): + def __init__(self, x): + self.x = x + def bar(self, y): + return self.x + y + 1 + @property + def quux(self): + return 43 + +class Bar: # old-style class + def foo(self): + return 2 +""", + ) + + def check_module_contents(): + self.assertEqual(mod.x, 10) + self.assertFalse(hasattr(mod, "z")) + + self.assertEqual(old_foo(0), 4) # superreload magic! + self.assertEqual(mod.foo(0), 4) + + obj = mod.Baz(9) + self.assertEqual(old_obj.bar(1), 11) # superreload magic! + self.assertEqual(obj.bar(1), 11) + + self.assertEqual(old_obj.quux, 43) + self.assertEqual(obj.quux, 43) + + self.assertFalse(hasattr(old_obj, "zzz")) + self.assertFalse(hasattr(obj, "zzz")) + + obj2 = mod.Bar() + self.assertEqual(old_obj2.foo(), 2) + self.assertEqual(obj2.foo(), 2) + + self.shell.run_code("pass") # trigger reload + check_module_contents() + + # + # Another failure case: deleted file (shouldn't reload) + # + os.unlink(mod_fn) + + self.shell.run_code("pass") # trigger reload + check_module_contents() + + # + # Disable autoreload and rewrite module: no reload should occur + # + if use_aimport: + self.shell.magic_aimport("-" + mod_name) + stream = StringIO() + self.shell.magic_aimport("", stream=stream) + self.assertTrue(("Modules to skip:\n%s" % mod_name) in stream.getvalue()) + + # This should succeed, although no such module exists + self.shell.magic_aimport("-tmpmod_as318989e89ds") + else: + self.shell.magic_autoreload("0") + + self.write_file( + mod_fn, + """ +x = -99 +""", + ) + + self.shell.run_code("pass") # trigger reload + self.shell.run_code("pass") + check_module_contents() + + # + # Re-enable autoreload: reload should now occur + # + if use_aimport: + self.shell.magic_aimport(mod_name) + else: + self.shell.magic_autoreload("") + + self.shell.run_code("pass") # trigger reload + self.assertEqual(mod.x, -99) + + def test_smoketest_aimport(self): + self._check_smoketest(use_aimport=True) + + def test_smoketest_autoreload(self): + self._check_smoketest(use_aimport=False) diff --git a/myenv/lib/python3.12/site-packages/IPython/extensions/tests/test_storemagic.py b/myenv/lib/python3.12/site-packages/IPython/extensions/tests/test_storemagic.py new file mode 100644 index 00000000..66122abf --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/extensions/tests/test_storemagic.py @@ -0,0 +1,71 @@ +import tempfile, os +from pathlib import Path + +from traitlets.config.loader import Config + + +def setup_module(): + ip.run_line_magic("load_ext", "storemagic") + + +def test_store_restore(): + assert "bar" not in ip.user_ns, "Error: some other test leaked `bar` in user_ns" + assert "foo" not in ip.user_ns, "Error: some other test leaked `foo` in user_ns" + assert ( + "foobar" not in ip.user_ns + ), "Error: some other test leaked `foobar` in user_ns" + assert ( + "foobaz" not in ip.user_ns + ), "Error: some other test leaked `foobaz` in user_ns" + ip.user_ns["foo"] = 78 + ip.run_line_magic("alias", 'bar echo "hello"') + ip.user_ns["foobar"] = 79 + ip.user_ns["foobaz"] = "80" + tmpd = tempfile.mkdtemp() + ip.run_line_magic("cd", tmpd) + ip.run_line_magic("store", "foo") + ip.run_line_magic("store", "bar") + ip.run_line_magic("store", "foobar foobaz") + + # Check storing + assert ip.db["autorestore/foo"] == 78 + assert "bar" in ip.db["stored_aliases"] + assert ip.db["autorestore/foobar"] == 79 + assert ip.db["autorestore/foobaz"] == "80" + + # Remove those items + ip.user_ns.pop("foo", None) + ip.user_ns.pop("foobar", None) + ip.user_ns.pop("foobaz", None) + ip.alias_manager.undefine_alias("bar") + ip.run_line_magic("cd", "-") + ip.user_ns["_dh"][:] = [] + + # Check restoring + ip.run_line_magic("store", "-r foo bar foobar foobaz") + assert ip.user_ns["foo"] == 78 + assert ip.alias_manager.is_alias("bar") + assert ip.user_ns["foobar"] == 79 + assert ip.user_ns["foobaz"] == "80" + + ip.run_line_magic("store", "-r") # restores _dh too + assert any(Path(tmpd).samefile(p) for p in ip.user_ns["_dh"]) + + os.rmdir(tmpd) + +def test_autorestore(): + ip.user_ns["foo"] = 95 + ip.run_line_magic("store", "foo") + del ip.user_ns["foo"] + c = Config() + c.StoreMagics.autorestore = False + orig_config = ip.config + try: + ip.config = c + ip.extension_manager.reload_extension("storemagic") + assert "foo" not in ip.user_ns + c.StoreMagics.autorestore = True + ip.extension_manager.reload_extension("storemagic") + assert ip.user_ns["foo"] == 95 + finally: + ip.config = orig_config diff --git a/myenv/lib/python3.12/site-packages/IPython/external/__init__.py b/myenv/lib/python3.12/site-packages/IPython/external/__init__.py new file mode 100644 index 00000000..eedc338e --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/external/__init__.py @@ -0,0 +1,7 @@ +""" +This package contains all third-party modules bundled with IPython. +""" + +from typing import List + +__all__: List[str] = [] diff --git a/myenv/lib/python3.12/site-packages/IPython/external/qt_for_kernel.py b/myenv/lib/python3.12/site-packages/IPython/external/qt_for_kernel.py new file mode 100644 index 00000000..11e88625 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/external/qt_for_kernel.py @@ -0,0 +1,124 @@ +""" Import Qt in a manner suitable for an IPython kernel. + +This is the import used for the `gui=qt` or `matplotlib=qt` initialization. + +Import Priority: + +if Qt has been imported anywhere else: + use that + +if matplotlib has been imported and doesn't support v2 (<= 1.0.1): + use PyQt4 @v1 + +Next, ask QT_API env variable + +if QT_API not set: + ask matplotlib what it's using. If Qt4Agg or Qt5Agg, then use the + version matplotlib is configured with + + else: (matplotlib said nothing) + # this is the default path - nobody told us anything + try in this order: + PyQt default version, PySide, PyQt5 +else: + use what QT_API says + + Note that %gui's implementation will always set a `QT_API`, see + `IPython.terminal.pt_inputhooks.get_inputhook_name_and_func` + +""" +# NOTE: This is no longer an external, third-party module, and should be +# considered part of IPython. For compatibility however, it is being kept in +# IPython/external. + +import os +import sys + +from IPython.external.qt_loaders import ( + load_qt, + loaded_api, + enum_factory, + # QT6 + QT_API_PYQT6, + QT_API_PYSIDE6, + # QT5 + QT_API_PYQT5, + QT_API_PYSIDE2, + # QT4 + QT_API_PYQT, + QT_API_PYSIDE, + # default + QT_API_PYQT_DEFAULT, +) + +_qt_apis = ( + # QT6 + QT_API_PYQT6, + QT_API_PYSIDE6, + # QT5 + QT_API_PYQT5, + QT_API_PYSIDE2, + # default + QT_API_PYQT_DEFAULT, +) + + +def matplotlib_options(mpl): + """Constraints placed on an imported matplotlib.""" + if mpl is None: + return + backend = mpl.rcParams.get('backend', None) + if backend == 'Qt4Agg': + mpqt = mpl.rcParams.get('backend.qt4', None) + if mpqt is None: + return None + if mpqt.lower() == 'pyside': + return [QT_API_PYSIDE] + elif mpqt.lower() == 'pyqt4': + return [QT_API_PYQT_DEFAULT] + elif mpqt.lower() == 'pyqt4v2': + return [QT_API_PYQT] + raise ImportError("unhandled value for backend.qt4 from matplotlib: %r" % + mpqt) + elif backend == 'Qt5Agg': + mpqt = mpl.rcParams.get('backend.qt5', None) + if mpqt is None: + return None + if mpqt.lower() == 'pyqt5': + return [QT_API_PYQT5] + raise ImportError("unhandled value for backend.qt5 from matplotlib: %r" % + mpqt) + +def get_options(): + """Return a list of acceptable QT APIs, in decreasing order of preference.""" + #already imported Qt somewhere. Use that + loaded = loaded_api() + if loaded is not None: + return [loaded] + + mpl = sys.modules.get("matplotlib", None) + + if mpl is not None and tuple(mpl.__version__.split(".")) < ("1", "0", "2"): + # 1.0.1 only supports PyQt4 v1 + return [QT_API_PYQT_DEFAULT] + + qt_api = os.environ.get('QT_API', None) + if qt_api is None: + #no ETS variable. Ask mpl, then use default fallback path + return matplotlib_options(mpl) or [ + QT_API_PYQT_DEFAULT, + QT_API_PYQT6, + QT_API_PYSIDE6, + QT_API_PYQT5, + QT_API_PYSIDE2, + ] + elif qt_api not in _qt_apis: + raise RuntimeError("Invalid Qt API %r, valid values are: %r" % + (qt_api, ', '.join(_qt_apis))) + else: + return [qt_api] + + +api_opts = get_options() +QtCore, QtGui, QtSvg, QT_API = load_qt(api_opts) +enum_helper = enum_factory(QT_API, QtCore) diff --git a/myenv/lib/python3.12/site-packages/IPython/external/qt_loaders.py b/myenv/lib/python3.12/site-packages/IPython/external/qt_loaders.py new file mode 100644 index 00000000..87b71658 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/external/qt_loaders.py @@ -0,0 +1,423 @@ +""" +This module contains factory functions that attempt +to return Qt submodules from the various python Qt bindings. + +It also protects against double-importing Qt with different +bindings, which is unstable and likely to crash + +This is used primarily by qt and qt_for_kernel, and shouldn't +be accessed directly from the outside +""" + +import importlib.abc +import sys +import os +import types +from functools import partial, lru_cache +import operator + +# ### Available APIs. +# Qt6 +QT_API_PYQT6 = "pyqt6" +QT_API_PYSIDE6 = "pyside6" + +# Qt5 +QT_API_PYQT5 = 'pyqt5' +QT_API_PYSIDE2 = 'pyside2' + +# Qt4 +# NOTE: Here for legacy matplotlib compatibility, but not really supported on the IPython side. +QT_API_PYQT = "pyqt" # Force version 2 +QT_API_PYQTv1 = "pyqtv1" # Force version 2 +QT_API_PYSIDE = "pyside" + +QT_API_PYQT_DEFAULT = "pyqtdefault" # use system default for version 1 vs. 2 + +api_to_module = { + # Qt6 + QT_API_PYQT6: "PyQt6", + QT_API_PYSIDE6: "PySide6", + # Qt5 + QT_API_PYQT5: "PyQt5", + QT_API_PYSIDE2: "PySide2", + # Qt4 + QT_API_PYSIDE: "PySide", + QT_API_PYQT: "PyQt4", + QT_API_PYQTv1: "PyQt4", + # default + QT_API_PYQT_DEFAULT: "PyQt6", +} + + +class ImportDenier(importlib.abc.MetaPathFinder): + """Import Hook that will guard against bad Qt imports + once IPython commits to a specific binding + """ + + def __init__(self): + self.__forbidden = set() + + def forbid(self, module_name): + sys.modules.pop(module_name, None) + self.__forbidden.add(module_name) + + def find_spec(self, fullname, path, target=None): + if path: + return + if fullname in self.__forbidden: + raise ImportError( + """ + Importing %s disabled by IPython, which has + already imported an Incompatible QT Binding: %s + """ + % (fullname, loaded_api()) + ) + + +ID = ImportDenier() +sys.meta_path.insert(0, ID) + + +def commit_api(api): + """Commit to a particular API, and trigger ImportErrors on subsequent + dangerous imports""" + modules = set(api_to_module.values()) + + modules.remove(api_to_module[api]) + for mod in modules: + ID.forbid(mod) + + +def loaded_api(): + """Return which API is loaded, if any + + If this returns anything besides None, + importing any other Qt binding is unsafe. + + Returns + ------- + None, 'pyside6', 'pyqt6', 'pyside2', 'pyside', 'pyqt', 'pyqt5', 'pyqtv1' + """ + if sys.modules.get("PyQt6.QtCore"): + return QT_API_PYQT6 + elif sys.modules.get("PySide6.QtCore"): + return QT_API_PYSIDE6 + elif sys.modules.get("PyQt5.QtCore"): + return QT_API_PYQT5 + elif sys.modules.get("PySide2.QtCore"): + return QT_API_PYSIDE2 + elif sys.modules.get("PyQt4.QtCore"): + if qtapi_version() == 2: + return QT_API_PYQT + else: + return QT_API_PYQTv1 + elif sys.modules.get("PySide.QtCore"): + return QT_API_PYSIDE + + return None + + +def has_binding(api): + """Safely check for PyQt4/5, PySide or PySide2, without importing submodules + + Parameters + ---------- + api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault'] + Which module to check for + + Returns + ------- + True if the relevant module appears to be importable + """ + module_name = api_to_module[api] + from importlib.util import find_spec + + required = ['QtCore', 'QtGui', 'QtSvg'] + if api in (QT_API_PYQT5, QT_API_PYSIDE2, QT_API_PYQT6, QT_API_PYSIDE6): + # QT5 requires QtWidgets too + required.append('QtWidgets') + + for submod in required: + try: + spec = find_spec('%s.%s' % (module_name, submod)) + except ImportError: + # Package (e.g. PyQt5) not found + return False + else: + if spec is None: + # Submodule (e.g. PyQt5.QtCore) not found + return False + + if api == QT_API_PYSIDE: + # We can also safely check PySide version + import PySide + + return PySide.__version_info__ >= (1, 0, 3) + + return True + + +def qtapi_version(): + """Return which QString API has been set, if any + + Returns + ------- + The QString API version (1 or 2), or None if not set + """ + try: + import sip + except ImportError: + # as of PyQt5 5.11, sip is no longer available as a top-level + # module and needs to be imported from the PyQt5 namespace + try: + from PyQt5 import sip + except ImportError: + return + try: + return sip.getapi('QString') + except ValueError: + return + + +def can_import(api): + """Safely query whether an API is importable, without importing it""" + if not has_binding(api): + return False + + current = loaded_api() + if api == QT_API_PYQT_DEFAULT: + return current in [QT_API_PYQT6, None] + else: + return current in [api, None] + + +def import_pyqt4(version=2): + """ + Import PyQt4 + + Parameters + ---------- + version : 1, 2, or None + Which QString/QVariant API to use. Set to None to use the system + default + ImportErrors raised within this function are non-recoverable + """ + # The new-style string API (version=2) automatically + # converts QStrings to Unicode Python strings. Also, automatically unpacks + # QVariants to their underlying objects. + import sip + + if version is not None: + sip.setapi('QString', version) + sip.setapi('QVariant', version) + + from PyQt4 import QtGui, QtCore, QtSvg + + if QtCore.PYQT_VERSION < 0x040700: + raise ImportError("IPython requires PyQt4 >= 4.7, found %s" % + QtCore.PYQT_VERSION_STR) + + # Alias PyQt-specific functions for PySide compatibility. + QtCore.Signal = QtCore.pyqtSignal + QtCore.Slot = QtCore.pyqtSlot + + # query for the API version (in case version == None) + version = sip.getapi('QString') + api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT + return QtCore, QtGui, QtSvg, api + + +def import_pyqt5(): + """ + Import PyQt5 + + ImportErrors raised within this function are non-recoverable + """ + + from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui + + # Alias PyQt-specific functions for PySide compatibility. + QtCore.Signal = QtCore.pyqtSignal + QtCore.Slot = QtCore.pyqtSlot + + # Join QtGui and QtWidgets for Qt4 compatibility. + QtGuiCompat = types.ModuleType('QtGuiCompat') + QtGuiCompat.__dict__.update(QtGui.__dict__) + QtGuiCompat.__dict__.update(QtWidgets.__dict__) + + api = QT_API_PYQT5 + return QtCore, QtGuiCompat, QtSvg, api + + +def import_pyqt6(): + """ + Import PyQt6 + + ImportErrors raised within this function are non-recoverable + """ + + from PyQt6 import QtCore, QtSvg, QtWidgets, QtGui + + # Alias PyQt-specific functions for PySide compatibility. + QtCore.Signal = QtCore.pyqtSignal + QtCore.Slot = QtCore.pyqtSlot + + # Join QtGui and QtWidgets for Qt4 compatibility. + QtGuiCompat = types.ModuleType("QtGuiCompat") + QtGuiCompat.__dict__.update(QtGui.__dict__) + QtGuiCompat.__dict__.update(QtWidgets.__dict__) + + api = QT_API_PYQT6 + return QtCore, QtGuiCompat, QtSvg, api + + +def import_pyside(): + """ + Import PySide + + ImportErrors raised within this function are non-recoverable + """ + from PySide import QtGui, QtCore, QtSvg + return QtCore, QtGui, QtSvg, QT_API_PYSIDE + +def import_pyside2(): + """ + Import PySide2 + + ImportErrors raised within this function are non-recoverable + """ + from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport + + # Join QtGui and QtWidgets for Qt4 compatibility. + QtGuiCompat = types.ModuleType('QtGuiCompat') + QtGuiCompat.__dict__.update(QtGui.__dict__) + QtGuiCompat.__dict__.update(QtWidgets.__dict__) + QtGuiCompat.__dict__.update(QtPrintSupport.__dict__) + + return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2 + + +def import_pyside6(): + """ + Import PySide6 + + ImportErrors raised within this function are non-recoverable + """ + + def get_attrs(module): + return { + name: getattr(module, name) + for name in dir(module) + if not name.startswith("_") + } + + from PySide6 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport + + # Join QtGui and QtWidgets for Qt4 compatibility. + QtGuiCompat = types.ModuleType("QtGuiCompat") + QtGuiCompat.__dict__.update(QtGui.__dict__) + if QtCore.__version_info__ < (6, 7): + QtGuiCompat.__dict__.update(QtWidgets.__dict__) + QtGuiCompat.__dict__.update(QtPrintSupport.__dict__) + else: + QtGuiCompat.__dict__.update(get_attrs(QtWidgets)) + QtGuiCompat.__dict__.update(get_attrs(QtPrintSupport)) + + return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE6 + + +def load_qt(api_options): + """ + Attempt to import Qt, given a preference list + of permissible bindings + + It is safe to call this function multiple times. + + Parameters + ---------- + api_options : List of strings + The order of APIs to try. Valid items are 'pyside', 'pyside2', + 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault' + + Returns + ------- + A tuple of QtCore, QtGui, QtSvg, QT_API + The first three are the Qt modules. The last is the + string indicating which module was loaded. + + Raises + ------ + ImportError, if it isn't possible to import any requested + bindings (either because they aren't installed, or because + an incompatible library has already been installed) + """ + loaders = { + # Qt6 + QT_API_PYQT6: import_pyqt6, + QT_API_PYSIDE6: import_pyside6, + # Qt5 + QT_API_PYQT5: import_pyqt5, + QT_API_PYSIDE2: import_pyside2, + # Qt4 + QT_API_PYSIDE: import_pyside, + QT_API_PYQT: import_pyqt4, + QT_API_PYQTv1: partial(import_pyqt4, version=1), + # default + QT_API_PYQT_DEFAULT: import_pyqt6, + } + + for api in api_options: + + if api not in loaders: + raise RuntimeError( + "Invalid Qt API %r, valid values are: %s" % + (api, ", ".join(["%r" % k for k in loaders.keys()]))) + + if not can_import(api): + continue + + #cannot safely recover from an ImportError during this + result = loaders[api]() + api = result[-1] # changed if api = QT_API_PYQT_DEFAULT + commit_api(api) + return result + else: + # Clear the environment variable since it doesn't work. + if "QT_API" in os.environ: + del os.environ["QT_API"] + + raise ImportError( + """ + Could not load requested Qt binding. Please ensure that + PyQt4 >= 4.7, PyQt5, PyQt6, PySide >= 1.0.3, PySide2, or + PySide6 is available, and only one is imported per session. + + Currently-imported Qt library: %r + PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s + PyQt6 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s + PySide2 installed: %s + PySide6 installed: %s + Tried to load: %r + """ + % ( + loaded_api(), + has_binding(QT_API_PYQT5), + has_binding(QT_API_PYQT6), + has_binding(QT_API_PYSIDE2), + has_binding(QT_API_PYSIDE6), + api_options, + ) + ) + + +def enum_factory(QT_API, QtCore): + """Construct an enum helper to account for PyQt5 <-> PyQt6 changes.""" + + @lru_cache(None) + def _enum(name): + # foo.bar.Enum.Entry (PyQt6) <=> foo.bar.Entry (non-PyQt6). + return operator.attrgetter( + name if QT_API == QT_API_PYQT6 else name.rpartition(".")[0] + )(sys.modules[QtCore.__package__]) + + return _enum diff --git a/myenv/lib/python3.12/site-packages/IPython/external/tests/__init__.py b/myenv/lib/python3.12/site-packages/IPython/external/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/myenv/lib/python3.12/site-packages/IPython/external/tests/test_qt_loaders.py b/myenv/lib/python3.12/site-packages/IPython/external/tests/test_qt_loaders.py new file mode 100644 index 00000000..7bc9ccfa --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/external/tests/test_qt_loaders.py @@ -0,0 +1,11 @@ +import importlib +import pytest +from IPython.external.qt_loaders import ID + + +def test_import_denier(): + ID.forbid("ipython_denied_module") + with pytest.raises(ImportError, match="disabled by IPython"): + import ipython_denied_module + with pytest.raises(ImportError, match="disabled by IPython"): + importlib.import_module("ipython_denied_module") diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/__init__.py b/myenv/lib/python3.12/site-packages/IPython/lib/__init__.py new file mode 100644 index 00000000..cf8ccb04 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/__init__.py @@ -0,0 +1,11 @@ +# encoding: utf-8 +""" +Extra capabilities for IPython +""" + +# ----------------------------------------------------------------------------- +# Copyright (C) 2008-2011 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +# ----------------------------------------------------------------------------- diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/backgroundjobs.py b/myenv/lib/python3.12/site-packages/IPython/lib/backgroundjobs.py new file mode 100644 index 00000000..e7ad51eb --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/backgroundjobs.py @@ -0,0 +1,491 @@ +# -*- coding: utf-8 -*- +"""Manage background (threaded) jobs conveniently from an interactive shell. + +This module provides a BackgroundJobManager class. This is the main class +meant for public usage, it implements an object which can create and manage +new background jobs. + +It also provides the actual job classes managed by these BackgroundJobManager +objects, see their docstrings below. + + +This system was inspired by discussions with B. Granger and the +BackgroundCommand class described in the book Python Scripting for +Computational Science, by H. P. Langtangen: + +http://folk.uio.no/hpl/scripting + +(although ultimately no code from this text was used, as IPython's system is a +separate implementation). + +An example notebook is provided in our documentation illustrating interactive +use of the system. +""" + +#***************************************************************************** +# Copyright (C) 2005-2006 Fernando Perez +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#***************************************************************************** + +# Code begins +import sys +import threading + +from IPython import get_ipython +from IPython.core.ultratb import AutoFormattedTB +from logging import error, debug + + +class BackgroundJobManager(object): + """Class to manage a pool of backgrounded threaded jobs. + + Below, we assume that 'jobs' is a BackgroundJobManager instance. + + Usage summary (see the method docstrings for details): + + jobs.new(...) -> start a new job + + jobs() or jobs.status() -> print status summary of all jobs + + jobs[N] -> returns job number N. + + foo = jobs[N].result -> assign to variable foo the result of job N + + jobs[N].traceback() -> print the traceback of dead job N + + jobs.remove(N) -> remove (finished) job N + + jobs.flush() -> remove all finished jobs + + As a convenience feature, BackgroundJobManager instances provide the + utility result and traceback methods which retrieve the corresponding + information from the jobs list: + + jobs.result(N) <--> jobs[N].result + jobs.traceback(N) <--> jobs[N].traceback() + + While this appears minor, it allows you to use tab completion + interactively on the job manager instance. + """ + + def __init__(self): + # Lists for job management, accessed via a property to ensure they're + # up to date.x + self._running = [] + self._completed = [] + self._dead = [] + # A dict of all jobs, so users can easily access any of them + self.all = {} + # For reporting + self._comp_report = [] + self._dead_report = [] + # Store status codes locally for fast lookups + self._s_created = BackgroundJobBase.stat_created_c + self._s_running = BackgroundJobBase.stat_running_c + self._s_completed = BackgroundJobBase.stat_completed_c + self._s_dead = BackgroundJobBase.stat_dead_c + self._current_job_id = 0 + + @property + def running(self): + self._update_status() + return self._running + + @property + def dead(self): + self._update_status() + return self._dead + + @property + def completed(self): + self._update_status() + return self._completed + + def new(self, func_or_exp, *args, **kwargs): + """Add a new background job and start it in a separate thread. + + There are two types of jobs which can be created: + + 1. Jobs based on expressions which can be passed to an eval() call. + The expression must be given as a string. For example: + + job_manager.new('myfunc(x,y,z=1)'[,glob[,loc]]) + + The given expression is passed to eval(), along with the optional + global/local dicts provided. If no dicts are given, they are + extracted automatically from the caller's frame. + + A Python statement is NOT a valid eval() expression. Basically, you + can only use as an eval() argument something which can go on the right + of an '=' sign and be assigned to a variable. + + For example,"print 'hello'" is not valid, but '2+3' is. + + 2. Jobs given a function object, optionally passing additional + positional arguments: + + job_manager.new(myfunc, x, y) + + The function is called with the given arguments. + + If you need to pass keyword arguments to your function, you must + supply them as a dict named kw: + + job_manager.new(myfunc, x, y, kw=dict(z=1)) + + The reason for this asymmetry is that the new() method needs to + maintain access to its own keywords, and this prevents name collisions + between arguments to new() and arguments to your own functions. + + In both cases, the result is stored in the job.result field of the + background job object. + + You can set `daemon` attribute of the thread by giving the keyword + argument `daemon`. + + Notes and caveats: + + 1. All threads running share the same standard output. Thus, if your + background jobs generate output, it will come out on top of whatever + you are currently writing. For this reason, background jobs are best + used with silent functions which simply return their output. + + 2. Threads also all work within the same global namespace, and this + system does not lock interactive variables. So if you send job to the + background which operates on a mutable object for a long time, and + start modifying that same mutable object interactively (or in another + backgrounded job), all sorts of bizarre behaviour will occur. + + 3. If a background job is spending a lot of time inside a C extension + module which does not release the Python Global Interpreter Lock + (GIL), this will block the IPython prompt. This is simply because the + Python interpreter can only switch between threads at Python + bytecodes. While the execution is inside C code, the interpreter must + simply wait unless the extension module releases the GIL. + + 4. There is no way, due to limitations in the Python threads library, + to kill a thread once it has started.""" + + if callable(func_or_exp): + kw = kwargs.get('kw',{}) + job = BackgroundJobFunc(func_or_exp,*args,**kw) + elif isinstance(func_or_exp, str): + if not args: + frame = sys._getframe(1) + glob, loc = frame.f_globals, frame.f_locals + elif len(args)==1: + glob = loc = args[0] + elif len(args)==2: + glob,loc = args + else: + raise ValueError( + 'Expression jobs take at most 2 args (globals,locals)') + job = BackgroundJobExpr(func_or_exp, glob, loc) + else: + raise TypeError('invalid args for new job') + + if kwargs.get('daemon', False): + job.daemon = True + job.num = self._current_job_id + self._current_job_id += 1 + self.running.append(job) + self.all[job.num] = job + debug('Starting job # %s in a separate thread.' % job.num) + job.start() + return job + + def __getitem__(self, job_key): + num = job_key if isinstance(job_key, int) else job_key.num + return self.all[num] + + def __call__(self): + """An alias to self.status(), + + This allows you to simply call a job manager instance much like the + Unix `jobs` shell command.""" + + return self.status() + + def _update_status(self): + """Update the status of the job lists. + + This method moves finished jobs to one of two lists: + - self.completed: jobs which completed successfully + - self.dead: jobs which finished but died. + + It also copies those jobs to corresponding _report lists. These lists + are used to report jobs completed/dead since the last update, and are + then cleared by the reporting function after each call.""" + + # Status codes + srun, scomp, sdead = self._s_running, self._s_completed, self._s_dead + # State lists, use the actual lists b/c the public names are properties + # that call this very function on access + running, completed, dead = self._running, self._completed, self._dead + + # Now, update all state lists + for num, job in enumerate(running): + stat = job.stat_code + if stat == srun: + continue + elif stat == scomp: + completed.append(job) + self._comp_report.append(job) + running[num] = False + elif stat == sdead: + dead.append(job) + self._dead_report.append(job) + running[num] = False + # Remove dead/completed jobs from running list + running[:] = filter(None, running) + + def _group_report(self,group,name): + """Report summary for a given job group. + + Return True if the group had any elements.""" + + if group: + print('%s jobs:' % name) + for job in group: + print('%s : %s' % (job.num,job)) + print() + return True + + def _group_flush(self,group,name): + """Flush a given job group + + Return True if the group had any elements.""" + + njobs = len(group) + if njobs: + plural = {1:''}.setdefault(njobs,'s') + print('Flushing %s %s job%s.' % (njobs,name,plural)) + group[:] = [] + return True + + def _status_new(self): + """Print the status of newly finished jobs. + + Return True if any new jobs are reported. + + This call resets its own state every time, so it only reports jobs + which have finished since the last time it was called.""" + + self._update_status() + new_comp = self._group_report(self._comp_report, 'Completed') + new_dead = self._group_report(self._dead_report, + 'Dead, call jobs.traceback() for details') + self._comp_report[:] = [] + self._dead_report[:] = [] + return new_comp or new_dead + + def status(self,verbose=0): + """Print a status of all jobs currently being managed.""" + + self._update_status() + self._group_report(self.running,'Running') + self._group_report(self.completed,'Completed') + self._group_report(self.dead,'Dead') + # Also flush the report queues + self._comp_report[:] = [] + self._dead_report[:] = [] + + def remove(self,num): + """Remove a finished (completed or dead) job.""" + + try: + job = self.all[num] + except KeyError: + error('Job #%s not found' % num) + else: + stat_code = job.stat_code + if stat_code == self._s_running: + error('Job #%s is still running, it can not be removed.' % num) + return + elif stat_code == self._s_completed: + self.completed.remove(job) + elif stat_code == self._s_dead: + self.dead.remove(job) + + def flush(self): + """Flush all finished jobs (completed and dead) from lists. + + Running jobs are never flushed. + + It first calls _status_new(), to update info. If any jobs have + completed since the last _status_new() call, the flush operation + aborts.""" + + # Remove the finished jobs from the master dict + alljobs = self.all + for job in self.completed+self.dead: + del(alljobs[job.num]) + + # Now flush these lists completely + fl_comp = self._group_flush(self.completed, 'Completed') + fl_dead = self._group_flush(self.dead, 'Dead') + if not (fl_comp or fl_dead): + print('No jobs to flush.') + + def result(self,num): + """result(N) -> return the result of job N.""" + try: + return self.all[num].result + except KeyError: + error('Job #%s not found' % num) + + def _traceback(self, job): + num = job if isinstance(job, int) else job.num + try: + self.all[num].traceback() + except KeyError: + error('Job #%s not found' % num) + + def traceback(self, job=None): + if job is None: + self._update_status() + for deadjob in self.dead: + print("Traceback for: %r" % deadjob) + self._traceback(deadjob) + print() + else: + self._traceback(job) + + +class BackgroundJobBase(threading.Thread): + """Base class to build BackgroundJob classes. + + The derived classes must implement: + + - Their own __init__, since the one here raises NotImplementedError. The + derived constructor must call self._init() at the end, to provide common + initialization. + + - A strform attribute used in calls to __str__. + + - A call() method, which will make the actual execution call and must + return a value to be held in the 'result' field of the job object. + """ + + # Class constants for status, in string and as numerical codes (when + # updating jobs lists, we don't want to do string comparisons). This will + # be done at every user prompt, so it has to be as fast as possible + stat_created = 'Created'; stat_created_c = 0 + stat_running = 'Running'; stat_running_c = 1 + stat_completed = 'Completed'; stat_completed_c = 2 + stat_dead = 'Dead (Exception), call jobs.traceback() for details' + stat_dead_c = -1 + + def __init__(self): + """Must be implemented in subclasses. + + Subclasses must call :meth:`_init` for standard initialisation. + """ + raise NotImplementedError("This class can not be instantiated directly.") + + def _init(self): + """Common initialization for all BackgroundJob objects""" + + for attr in ['call','strform']: + assert hasattr(self,attr), "Missing attribute <%s>" % attr + + # The num tag can be set by an external job manager + self.num = None + + self.status = BackgroundJobBase.stat_created + self.stat_code = BackgroundJobBase.stat_created_c + self.finished = False + self.result = '' + + # reuse the ipython traceback handler if we can get to it, otherwise + # make a new one + try: + make_tb = get_ipython().InteractiveTB.text + except: + make_tb = AutoFormattedTB(mode = 'Context', + color_scheme='NoColor', + tb_offset = 1).text + # Note that the actual API for text() requires the three args to be + # passed in, so we wrap it in a simple lambda. + self._make_tb = lambda : make_tb(None, None, None) + + # Hold a formatted traceback if one is generated. + self._tb = None + + threading.Thread.__init__(self) + + def __str__(self): + return self.strform + + def __repr__(self): + return '' % (self.num, self.strform) + + def traceback(self): + print(self._tb) + + def run(self): + try: + self.status = BackgroundJobBase.stat_running + self.stat_code = BackgroundJobBase.stat_running_c + self.result = self.call() + except: + self.status = BackgroundJobBase.stat_dead + self.stat_code = BackgroundJobBase.stat_dead_c + self.finished = None + self.result = ('') + self._tb = self._make_tb() + else: + self.status = BackgroundJobBase.stat_completed + self.stat_code = BackgroundJobBase.stat_completed_c + self.finished = True + + +class BackgroundJobExpr(BackgroundJobBase): + """Evaluate an expression as a background job (uses a separate thread).""" + + def __init__(self, expression, glob=None, loc=None): + """Create a new job from a string which can be fed to eval(). + + global/locals dicts can be provided, which will be passed to the eval + call.""" + + # fail immediately if the given expression can't be compiled + self.code = compile(expression,'','eval') + + glob = {} if glob is None else glob + loc = {} if loc is None else loc + self.expression = self.strform = expression + self.glob = glob + self.loc = loc + self._init() + + def call(self): + return eval(self.code,self.glob,self.loc) + + +class BackgroundJobFunc(BackgroundJobBase): + """Run a function call as a background job (uses a separate thread).""" + + def __init__(self, func, *args, **kwargs): + """Create a new job from a callable object. + + Any positional arguments and keyword args given to this constructor + after the initial callable are passed directly to it.""" + + if not callable(func): + raise TypeError( + 'first argument to BackgroundJobFunc must be callable') + + self.func = func + self.args = args + self.kwargs = kwargs + # The string form will only include the function passed, because + # generating string representations of the arguments is a potentially + # _very_ expensive operation (e.g. with large arrays). + self.strform = str(func) + self._init() + + def call(self): + return self.func(*self.args, **self.kwargs) diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/clipboard.py b/myenv/lib/python3.12/site-packages/IPython/lib/clipboard.py new file mode 100644 index 00000000..e0bf80b0 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/clipboard.py @@ -0,0 +1,102 @@ +""" Utilities for accessing the platform's clipboard. +""" + +import os +import subprocess + +from IPython.core.error import TryNext +import IPython.utils.py3compat as py3compat + + +class ClipboardEmpty(ValueError): + pass + + +def win32_clipboard_get(): + """ Get the current clipboard's text on Windows. + + Requires Mark Hammond's pywin32 extensions. + """ + try: + import win32clipboard + except ImportError as e: + raise TryNext("Getting text from the clipboard requires the pywin32 " + "extensions: http://sourceforge.net/projects/pywin32/") from e + win32clipboard.OpenClipboard() + try: + text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT) + except (TypeError, win32clipboard.error): + try: + text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT) + text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING) + except (TypeError, win32clipboard.error) as e: + raise ClipboardEmpty from e + finally: + win32clipboard.CloseClipboard() + return text + + +def osx_clipboard_get() -> str: + """ Get the clipboard's text on OS X. + """ + p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'], + stdout=subprocess.PIPE) + bytes_, stderr = p.communicate() + # Text comes in with old Mac \r line endings. Change them to \n. + bytes_ = bytes_.replace(b'\r', b'\n') + text = py3compat.decode(bytes_) + return text + + +def tkinter_clipboard_get(): + """ Get the clipboard's text using Tkinter. + + This is the default on systems that are not Windows or OS X. It may + interfere with other UI toolkits and should be replaced with an + implementation that uses that toolkit. + """ + try: + from tkinter import Tk, TclError + except ImportError as e: + raise TryNext("Getting text from the clipboard on this platform requires tkinter.") from e + + root = Tk() + root.withdraw() + try: + text = root.clipboard_get() + except TclError as e: + raise ClipboardEmpty from e + finally: + root.destroy() + text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING) + return text + + +def wayland_clipboard_get(): + """Get the clipboard's text under Wayland using wl-paste command. + + This requires Wayland and wl-clipboard installed and running. + """ + if os.environ.get("XDG_SESSION_TYPE") != "wayland": + raise TryNext("wayland is not detected") + + try: + with subprocess.Popen(["wl-paste"], stdout=subprocess.PIPE) as p: + raw, err = p.communicate() + if p.wait(): + raise TryNext(err) + except FileNotFoundError as e: + raise TryNext( + "Getting text from the clipboard under Wayland requires the wl-clipboard " + "extension: https://github.com/bugaevc/wl-clipboard" + ) from e + + if not raw: + raise ClipboardEmpty + + try: + text = py3compat.decode(raw) + except UnicodeDecodeError as e: + raise ClipboardEmpty from e + + return text diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/deepreload.py b/myenv/lib/python3.12/site-packages/IPython/lib/deepreload.py new file mode 100644 index 00000000..aaedab24 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/deepreload.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- +""" +Provides a reload() function that acts recursively. + +Python's normal :func:`python:reload` function only reloads the module that it's +passed. The :func:`reload` function in this module also reloads everything +imported from that module, which is useful when you're changing files deep +inside a package. + +To use this as your default reload function, type this:: + + import builtins + from IPython.lib import deepreload + builtins.reload = deepreload.reload + +A reference to the original :func:`python:reload` is stored in this module as +:data:`original_reload`, so you can restore it later. + +This code is almost entirely based on knee.py, which is a Python +re-implementation of hierarchical module import. +""" +#***************************************************************************** +# Copyright (C) 2001 Nathaniel Gray +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#***************************************************************************** + +import builtins as builtin_mod +from contextlib import contextmanager +import importlib +import sys + +from types import ModuleType +from warnings import warn +import types + +original_import = builtin_mod.__import__ + +@contextmanager +def replace_import_hook(new_import): + saved_import = builtin_mod.__import__ + builtin_mod.__import__ = new_import + try: + yield + finally: + builtin_mod.__import__ = saved_import + +def get_parent(globals, level): + """ + parent, name = get_parent(globals, level) + + Return the package that an import is being performed in. If globals comes + from the module foo.bar.bat (not itself a package), this returns the + sys.modules entry for foo.bar. If globals is from a package's __init__.py, + the package's entry in sys.modules is returned. + + If globals doesn't come from a package or a module in a package, or a + corresponding entry is not found in sys.modules, None is returned. + """ + orig_level = level + + if not level or not isinstance(globals, dict): + return None, '' + + pkgname = globals.get('__package__', None) + + if pkgname is not None: + # __package__ is set, so use it + if not hasattr(pkgname, 'rindex'): + raise ValueError('__package__ set to non-string') + if len(pkgname) == 0: + if level > 0: + raise ValueError('Attempted relative import in non-package') + return None, '' + name = pkgname + else: + # __package__ not set, so figure it out and set it + if '__name__' not in globals: + return None, '' + modname = globals['__name__'] + + if '__path__' in globals: + # __path__ is set, so modname is already the package name + globals['__package__'] = name = modname + else: + # Normal module, so work out the package name if any + lastdot = modname.rfind('.') + if lastdot < 0 < level: + raise ValueError("Attempted relative import in non-package") + if lastdot < 0: + globals['__package__'] = None + return None, '' + globals['__package__'] = name = modname[:lastdot] + + dot = len(name) + for x in range(level, 1, -1): + try: + dot = name.rindex('.', 0, dot) + except ValueError as e: + raise ValueError("attempted relative import beyond top-level " + "package") from e + name = name[:dot] + + try: + parent = sys.modules[name] + except BaseException as e: + if orig_level < 1: + warn("Parent module '%.200s' not found while handling absolute " + "import" % name) + parent = None + else: + raise SystemError("Parent module '%.200s' not loaded, cannot " + "perform relative import" % name) from e + + # We expect, but can't guarantee, if parent != None, that: + # - parent.__name__ == name + # - parent.__dict__ is globals + # If this is violated... Who cares? + return parent, name + +def load_next(mod, altmod, name, buf): + """ + mod, name, buf = load_next(mod, altmod, name, buf) + + altmod is either None or same as mod + """ + + if len(name) == 0: + # completely empty module name should only happen in + # 'from . import' (or '__import__("")') + return mod, None, buf + + dot = name.find('.') + if dot == 0: + raise ValueError('Empty module name') + + if dot < 0: + subname = name + next = None + else: + subname = name[:dot] + next = name[dot+1:] + + if buf != '': + buf += '.' + buf += subname + + result = import_submodule(mod, subname, buf) + if result is None and mod != altmod: + result = import_submodule(altmod, subname, subname) + if result is not None: + buf = subname + + if result is None: + raise ImportError("No module named %.200s" % name) + + return result, next, buf + + +# Need to keep track of what we've already reloaded to prevent cyclic evil +found_now = {} + +def import_submodule(mod, subname, fullname): + """m = import_submodule(mod, subname, fullname)""" + # Require: + # if mod == None: subname == fullname + # else: mod.__name__ + "." + subname == fullname + + global found_now + if fullname in found_now and fullname in sys.modules: + m = sys.modules[fullname] + else: + print('Reloading', fullname) + found_now[fullname] = 1 + oldm = sys.modules.get(fullname, None) + try: + if oldm is not None: + m = importlib.reload(oldm) + else: + m = importlib.import_module(subname, mod) + except: + # load_module probably removed name from modules because of + # the error. Put back the original module object. + if oldm: + sys.modules[fullname] = oldm + raise + + add_submodule(mod, m, fullname, subname) + + return m + +def add_submodule(mod, submod, fullname, subname): + """mod.{subname} = submod""" + if mod is None: + return #Nothing to do here. + + if submod is None: + submod = sys.modules[fullname] + + setattr(mod, subname, submod) + + return + +def ensure_fromlist(mod, fromlist, buf, recursive): + """Handle 'from module import a, b, c' imports.""" + if not hasattr(mod, '__path__'): + return + for item in fromlist: + if not hasattr(item, 'rindex'): + raise TypeError("Item in ``from list'' not a string") + if item == '*': + if recursive: + continue # avoid endless recursion + try: + all = mod.__all__ + except AttributeError: + pass + else: + ret = ensure_fromlist(mod, all, buf, 1) + if not ret: + return 0 + elif not hasattr(mod, item): + import_submodule(mod, item, buf + '.' + item) + +def deep_import_hook(name, globals=None, locals=None, fromlist=None, level=-1): + """Replacement for __import__()""" + parent, buf = get_parent(globals, level) + + head, name, buf = load_next(parent, None if level < 0 else parent, name, buf) + + tail = head + while name: + tail, name, buf = load_next(tail, tail, name, buf) + + # If tail is None, both get_parent and load_next found + # an empty module name: someone called __import__("") or + # doctored faulty bytecode + if tail is None: + raise ValueError('Empty module name') + + if not fromlist: + return head + + ensure_fromlist(tail, fromlist, buf, 0) + return tail + +modules_reloading = {} + +def deep_reload_hook(m): + """Replacement for reload().""" + # Hardcode this one as it would raise a NotImplementedError from the + # bowels of Python and screw up the import machinery after. + # unlike other imports the `exclude` list already in place is not enough. + + if m is types: + return m + if not isinstance(m, ModuleType): + raise TypeError("reload() argument must be module") + + name = m.__name__ + + if name not in sys.modules: + raise ImportError("reload(): module %.200s not in sys.modules" % name) + + global modules_reloading + try: + return modules_reloading[name] + except: + modules_reloading[name] = m + + try: + newm = importlib.reload(m) + except: + sys.modules[name] = m + raise + finally: + modules_reloading.clear() + return newm + +# Save the original hooks +original_reload = importlib.reload + +# Replacement for reload() +def reload( + module, + exclude=( + *sys.builtin_module_names, + "sys", + "os.path", + "builtins", + "__main__", + "numpy", + "numpy._globals", + ), +): + """Recursively reload all modules used in the given module. Optionally + takes a list of modules to exclude from reloading. The default exclude + list contains modules listed in sys.builtin_module_names with additional + sys, os.path, builtins and __main__, to prevent, e.g., resetting + display, exception, and io hooks. + """ + global found_now + for i in exclude: + found_now[i] = 1 + try: + with replace_import_hook(deep_import_hook): + return deep_reload_hook(module) + finally: + found_now = {} diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/demo.py b/myenv/lib/python3.12/site-packages/IPython/lib/demo.py new file mode 100644 index 00000000..ebffd54a --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/demo.py @@ -0,0 +1,672 @@ +"""Module for interactive demos using IPython. + +This module implements a few classes for running Python scripts interactively +in IPython for demonstrations. With very simple markup (a few tags in +comments), you can control points where the script stops executing and returns +control to IPython. + + +Provided classes +---------------- + +The classes are (see their docstrings for further details): + + - Demo: pure python demos + + - IPythonDemo: demos with input to be processed by IPython as if it had been + typed interactively (so magics work, as well as any other special syntax you + may have added via input prefilters). + + - LineDemo: single-line version of the Demo class. These demos are executed + one line at a time, and require no markup. + + - IPythonLineDemo: IPython version of the LineDemo class (the demo is + executed a line at a time, but processed via IPython). + + - ClearMixin: mixin to make Demo classes with less visual clutter. It + declares an empty marquee and a pre_cmd that clears the screen before each + block (see Subclassing below). + + - ClearDemo, ClearIPDemo: mixin-enabled versions of the Demo and IPythonDemo + classes. + +Inheritance diagram: + +.. inheritance-diagram:: IPython.lib.demo + :parts: 3 + +Subclassing +----------- + +The classes here all include a few methods meant to make customization by +subclassing more convenient. Their docstrings below have some more details: + + - highlight(): format every block and optionally highlight comments and + docstring content. + + - marquee(): generates a marquee to provide visible on-screen markers at each + block start and end. + + - pre_cmd(): run right before the execution of each block. + + - post_cmd(): run right after the execution of each block. If the block + raises an exception, this is NOT called. + + +Operation +--------- + +The file is run in its own empty namespace (though you can pass it a string of +arguments as if in a command line environment, and it will see those as +sys.argv). But at each stop, the global IPython namespace is updated with the +current internal demo namespace, so you can work interactively with the data +accumulated so far. + +By default, each block of code is printed (with syntax highlighting) before +executing it and you have to confirm execution. This is intended to show the +code to an audience first so you can discuss it, and only proceed with +execution once you agree. There are a few tags which allow you to modify this +behavior. + +The supported tags are: + +# stop + + Defines block boundaries, the points where IPython stops execution of the + file and returns to the interactive prompt. + + You can optionally mark the stop tag with extra dashes before and after the + word 'stop', to help visually distinguish the blocks in a text editor: + + # --- stop --- + + +# silent + + Make a block execute silently (and hence automatically). Typically used in + cases where you have some boilerplate or initialization code which you need + executed but do not want to be seen in the demo. + +# auto + + Make a block execute automatically, but still being printed. Useful for + simple code which does not warrant discussion, since it avoids the extra + manual confirmation. + +# auto_all + + This tag can _only_ be in the first block, and if given it overrides the + individual auto tags to make the whole demo fully automatic (no block asks + for confirmation). It can also be given at creation time (or the attribute + set later) to override what's in the file. + +While _any_ python file can be run as a Demo instance, if there are no stop +tags the whole file will run in a single block (no different that calling +first %pycat and then %run). The minimal markup to make this useful is to +place a set of stop tags; the other tags are only there to let you fine-tune +the execution. + +This is probably best explained with the simple example file below. You can +copy this into a file named ex_demo.py, and try running it via:: + + from IPython.lib.demo import Demo + d = Demo('ex_demo.py') + d() + +Each time you call the demo object, it runs the next block. The demo object +has a few useful methods for navigation, like again(), edit(), jump(), seek() +and back(). It can be reset for a new run via reset() or reloaded from disk +(in case you've edited the source) via reload(). See their docstrings below. + +Note: To make this simpler to explore, a file called "demo-exercizer.py" has +been added to the "docs/examples/core" directory. Just cd to this directory in +an IPython session, and type:: + + %run demo-exercizer.py + +and then follow the directions. + +Example +------- + +The following is a very simple example of a valid demo file. + +:: + + #################### EXAMPLE DEMO ############################### + '''A simple interactive demo to illustrate the use of IPython's Demo class.''' + + print('Hello, welcome to an interactive IPython demo.') + + # The mark below defines a block boundary, which is a point where IPython will + # stop execution and return to the interactive prompt. The dashes are actually + # optional and used only as a visual aid to clearly separate blocks while + # editing the demo code. + # stop + + x = 1 + y = 2 + + # stop + + # the mark below makes this block as silent + # silent + + print('This is a silent block, which gets executed but not printed.') + + # stop + # auto + print('This is an automatic block.') + print('It is executed without asking for confirmation, but printed.') + z = x + y + + print('z =', x) + + # stop + # This is just another normal block. + print('z is now:', z) + + print('bye!') + ################### END EXAMPLE DEMO ############################ +""" + + +#***************************************************************************** +# Copyright (C) 2005-2006 Fernando Perez. +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +# +#***************************************************************************** + +import os +import re +import shlex +import sys +import pygments +from pathlib import Path + +from IPython.utils.text import marquee +from IPython.utils import openpy +from IPython.utils import py3compat +__all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError'] + +class DemoError(Exception): pass + +def re_mark(mark): + return re.compile(r'^\s*#\s+\s+%s\s*$' % mark,re.MULTILINE) + +class Demo(object): + + re_stop = re_mark(r'-*\s?stop\s?-*') + re_silent = re_mark('silent') + re_auto = re_mark('auto') + re_auto_all = re_mark('auto_all') + + def __init__(self,src,title='',arg_str='',auto_all=None, format_rst=False, + formatter='terminal', style='default'): + """Make a new demo object. To run the demo, simply call the object. + + See the module docstring for full details and an example (you can use + IPython.Demo? in IPython to see it). + + Inputs: + + - src is either a file, or file-like object, or a + string that can be resolved to a filename. + + Optional inputs: + + - title: a string to use as the demo name. Of most use when the demo + you are making comes from an object that has no filename, or if you + want an alternate denotation distinct from the filename. + + - arg_str(''): a string of arguments, internally converted to a list + just like sys.argv, so the demo script can see a similar + environment. + + - auto_all(None): global flag to run all blocks automatically without + confirmation. This attribute overrides the block-level tags and + applies to the whole demo. It is an attribute of the object, and + can be changed at runtime simply by reassigning it to a boolean + value. + + - format_rst(False): a bool to enable comments and doc strings + formatting with pygments rst lexer + + - formatter('terminal'): a string of pygments formatter name to be + used. Useful values for terminals: terminal, terminal256, + terminal16m + + - style('default'): a string of pygments style name to be used. + """ + if hasattr(src, "read"): + # It seems to be a file or a file-like object + self.fname = "from a file-like object" + if title == '': + self.title = "from a file-like object" + else: + self.title = title + else: + # Assume it's a string or something that can be converted to one + self.fname = src + if title == '': + (filepath, filename) = os.path.split(src) + self.title = filename + else: + self.title = title + self.sys_argv = [src] + shlex.split(arg_str) + self.auto_all = auto_all + self.src = src + + try: + ip = get_ipython() # this is in builtins whenever IPython is running + self.inside_ipython = True + except NameError: + self.inside_ipython = False + + if self.inside_ipython: + # get a few things from ipython. While it's a bit ugly design-wise, + # it ensures that things like color scheme and the like are always in + # sync with the ipython mode being used. This class is only meant to + # be used inside ipython anyways, so it's OK. + self.ip_ns = ip.user_ns + self.ip_colorize = ip.pycolorize + self.ip_showtb = ip.showtraceback + self.ip_run_cell = ip.run_cell + self.shell = ip + + self.formatter = pygments.formatters.get_formatter_by_name(formatter, + style=style) + self.python_lexer = pygments.lexers.get_lexer_by_name("py3") + self.format_rst = format_rst + if format_rst: + self.rst_lexer = pygments.lexers.get_lexer_by_name("rst") + + # load user data and initialize data structures + self.reload() + + def fload(self): + """Load file object.""" + # read data and parse into blocks + if hasattr(self, 'fobj') and self.fobj is not None: + self.fobj.close() + if hasattr(self.src, "read"): + # It seems to be a file or a file-like object + self.fobj = self.src + else: + # Assume it's a string or something that can be converted to one + self.fobj = openpy.open(self.fname) + + def reload(self): + """Reload source from disk and initialize state.""" + self.fload() + + self.src = "".join(openpy.strip_encoding_cookie(self.fobj)) + src_b = [b.strip() for b in self.re_stop.split(self.src) if b] + self._silent = [bool(self.re_silent.findall(b)) for b in src_b] + self._auto = [bool(self.re_auto.findall(b)) for b in src_b] + + # if auto_all is not given (def. None), we read it from the file + if self.auto_all is None: + self.auto_all = bool(self.re_auto_all.findall(src_b[0])) + else: + self.auto_all = bool(self.auto_all) + + # Clean the sources from all markup so it doesn't get displayed when + # running the demo + src_blocks = [] + auto_strip = lambda s: self.re_auto.sub('',s) + for i,b in enumerate(src_b): + if self._auto[i]: + src_blocks.append(auto_strip(b)) + else: + src_blocks.append(b) + # remove the auto_all marker + src_blocks[0] = self.re_auto_all.sub('',src_blocks[0]) + + self.nblocks = len(src_blocks) + self.src_blocks = src_blocks + + # also build syntax-highlighted source + self.src_blocks_colored = list(map(self.highlight,self.src_blocks)) + + # ensure clean namespace and seek offset + self.reset() + + def reset(self): + """Reset the namespace and seek pointer to restart the demo""" + self.user_ns = {} + self.finished = False + self.block_index = 0 + + def _validate_index(self,index): + if index<0 or index>=self.nblocks: + raise ValueError('invalid block index %s' % index) + + def _get_index(self,index): + """Get the current block index, validating and checking status. + + Returns None if the demo is finished""" + + if index is None: + if self.finished: + print('Demo finished. Use .reset() if you want to rerun it.') + return None + index = self.block_index + else: + self._validate_index(index) + return index + + def seek(self,index): + """Move the current seek pointer to the given block. + + You can use negative indices to seek from the end, with identical + semantics to those of Python lists.""" + if index<0: + index = self.nblocks + index + self._validate_index(index) + self.block_index = index + self.finished = False + + def back(self,num=1): + """Move the seek pointer back num blocks (default is 1).""" + self.seek(self.block_index-num) + + def jump(self,num=1): + """Jump a given number of blocks relative to the current one. + + The offset can be positive or negative, defaults to 1.""" + self.seek(self.block_index+num) + + def again(self): + """Move the seek pointer back one block and re-execute.""" + self.back(1) + self() + + def edit(self,index=None): + """Edit a block. + + If no number is given, use the last block executed. + + This edits the in-memory copy of the demo, it does NOT modify the + original source file. If you want to do that, simply open the file in + an editor and use reload() when you make changes to the file. This + method is meant to let you change a block during a demonstration for + explanatory purposes, without damaging your original script.""" + + index = self._get_index(index) + if index is None: + return + # decrease the index by one (unless we're at the very beginning), so + # that the default demo.edit() call opens up the sblock we've last run + if index>0: + index -= 1 + + filename = self.shell.mktempfile(self.src_blocks[index]) + self.shell.hooks.editor(filename, 1) + with open(Path(filename), "r", encoding="utf-8") as f: + new_block = f.read() + # update the source and colored block + self.src_blocks[index] = new_block + self.src_blocks_colored[index] = self.highlight(new_block) + self.block_index = index + # call to run with the newly edited index + self() + + def show(self,index=None): + """Show a single block on screen""" + + index = self._get_index(index) + if index is None: + return + + print(self.marquee('<%s> block # %s (%s remaining)' % + (self.title,index,self.nblocks-index-1))) + print(self.src_blocks_colored[index]) + sys.stdout.flush() + + def show_all(self): + """Show entire demo on screen, block by block""" + + fname = self.title + title = self.title + nblocks = self.nblocks + silent = self._silent + marquee = self.marquee + for index,block in enumerate(self.src_blocks_colored): + if silent[index]: + print(marquee('<%s> SILENT block # %s (%s remaining)' % + (title,index,nblocks-index-1))) + else: + print(marquee('<%s> block # %s (%s remaining)' % + (title,index,nblocks-index-1))) + print(block, end=' ') + sys.stdout.flush() + + def run_cell(self,source): + """Execute a string with one or more lines of code""" + + exec(source, self.user_ns) + + def __call__(self,index=None): + """run a block of the demo. + + If index is given, it should be an integer >=1 and <= nblocks. This + means that the calling convention is one off from typical Python + lists. The reason for the inconsistency is that the demo always + prints 'Block n/N, and N is the total, so it would be very odd to use + zero-indexing here.""" + + index = self._get_index(index) + if index is None: + return + try: + marquee = self.marquee + next_block = self.src_blocks[index] + self.block_index += 1 + if self._silent[index]: + print(marquee('Executing silent block # %s (%s remaining)' % + (index,self.nblocks-index-1))) + else: + self.pre_cmd() + self.show(index) + if self.auto_all or self._auto[index]: + print(marquee('output:')) + else: + print(marquee('Press to quit, to execute...'), end=' ') + ans = py3compat.input().strip() + if ans: + print(marquee('Block NOT executed')) + return + try: + save_argv = sys.argv + sys.argv = self.sys_argv + self.run_cell(next_block) + self.post_cmd() + finally: + sys.argv = save_argv + + except: + if self.inside_ipython: + self.ip_showtb(filename=self.fname) + else: + if self.inside_ipython: + self.ip_ns.update(self.user_ns) + + if self.block_index == self.nblocks: + mq1 = self.marquee('END OF DEMO') + if mq1: + # avoid spurious print if empty marquees are used + print() + print(mq1) + print(self.marquee('Use .reset() if you want to rerun it.')) + self.finished = True + + # These methods are meant to be overridden by subclasses who may wish to + # customize the behavior of of their demos. + def marquee(self,txt='',width=78,mark='*'): + """Return the input string centered in a 'marquee'.""" + return marquee(txt,width,mark) + + def pre_cmd(self): + """Method called before executing each block.""" + pass + + def post_cmd(self): + """Method called after executing each block.""" + pass + + def highlight(self, block): + """Method called on each block to highlight it content""" + tokens = pygments.lex(block, self.python_lexer) + if self.format_rst: + from pygments.token import Token + toks = [] + for token in tokens: + if token[0] == Token.String.Doc and len(token[1]) > 6: + toks += pygments.lex(token[1][:3], self.python_lexer) + # parse doc string content by rst lexer + toks += pygments.lex(token[1][3:-3], self.rst_lexer) + toks += pygments.lex(token[1][-3:], self.python_lexer) + elif token[0] == Token.Comment.Single: + toks.append((Token.Comment.Single, token[1][0])) + # parse comment content by rst lexer + # remove the extra newline added by rst lexer + toks += list(pygments.lex(token[1][1:], self.rst_lexer))[:-1] + else: + toks.append(token) + tokens = toks + return pygments.format(tokens, self.formatter) + + +class IPythonDemo(Demo): + """Class for interactive demos with IPython's input processing applied. + + This subclasses Demo, but instead of executing each block by the Python + interpreter (via exec), it actually calls IPython on it, so that any input + filters which may be in place are applied to the input block. + + If you have an interactive environment which exposes special input + processing, you can use this class instead to write demo scripts which + operate exactly as if you had typed them interactively. The default Demo + class requires the input to be valid, pure Python code. + """ + + def run_cell(self,source): + """Execute a string with one or more lines of code""" + + self.shell.run_cell(source) + +class LineDemo(Demo): + """Demo where each line is executed as a separate block. + + The input script should be valid Python code. + + This class doesn't require any markup at all, and it's meant for simple + scripts (with no nesting or any kind of indentation) which consist of + multiple lines of input to be executed, one at a time, as if they had been + typed in the interactive prompt. + + Note: the input can not have *any* indentation, which means that only + single-lines of input are accepted, not even function definitions are + valid.""" + + def reload(self): + """Reload source from disk and initialize state.""" + # read data and parse into blocks + self.fload() + lines = self.fobj.readlines() + src_b = [l for l in lines if l.strip()] + nblocks = len(src_b) + self.src = ''.join(lines) + self._silent = [False]*nblocks + self._auto = [True]*nblocks + self.auto_all = True + self.nblocks = nblocks + self.src_blocks = src_b + + # also build syntax-highlighted source + self.src_blocks_colored = list(map(self.highlight,self.src_blocks)) + + # ensure clean namespace and seek offset + self.reset() + + +class IPythonLineDemo(IPythonDemo,LineDemo): + """Variant of the LineDemo class whose input is processed by IPython.""" + pass + + +class ClearMixin(object): + """Use this mixin to make Demo classes with less visual clutter. + + Demos using this mixin will clear the screen before every block and use + blank marquees. + + Note that in order for the methods defined here to actually override those + of the classes it's mixed with, it must go /first/ in the inheritance + tree. For example: + + class ClearIPDemo(ClearMixin,IPythonDemo): pass + + will provide an IPythonDemo class with the mixin's features. + """ + + def marquee(self,txt='',width=78,mark='*'): + """Blank marquee that returns '' no matter what the input.""" + return '' + + def pre_cmd(self): + """Method called before executing each block. + + This one simply clears the screen.""" + from IPython.utils.terminal import _term_clear + _term_clear() + +class ClearDemo(ClearMixin,Demo): + pass + + +class ClearIPDemo(ClearMixin,IPythonDemo): + pass + + +def slide(file_path, noclear=False, format_rst=True, formatter="terminal", + style="native", auto_all=False, delimiter='...'): + if noclear: + demo_class = Demo + else: + demo_class = ClearDemo + demo = demo_class(file_path, format_rst=format_rst, formatter=formatter, + style=style, auto_all=auto_all) + while not demo.finished: + demo() + try: + py3compat.input('\n' + delimiter) + except KeyboardInterrupt: + exit(1) + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(description='Run python demos') + parser.add_argument('--noclear', '-C', action='store_true', + help='Do not clear terminal on each slide') + parser.add_argument('--rst', '-r', action='store_true', + help='Highlight comments and dostrings as rst') + parser.add_argument('--formatter', '-f', default='terminal', + help='pygments formatter name could be: terminal, ' + 'terminal256, terminal16m') + parser.add_argument('--style', '-s', default='default', + help='pygments style name') + parser.add_argument('--auto', '-a', action='store_true', + help='Run all blocks automatically without' + 'confirmation') + parser.add_argument('--delimiter', '-d', default='...', + help='slides delimiter added after each slide run') + parser.add_argument('file', nargs=1, + help='python demo file') + args = parser.parse_args() + slide(args.file[0], noclear=args.noclear, format_rst=args.rst, + formatter=args.formatter, style=args.style, auto_all=args.auto, + delimiter=args.delimiter) diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/display.py b/myenv/lib/python3.12/site-packages/IPython/lib/display.py new file mode 100644 index 00000000..f39f389f --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/display.py @@ -0,0 +1,677 @@ +"""Various display related classes. + +Authors : MinRK, gregcaporaso, dannystaple +""" +from html import escape as html_escape +from os.path import exists, isfile, splitext, abspath, join, isdir +from os import walk, sep, fsdecode + +from IPython.core.display import DisplayObject, TextDisplayObject + +from typing import Tuple, Iterable, Optional + +__all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument', + 'FileLink', 'FileLinks', 'Code'] + + +class Audio(DisplayObject): + """Create an audio object. + + When this object is returned by an input cell or passed to the + display function, it will result in Audio controls being displayed + in the frontend (only works in the notebook). + + Parameters + ---------- + data : numpy array, list, unicode, str or bytes + Can be one of + + * Numpy 1d array containing the desired waveform (mono) + * Numpy 2d array containing waveforms for each channel. + Shape=(NCHAN, NSAMPLES). For the standard channel order, see + http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx + * List of float or integer representing the waveform (mono) + * String containing the filename + * Bytestring containing raw PCM data or + * URL pointing to a file on the web. + + If the array option is used, the waveform will be normalized. + + If a filename or url is used, the format support will be browser + dependent. + url : unicode + A URL to download the data from. + filename : unicode + Path to a local file to load the data from. + embed : boolean + Should the audio data be embedded using a data URI (True) or should + the original source be referenced. Set this to True if you want the + audio to playable later with no internet connection in the notebook. + + Default is `True`, unless the keyword argument `url` is set, then + default value is `False`. + rate : integer + The sampling rate of the raw data. + Only required when data parameter is being used as an array + autoplay : bool + Set to True if the audio should immediately start playing. + Default is `False`. + normalize : bool + Whether audio should be normalized (rescaled) to the maximum possible + range. Default is `True`. When set to `False`, `data` must be between + -1 and 1 (inclusive), otherwise an error is raised. + Applies only when `data` is a list or array of samples; other types of + audio are never normalized. + + Examples + -------- + + >>> import pytest + >>> np = pytest.importorskip("numpy") + + Generate a sound + + >>> import numpy as np + >>> framerate = 44100 + >>> t = np.linspace(0,5,framerate*5) + >>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t) + >>> Audio(data, rate=framerate) + + + Can also do stereo or more channels + + >>> dataleft = np.sin(2*np.pi*220*t) + >>> dataright = np.sin(2*np.pi*224*t) + >>> Audio([dataleft, dataright], rate=framerate) + + + From URL: + + >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP + >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP + + From a File: + + >>> Audio('IPython/lib/tests/test.wav') # doctest: +SKIP + >>> Audio(filename='IPython/lib/tests/test.wav') # doctest: +SKIP + + From Bytes: + + >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP + >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP + + See Also + -------- + ipywidgets.Audio + + Audio widget with more more flexibility and options. + + """ + _read_flags = 'rb' + + def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *, + element_id=None): + if filename is None and url is None and data is None: + raise ValueError("No audio data found. Expecting filename, url, or data.") + if embed is False and url is None: + raise ValueError("No url found. Expecting url when embed=False") + + if url is not None and embed is not True: + self.embed = False + else: + self.embed = True + self.autoplay = autoplay + self.element_id = element_id + super(Audio, self).__init__(data=data, url=url, filename=filename) + + if self.data is not None and not isinstance(self.data, bytes): + if rate is None: + raise ValueError("rate must be specified when data is a numpy array or list of audio samples.") + self.data = Audio._make_wav(data, rate, normalize) + + def reload(self): + """Reload the raw data from file or URL.""" + import mimetypes + if self.embed: + super(Audio, self).reload() + + if self.filename is not None: + self.mimetype = mimetypes.guess_type(self.filename)[0] + elif self.url is not None: + self.mimetype = mimetypes.guess_type(self.url)[0] + else: + self.mimetype = "audio/wav" + + @staticmethod + def _make_wav(data, rate, normalize): + """ Transform a numpy array to a PCM bytestring """ + from io import BytesIO + import wave + + try: + scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize) + except ImportError: + scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize) + + fp = BytesIO() + waveobj = wave.open(fp,mode='wb') + waveobj.setnchannels(nchan) + waveobj.setframerate(rate) + waveobj.setsampwidth(2) + waveobj.setcomptype('NONE','NONE') + waveobj.writeframes(scaled) + val = fp.getvalue() + waveobj.close() + + return val + + @staticmethod + def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]: + import numpy as np + + data = np.array(data, dtype=float) + if len(data.shape) == 1: + nchan = 1 + elif len(data.shape) == 2: + # In wave files,channels are interleaved. E.g., + # "L1R1L2R2..." for stereo. See + # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx + # for channel ordering + nchan = data.shape[0] + data = data.T.ravel() + else: + raise ValueError('Array audio input must be a 1D or 2D array') + + max_abs_value = np.max(np.abs(data)) + normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize) + scaled = data / normalization_factor * 32767 + return scaled.astype(" 1: + raise ValueError('Audio data must be between -1 and 1 when normalize=False.') + return max_abs_value if normalize else 1 + + def _data_and_metadata(self): + """shortcut for returning metadata with url information, if defined""" + md = {} + if self.url: + md['url'] = self.url + if md: + return self.data, md + else: + return self.data + + def _repr_html_(self): + src = """ + + """ + return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(), + element_id=self.element_id_attr()) + + def src_attr(self): + import base64 + if self.embed and (self.data is not None): + data = base64=base64.b64encode(self.data).decode('ascii') + return """data:{type};base64,{base64}""".format(type=self.mimetype, + base64=data) + elif self.url is not None: + return self.url + else: + return "" + + def autoplay_attr(self): + if(self.autoplay): + return 'autoplay="autoplay"' + else: + return '' + + def element_id_attr(self): + if (self.element_id): + return 'id="{element_id}"'.format(element_id=self.element_id) + else: + return '' + +class IFrame(object): + """ + Generic class to embed an iframe in an IPython notebook + """ + + iframe = """ + + """ + + def __init__( + self, src, width, height, extras: Optional[Iterable[str]] = None, **kwargs + ): + if extras is None: + extras = [] + + self.src = src + self.width = width + self.height = height + self.extras = extras + self.params = kwargs + + def _repr_html_(self): + """return the embed iframe""" + if self.params: + from urllib.parse import urlencode + params = "?" + urlencode(self.params) + else: + params = "" + return self.iframe.format( + src=self.src, + width=self.width, + height=self.height, + params=params, + extras=" ".join(self.extras), + ) + + +class YouTubeVideo(IFrame): + """Class for embedding a YouTube Video in an IPython session, based on its video id. + + e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would + do:: + + vid = YouTubeVideo("foo") + display(vid) + + To start from 30 seconds:: + + vid = YouTubeVideo("abc", start=30) + display(vid) + + To calculate seconds from time as hours, minutes, seconds use + :class:`datetime.timedelta`:: + + start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds()) + + Other parameters can be provided as documented at + https://developers.google.com/youtube/player_parameters#Parameters + + When converting the notebook using nbconvert, a jpeg representation of the video + will be inserted in the document. + """ + + def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs): + self.id=id + src = "https://www.youtube.com/embed/{0}".format(id) + if allow_autoplay: + extras = list(kwargs.get("extras", [])) + ['allow="autoplay"'] + kwargs.update(autoplay=1, extras=extras) + super(YouTubeVideo, self).__init__(src, width, height, **kwargs) + + def _repr_jpeg_(self): + # Deferred import + from urllib.request import urlopen + + try: + return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read() + except IOError: + return None + +class VimeoVideo(IFrame): + """ + Class for embedding a Vimeo video in an IPython session, based on its video id. + """ + + def __init__(self, id, width=400, height=300, **kwargs): + src="https://player.vimeo.com/video/{0}".format(id) + super(VimeoVideo, self).__init__(src, width, height, **kwargs) + +class ScribdDocument(IFrame): + """ + Class for embedding a Scribd document in an IPython session + + Use the start_page params to specify a starting point in the document + Use the view_mode params to specify display type one off scroll | slideshow | book + + e.g to Display Wes' foundational paper about PANDAS in book mode from page 3 + + ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book") + """ + + def __init__(self, id, width=400, height=300, **kwargs): + src="https://www.scribd.com/embeds/{0}/content".format(id) + super(ScribdDocument, self).__init__(src, width, height, **kwargs) + +class FileLink(object): + """Class for embedding a local file link in an IPython session, based on path + + e.g. to embed a link that was generated in the IPython notebook as my/data.txt + + you would do:: + + local_file = FileLink("my/data.txt") + display(local_file) + + or in the HTML notebook, just:: + + FileLink("my/data.txt") + """ + + html_link_str = "%s" + + def __init__(self, + path, + url_prefix='', + result_html_prefix='', + result_html_suffix='
'): + """ + Parameters + ---------- + path : str + path to the file or directory that should be formatted + url_prefix : str + prefix to be prepended to all files to form a working link [default: + ''] + result_html_prefix : str + text to append to beginning to link [default: ''] + result_html_suffix : str + text to append at the end of link [default: '
'] + """ + if isdir(path): + raise ValueError("Cannot display a directory using FileLink. " + "Use FileLinks to display '%s'." % path) + self.path = fsdecode(path) + self.url_prefix = url_prefix + self.result_html_prefix = result_html_prefix + self.result_html_suffix = result_html_suffix + + def _format_path(self): + fp = ''.join([self.url_prefix, html_escape(self.path)]) + return ''.join([self.result_html_prefix, + self.html_link_str % \ + (fp, html_escape(self.path, quote=False)), + self.result_html_suffix]) + + def _repr_html_(self): + """return html link to file + """ + if not exists(self.path): + return ("Path (%s) doesn't exist. " + "It may still be in the process of " + "being generated, or you may have the " + "incorrect path." % self.path) + + return self._format_path() + + def __repr__(self): + """return absolute path to file + """ + return abspath(self.path) + +class FileLinks(FileLink): + """Class for embedding local file links in an IPython session, based on path + + e.g. to embed links to files that were generated in the IPython notebook + under ``my/data``, you would do:: + + local_files = FileLinks("my/data") + display(local_files) + + or in the HTML notebook, just:: + + FileLinks("my/data") + """ + def __init__(self, + path, + url_prefix='', + included_suffixes=None, + result_html_prefix='', + result_html_suffix='
', + notebook_display_formatter=None, + terminal_display_formatter=None, + recursive=True): + """ + See :class:`FileLink` for the ``path``, ``url_prefix``, + ``result_html_prefix`` and ``result_html_suffix`` parameters. + + included_suffixes : list + Filename suffixes to include when formatting output [default: include + all files] + + notebook_display_formatter : function + Used to format links for display in the notebook. See discussion of + formatter functions below. + + terminal_display_formatter : function + Used to format links for display in the terminal. See discussion of + formatter functions below. + + Formatter functions must be of the form:: + + f(dirname, fnames, included_suffixes) + + dirname : str + The name of a directory + fnames : list + The files in that directory + included_suffixes : list + The file suffixes that should be included in the output (passing None + meansto include all suffixes in the output in the built-in formatters) + recursive : boolean + Whether to recurse into subdirectories. Default is True. + + The function should return a list of lines that will be printed in the + notebook (if passing notebook_display_formatter) or the terminal (if + passing terminal_display_formatter). This function is iterated over for + each directory in self.path. Default formatters are in place, can be + passed here to support alternative formatting. + + """ + if isfile(path): + raise ValueError("Cannot display a file using FileLinks. " + "Use FileLink to display '%s'." % path) + self.included_suffixes = included_suffixes + # remove trailing slashes for more consistent output formatting + path = path.rstrip('/') + + self.path = path + self.url_prefix = url_prefix + self.result_html_prefix = result_html_prefix + self.result_html_suffix = result_html_suffix + + self.notebook_display_formatter = \ + notebook_display_formatter or self._get_notebook_display_formatter() + self.terminal_display_formatter = \ + terminal_display_formatter or self._get_terminal_display_formatter() + + self.recursive = recursive + + def _get_display_formatter( + self, dirname_output_format, fname_output_format, fp_format, fp_cleaner=None + ): + """generate built-in formatter function + + this is used to define both the notebook and terminal built-in + formatters as they only differ by some wrapper text for each entry + + dirname_output_format: string to use for formatting directory + names, dirname will be substituted for a single "%s" which + must appear in this string + fname_output_format: string to use for formatting file names, + if a single "%s" appears in the string, fname will be substituted + if two "%s" appear in the string, the path to fname will be + substituted for the first and fname will be substituted for the + second + fp_format: string to use for formatting filepaths, must contain + exactly two "%s" and the dirname will be substituted for the first + and fname will be substituted for the second + """ + def f(dirname, fnames, included_suffixes=None): + result = [] + # begin by figuring out which filenames, if any, + # are going to be displayed + display_fnames = [] + for fname in fnames: + if (isfile(join(dirname,fname)) and + (included_suffixes is None or + splitext(fname)[1] in included_suffixes)): + display_fnames.append(fname) + + if len(display_fnames) == 0: + # if there are no filenames to display, don't print anything + # (not even the directory name) + pass + else: + # otherwise print the formatted directory name followed by + # the formatted filenames + dirname_output_line = dirname_output_format % dirname + result.append(dirname_output_line) + for fname in display_fnames: + fp = fp_format % (dirname,fname) + if fp_cleaner is not None: + fp = fp_cleaner(fp) + try: + # output can include both a filepath and a filename... + fname_output_line = fname_output_format % (fp, fname) + except TypeError: + # ... or just a single filepath + fname_output_line = fname_output_format % fname + result.append(fname_output_line) + return result + return f + + def _get_notebook_display_formatter(self, + spacer="  "): + """ generate function to use for notebook formatting + """ + dirname_output_format = \ + self.result_html_prefix + "%s/" + self.result_html_suffix + fname_output_format = \ + self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix + fp_format = self.url_prefix + '%s/%s' + if sep == "\\": + # Working on a platform where the path separator is "\", so + # must convert these to "/" for generating a URI + def fp_cleaner(fp): + # Replace all occurrences of backslash ("\") with a forward + # slash ("/") - this is necessary on windows when a path is + # provided as input, but we must link to a URI + return fp.replace('\\','/') + else: + fp_cleaner = None + + return self._get_display_formatter(dirname_output_format, + fname_output_format, + fp_format, + fp_cleaner) + + def _get_terminal_display_formatter(self, + spacer=" "): + """ generate function to use for terminal formatting + """ + dirname_output_format = "%s/" + fname_output_format = spacer + "%s" + fp_format = '%s/%s' + + return self._get_display_formatter(dirname_output_format, + fname_output_format, + fp_format) + + def _format_path(self): + result_lines = [] + if self.recursive: + walked_dir = list(walk(self.path)) + else: + walked_dir = [next(walk(self.path))] + walked_dir.sort() + for dirname, subdirs, fnames in walked_dir: + result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes) + return '\n'.join(result_lines) + + def __repr__(self): + """return newline-separated absolute paths + """ + result_lines = [] + if self.recursive: + walked_dir = list(walk(self.path)) + else: + walked_dir = [next(walk(self.path))] + walked_dir.sort() + for dirname, subdirs, fnames in walked_dir: + result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes) + return '\n'.join(result_lines) + + +class Code(TextDisplayObject): + """Display syntax-highlighted source code. + + This uses Pygments to highlight the code for HTML and Latex output. + + Parameters + ---------- + data : str + The code as a string + url : str + A URL to fetch the code from + filename : str + A local filename to load the code from + language : str + The short name of a Pygments lexer to use for highlighting. + If not specified, it will guess the lexer based on the filename + or the code. Available lexers: http://pygments.org/docs/lexers/ + """ + def __init__(self, data=None, url=None, filename=None, language=None): + self.language = language + super().__init__(data=data, url=url, filename=filename) + + def _get_lexer(self): + if self.language: + from pygments.lexers import get_lexer_by_name + return get_lexer_by_name(self.language) + elif self.filename: + from pygments.lexers import get_lexer_for_filename + return get_lexer_for_filename(self.filename) + else: + from pygments.lexers import guess_lexer + return guess_lexer(self.data) + + def __repr__(self): + return self.data + + def _repr_html_(self): + from pygments import highlight + from pygments.formatters import HtmlFormatter + fmt = HtmlFormatter() + style = ''.format(fmt.get_style_defs('.output_html')) + return style + highlight(self.data, self._get_lexer(), fmt) + + def _repr_latex_(self): + from pygments import highlight + from pygments.formatters import LatexFormatter + return highlight(self.data, self._get_lexer(), LatexFormatter()) diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/editorhooks.py b/myenv/lib/python3.12/site-packages/IPython/lib/editorhooks.py new file mode 100644 index 00000000..d8bd6ac8 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/editorhooks.py @@ -0,0 +1,127 @@ +""" 'editor' hooks for common editors that work well with ipython + +They should honor the line number argument, at least. + +Contributions are *very* welcome. +""" + +import os +import shlex +import subprocess +import sys + +from IPython import get_ipython +from IPython.core.error import TryNext +from IPython.utils import py3compat + + +def install_editor(template, wait=False): + """Installs the editor that is called by IPython for the %edit magic. + + This overrides the default editor, which is generally set by your EDITOR + environment variable or is notepad (windows) or vi (linux). By supplying a + template string `run_template`, you can control how the editor is invoked + by IPython -- (e.g. the format in which it accepts command line options) + + Parameters + ---------- + template : basestring + run_template acts as a template for how your editor is invoked by + the shell. It should contain '{filename}', which will be replaced on + invocation with the file name, and '{line}', $line by line number + (or 0) to invoke the file with. + wait : bool + If `wait` is true, wait until the user presses enter before returning, + to facilitate non-blocking editors that exit immediately after + the call. + """ + + # not all editors support $line, so we'll leave out this check + # for substitution in ['$file', '$line']: + # if not substitution in run_template: + # raise ValueError(('run_template should contain %s' + # ' for string substitution. You supplied "%s"' % (substitution, + # run_template))) + + def call_editor(self, filename, line=0): + if line is None: + line = 0 + cmd = template.format(filename=shlex.quote(filename), line=line) + print(">", cmd) + # shlex.quote doesn't work right on Windows, but it does after splitting + if sys.platform.startswith('win'): + cmd = shlex.split(cmd) + proc = subprocess.Popen(cmd, shell=True) + if proc.wait() != 0: + raise TryNext() + if wait: + py3compat.input("Press Enter when done editing:") + + get_ipython().set_hook('editor', call_editor) + get_ipython().editor = template + + +# in these, exe is always the path/name of the executable. Useful +# if you don't have the editor directory in your path +def komodo(exe=u'komodo'): + """ Activestate Komodo [Edit] """ + install_editor(exe + u' -l {line} {filename}', wait=True) + + +def scite(exe=u"scite"): + """ SciTE or Sc1 """ + install_editor(exe + u' {filename} -goto:{line}') + + +def notepadplusplus(exe=u'notepad++'): + """ Notepad++ http://notepad-plus.sourceforge.net """ + install_editor(exe + u' -n{line} {filename}') + + +def jed(exe=u'jed'): + """ JED, the lightweight emacsish editor """ + install_editor(exe + u' +{line} {filename}') + + +def idle(exe=u'idle'): + """ Idle, the editor bundled with python + + Parameters + ---------- + exe : str, None + If none, should be pretty smart about finding the executable. + """ + if exe is None: + import idlelib + p = os.path.dirname(idlelib.__filename__) + # i'm not sure if this actually works. Is this idle.py script + # guaranteed to be executable? + exe = os.path.join(p, 'idle.py') + install_editor(exe + u' {filename}') + + +def mate(exe=u'mate'): + """ TextMate, the missing editor""" + # wait=True is not required since we're using the -w flag to mate + install_editor(exe + u' -w -l {line} {filename}') + + +# ########################################## +# these are untested, report any problems +# ########################################## + + +def emacs(exe=u'emacs'): + install_editor(exe + u' +{line} {filename}') + + +def gnuclient(exe=u'gnuclient'): + install_editor(exe + u' -nw +{line} {filename}') + + +def crimson_editor(exe=u'cedt.exe'): + install_editor(exe + u' /L:{line} {filename}') + + +def kate(exe=u'kate'): + install_editor(exe + u' -u -l {line} {filename}') diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/guisupport.py b/myenv/lib/python3.12/site-packages/IPython/lib/guisupport.py new file mode 100644 index 00000000..4d532d0f --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/guisupport.py @@ -0,0 +1,155 @@ +# coding: utf-8 +""" +Support for creating GUI apps and starting event loops. + +IPython's GUI integration allows interactive plotting and GUI usage in IPython +session. IPython has two different types of GUI integration: + +1. The terminal based IPython supports GUI event loops through Python's + PyOS_InputHook. PyOS_InputHook is a hook that Python calls periodically + whenever raw_input is waiting for a user to type code. We implement GUI + support in the terminal by setting PyOS_InputHook to a function that + iterates the event loop for a short while. It is important to note that + in this situation, the real GUI event loop is NOT run in the normal + manner, so you can't use the normal means to detect that it is running. +2. In the two process IPython kernel/frontend, the GUI event loop is run in + the kernel. In this case, the event loop is run in the normal manner by + calling the function or method of the GUI toolkit that starts the event + loop. + +In addition to starting the GUI event loops in one of these two ways, IPython +will *always* create an appropriate GUI application object when GUi +integration is enabled. + +If you want your GUI apps to run in IPython you need to do two things: + +1. Test to see if there is already an existing main application object. If + there is, you should use it. If there is not an existing application object + you should create one. +2. Test to see if the GUI event loop is running. If it is, you should not + start it. If the event loop is not running you may start it. + +This module contains functions for each toolkit that perform these things +in a consistent manner. Because of how PyOS_InputHook runs the event loop +you cannot detect if the event loop is running using the traditional calls +(such as ``wx.GetApp.IsMainLoopRunning()`` in wxPython). If PyOS_InputHook is +set These methods will return a false negative. That is, they will say the +event loop is not running, when is actually is. To work around this limitation +we proposed the following informal protocol: + +* Whenever someone starts the event loop, they *must* set the ``_in_event_loop`` + attribute of the main application object to ``True``. This should be done + regardless of how the event loop is actually run. +* Whenever someone stops the event loop, they *must* set the ``_in_event_loop`` + attribute of the main application object to ``False``. +* If you want to see if the event loop is running, you *must* use ``hasattr`` + to see if ``_in_event_loop`` attribute has been set. If it is set, you + *must* use its value. If it has not been set, you can query the toolkit + in the normal manner. +* If you want GUI support and no one else has created an application or + started the event loop you *must* do this. We don't want projects to + attempt to defer these things to someone else if they themselves need it. + +The functions below implement this logic for each GUI toolkit. If you need +to create custom application subclasses, you will likely have to modify this +code for your own purposes. This code can be copied into your own project +so you don't have to depend on IPython. + +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from IPython.core.getipython import get_ipython + +#----------------------------------------------------------------------------- +# wx +#----------------------------------------------------------------------------- + +def get_app_wx(*args, **kwargs): + """Create a new wx app or return an exiting one.""" + import wx + app = wx.GetApp() + if app is None: + if 'redirect' not in kwargs: + kwargs['redirect'] = False + app = wx.PySimpleApp(*args, **kwargs) + return app + +def is_event_loop_running_wx(app=None): + """Is the wx event loop running.""" + # New way: check attribute on shell instance + ip = get_ipython() + if ip is not None: + if ip.active_eventloop and ip.active_eventloop == 'wx': + return True + # Fall through to checking the application, because Wx has a native way + # to check if the event loop is running, unlike Qt. + + # Old way: check Wx application + if app is None: + app = get_app_wx() + if hasattr(app, '_in_event_loop'): + return app._in_event_loop + else: + return app.IsMainLoopRunning() + +def start_event_loop_wx(app=None): + """Start the wx event loop in a consistent manner.""" + if app is None: + app = get_app_wx() + if not is_event_loop_running_wx(app): + app._in_event_loop = True + app.MainLoop() + app._in_event_loop = False + else: + app._in_event_loop = True + +#----------------------------------------------------------------------------- +# Qt +#----------------------------------------------------------------------------- + +def get_app_qt4(*args, **kwargs): + """Create a new Qt app or return an existing one.""" + from IPython.external.qt_for_kernel import QtGui + app = QtGui.QApplication.instance() + if app is None: + if not args: + args = ([""],) + app = QtGui.QApplication(*args, **kwargs) + return app + +def is_event_loop_running_qt4(app=None): + """Is the qt event loop running.""" + # New way: check attribute on shell instance + ip = get_ipython() + if ip is not None: + return ip.active_eventloop and ip.active_eventloop.startswith('qt') + + # Old way: check attribute on QApplication singleton + if app is None: + app = get_app_qt4([""]) + if hasattr(app, '_in_event_loop'): + return app._in_event_loop + else: + # Does qt provide a other way to detect this? + return False + +def start_event_loop_qt4(app=None): + """Start the qt event loop in a consistent manner.""" + if app is None: + app = get_app_qt4([""]) + if not is_event_loop_running_qt4(app): + app._in_event_loop = True + app.exec_() + app._in_event_loop = False + else: + app._in_event_loop = True + +#----------------------------------------------------------------------------- +# Tk +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# gtk +#----------------------------------------------------------------------------- diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/latextools.py b/myenv/lib/python3.12/site-packages/IPython/lib/latextools.py new file mode 100644 index 00000000..7bea589a --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/latextools.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- +"""Tools for handling LaTeX.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from io import BytesIO, open +import os +import tempfile +import shutil +import subprocess +from base64 import encodebytes +import textwrap + +from pathlib import Path + +from IPython.utils.process import find_cmd, FindCmdError +from traitlets.config import get_config +from traitlets.config.configurable import SingletonConfigurable +from traitlets import List, Bool, Unicode + + +class LaTeXTool(SingletonConfigurable): + """An object to store configuration of the LaTeX tool.""" + def _config_default(self): + return get_config() + + backends = List( + Unicode(), ["matplotlib", "dvipng"], + help="Preferred backend to draw LaTeX math equations. " + "Backends in the list are checked one by one and the first " + "usable one is used. Note that `matplotlib` backend " + "is usable only for inline style equations. To draw " + "display style equations, `dvipng` backend must be specified. ", + # It is a List instead of Enum, to make configuration more + # flexible. For example, to use matplotlib mainly but dvipng + # for display style, the default ["matplotlib", "dvipng"] can + # be used. To NOT use dvipng so that other repr such as + # unicode pretty printing is used, you can use ["matplotlib"]. + ).tag(config=True) + + use_breqn = Bool( + True, + help="Use breqn.sty to automatically break long equations. " + "This configuration takes effect only for dvipng backend.", + ).tag(config=True) + + packages = List( + ['amsmath', 'amsthm', 'amssymb', 'bm'], + help="A list of packages to use for dvipng backend. " + "'breqn' will be automatically appended when use_breqn=True.", + ).tag(config=True) + + preamble = Unicode( + help="Additional preamble to use when generating LaTeX source " + "for dvipng backend.", + ).tag(config=True) + + +def latex_to_png( + s: str, encode=False, backend=None, wrap=False, color="Black", scale=1.0 +): + """Render a LaTeX string to PNG. + + Parameters + ---------- + s : str + The raw string containing valid inline LaTeX. + encode : bool, optional + Should the PNG data base64 encoded to make it JSON'able. + backend : {matplotlib, dvipng} + Backend for producing PNG data. + wrap : bool + If true, Automatically wrap `s` as a LaTeX equation. + color : string + Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB + format, e.g. '#AA20FA'. + scale : float + Scale factor for the resulting PNG. + None is returned when the backend cannot be used. + + """ + assert isinstance(s, str) + allowed_backends = LaTeXTool.instance().backends + if backend is None: + backend = allowed_backends[0] + if backend not in allowed_backends: + return None + if backend == 'matplotlib': + f = latex_to_png_mpl + elif backend == 'dvipng': + f = latex_to_png_dvipng + if color.startswith('#'): + # Convert hex RGB color to LaTeX RGB color. + if len(color) == 7: + try: + color = "RGB {}".format(" ".join([str(int(x, 16)) for x in + textwrap.wrap(color[1:], 2)])) + except ValueError as e: + raise ValueError('Invalid color specification {}.'.format(color)) from e + else: + raise ValueError('Invalid color specification {}.'.format(color)) + else: + raise ValueError('No such backend {0}'.format(backend)) + bin_data = f(s, wrap, color, scale) + if encode and bin_data: + bin_data = encodebytes(bin_data) + return bin_data + + +def latex_to_png_mpl(s, wrap, color='Black', scale=1.0): + try: + from matplotlib import figure, font_manager, mathtext + from matplotlib.backends import backend_agg + from pyparsing import ParseFatalException + except ImportError: + return None + + # mpl mathtext doesn't support display math, force inline + s = s.replace('$$', '$') + if wrap: + s = u'${0}$'.format(s) + + try: + prop = font_manager.FontProperties(size=12) + dpi = 120 * scale + buffer = BytesIO() + + # Adapted from mathtext.math_to_image + parser = mathtext.MathTextParser("path") + width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop) + fig = figure.Figure(figsize=(width / 72, height / 72)) + fig.text(0, depth / height, s, fontproperties=prop, color=color) + backend_agg.FigureCanvasAgg(fig) + fig.savefig(buffer, dpi=dpi, format="png", transparent=True) + return buffer.getvalue() + except (ValueError, RuntimeError, ParseFatalException): + return None + + +def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0): + try: + find_cmd('latex') + find_cmd('dvipng') + except FindCmdError: + return None + + startupinfo = None + if os.name == "nt": + # prevent popup-windows + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + + try: + workdir = Path(tempfile.mkdtemp()) + tmpfile = "tmp.tex" + dvifile = "tmp.dvi" + outfile = "tmp.png" + + with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f: + f.writelines(genelatex(s, wrap)) + + subprocess.check_call( + ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile], + cwd=workdir, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + startupinfo=startupinfo, + ) + + resolution = round(150 * scale) + subprocess.check_call( + [ + "dvipng", + "-T", + "tight", + "-D", + str(resolution), + "-z", + "9", + "-bg", + "Transparent", + "-o", + outfile, + dvifile, + "-fg", + color, + ], + cwd=workdir, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + startupinfo=startupinfo, + ) + + with workdir.joinpath(outfile).open("rb") as f: + return f.read() + except subprocess.CalledProcessError: + return None + finally: + shutil.rmtree(workdir) + + +def kpsewhich(filename): + """Invoke kpsewhich command with an argument `filename`.""" + try: + find_cmd("kpsewhich") + proc = subprocess.Popen( + ["kpsewhich", filename], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (stdout, stderr) = proc.communicate() + return stdout.strip().decode('utf8', 'replace') + except FindCmdError: + pass + + +def genelatex(body, wrap): + """Generate LaTeX document for dvipng backend.""" + lt = LaTeXTool.instance() + breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty") + yield r'\documentclass{article}' + packages = lt.packages + if breqn: + packages = packages + ['breqn'] + for pack in packages: + yield r'\usepackage{{{0}}}'.format(pack) + yield r'\pagestyle{empty}' + if lt.preamble: + yield lt.preamble + yield r'\begin{document}' + if breqn: + yield r'\begin{dmath*}' + yield body + yield r'\end{dmath*}' + elif wrap: + yield u'$${0}$$'.format(body) + else: + yield body + yield u'\\end{document}' + + +_data_uri_template_png = u"""%s""" + +def latex_to_html(s, alt='image'): + """Render LaTeX to HTML with embedded PNG data using data URIs. + + Parameters + ---------- + s : str + The raw string containing valid inline LateX. + alt : str + The alt text to use for the HTML. + """ + base64_data = latex_to_png(s, encode=True).decode('ascii') + if base64_data: + return _data_uri_template_png % (base64_data, alt) + + diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/lexers.py b/myenv/lib/python3.12/site-packages/IPython/lib/lexers.py new file mode 100644 index 00000000..9aa3433a --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/lexers.py @@ -0,0 +1,540 @@ +# -*- coding: utf-8 -*- +""" +Defines a variety of Pygments lexers for highlighting IPython code. + +This includes: + + IPythonLexer, IPython3Lexer + Lexers for pure IPython (python + magic/shell commands) + + IPythonPartialTracebackLexer, IPythonTracebackLexer + Supports 2.x and 3.x via keyword `python3`. The partial traceback + lexer reads everything but the Python code appearing in a traceback. + The full lexer combines the partial lexer with an IPython lexer. + + IPythonConsoleLexer + A lexer for IPython console sessions, with support for tracebacks. + + IPyLexer + A friendly lexer which examines the first line of text and from it, + decides whether to use an IPython lexer or an IPython console lexer. + This is probably the only lexer that needs to be explicitly added + to Pygments. + +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2013, the IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +# Standard library +import re + +# Third party +from pygments.lexers import ( + BashLexer, HtmlLexer, JavascriptLexer, RubyLexer, PerlLexer, PythonLexer, + Python3Lexer, TexLexer) +from pygments.lexer import ( + Lexer, DelegatingLexer, RegexLexer, do_insertions, bygroups, using, +) +from pygments.token import ( + Generic, Keyword, Literal, Name, Operator, Other, Text, Error, +) +from pygments.util import get_bool_opt + +# Local + +line_re = re.compile('.*?\n') + +__all__ = ['build_ipy_lexer', 'IPython3Lexer', 'IPythonLexer', + 'IPythonPartialTracebackLexer', 'IPythonTracebackLexer', + 'IPythonConsoleLexer', 'IPyLexer'] + + +def build_ipy_lexer(python3): + """Builds IPython lexers depending on the value of `python3`. + + The lexer inherits from an appropriate Python lexer and then adds + information about IPython specific keywords (i.e. magic commands, + shell commands, etc.) + + Parameters + ---------- + python3 : bool + If `True`, then build an IPython lexer from a Python 3 lexer. + + """ + # It would be nice to have a single IPython lexer class which takes + # a boolean `python3`. But since there are two Python lexer classes, + # we will also have two IPython lexer classes. + if python3: + PyLexer = Python3Lexer + name = 'IPython3' + aliases = ['ipython3'] + doc = """IPython3 Lexer""" + else: + PyLexer = PythonLexer + name = 'IPython' + aliases = ['ipython2', 'ipython'] + doc = """IPython Lexer""" + + ipython_tokens = [ + (r'(?s)(\s*)(%%capture)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%debug)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?is)(\s*)(%%html)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(HtmlLexer))), + (r'(?s)(\s*)(%%javascript)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(JavascriptLexer))), + (r'(?s)(\s*)(%%js)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(JavascriptLexer))), + (r'(?s)(\s*)(%%latex)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(TexLexer))), + (r'(?s)(\s*)(%%perl)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PerlLexer))), + (r'(?s)(\s*)(%%prun)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%pypy)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%python)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%python2)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PythonLexer))), + (r'(?s)(\s*)(%%python3)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(Python3Lexer))), + (r'(?s)(\s*)(%%ruby)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(RubyLexer))), + (r'(?s)(\s*)(%%time)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%timeit)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%writefile)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r'(?s)(\s*)(%%file)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(PyLexer))), + (r"(?s)(\s*)(%%)(\w+)(.*)", bygroups(Text, Operator, Keyword, Text)), + (r'(?s)(^\s*)(%%!)([^\n]*\n)(.*)', bygroups(Text, Operator, Text, using(BashLexer))), + (r"(%%?)(\w+)(\?\??)$", bygroups(Operator, Keyword, Operator)), + (r"\b(\?\??)(\s*)$", bygroups(Operator, Text)), + (r'(%)(sx|sc|system)(.*)(\n)', bygroups(Operator, Keyword, + using(BashLexer), Text)), + (r'(%)(\w+)(.*\n)', bygroups(Operator, Keyword, Text)), + (r'^(!!)(.+)(\n)', bygroups(Operator, using(BashLexer), Text)), + (r'(!)(?!=)(.+)(\n)', bygroups(Operator, using(BashLexer), Text)), + (r'^(\s*)(\?\??)(\s*%{0,2}[\w\.\*]*)', bygroups(Text, Operator, Text)), + (r'(\s*%{0,2}[\w\.\*]*)(\?\??)(\s*)$', bygroups(Text, Operator, Text)), + ] + + tokens = PyLexer.tokens.copy() + tokens['root'] = ipython_tokens + tokens['root'] + + attrs = {'name': name, 'aliases': aliases, 'filenames': [], + '__doc__': doc, 'tokens': tokens} + + return type(name, (PyLexer,), attrs) + + +IPython3Lexer = build_ipy_lexer(python3=True) +IPythonLexer = build_ipy_lexer(python3=False) + + +class IPythonPartialTracebackLexer(RegexLexer): + """ + Partial lexer for IPython tracebacks. + + Handles all the non-python output. + + """ + name = 'IPython Partial Traceback' + + tokens = { + 'root': [ + # Tracebacks for syntax errors have a different style. + # For both types of tracebacks, we mark the first line with + # Generic.Traceback. For syntax errors, we mark the filename + # as we mark the filenames for non-syntax tracebacks. + # + # These two regexps define how IPythonConsoleLexer finds a + # traceback. + # + ## Non-syntax traceback + (r'^(\^C)?(-+\n)', bygroups(Error, Generic.Traceback)), + ## Syntax traceback + (r'^( File)(.*)(, line )(\d+\n)', + bygroups(Generic.Traceback, Name.Namespace, + Generic.Traceback, Literal.Number.Integer)), + + # (Exception Identifier)(Whitespace)(Traceback Message) + (r'(?u)(^[^\d\W]\w*)(\s*)(Traceback.*?\n)', + bygroups(Name.Exception, Generic.Whitespace, Text)), + # (Module/Filename)(Text)(Callee)(Function Signature) + # Better options for callee and function signature? + (r'(.*)( in )(.*)(\(.*\)\n)', + bygroups(Name.Namespace, Text, Name.Entity, Name.Tag)), + # Regular line: (Whitespace)(Line Number)(Python Code) + (r'(\s*?)(\d+)(.*?\n)', + bygroups(Generic.Whitespace, Literal.Number.Integer, Other)), + # Emphasized line: (Arrow)(Line Number)(Python Code) + # Using Exception token so arrow color matches the Exception. + (r'(-*>?\s?)(\d+)(.*?\n)', + bygroups(Name.Exception, Literal.Number.Integer, Other)), + # (Exception Identifier)(Message) + (r'(?u)(^[^\d\W]\w*)(:.*?\n)', + bygroups(Name.Exception, Text)), + # Tag everything else as Other, will be handled later. + (r'.*\n', Other), + ], + } + + +class IPythonTracebackLexer(DelegatingLexer): + """ + IPython traceback lexer. + + For doctests, the tracebacks can be snipped as much as desired with the + exception to the lines that designate a traceback. For non-syntax error + tracebacks, this is the line of hyphens. For syntax error tracebacks, + this is the line which lists the File and line number. + + """ + # The lexer inherits from DelegatingLexer. The "root" lexer is an + # appropriate IPython lexer, which depends on the value of the boolean + # `python3`. First, we parse with the partial IPython traceback lexer. + # Then, any code marked with the "Other" token is delegated to the root + # lexer. + # + name = 'IPython Traceback' + aliases = ['ipythontb'] + + def __init__(self, **options): + """ + A subclass of `DelegatingLexer` which delegates to the appropriate to either IPyLexer, + IPythonPartialTracebackLexer. + """ + # note we need a __init__ doc, as otherwise it inherits the doc from the super class + # which will fail the documentation build as it references section of the pygments docs that + # do not exists when building IPython's docs. + self.python3 = get_bool_opt(options, 'python3', False) + if self.python3: + self.aliases = ['ipython3tb'] + else: + self.aliases = ['ipython2tb', 'ipythontb'] + + if self.python3: + IPyLexer = IPython3Lexer + else: + IPyLexer = IPythonLexer + + DelegatingLexer.__init__(self, IPyLexer, + IPythonPartialTracebackLexer, **options) + +class IPythonConsoleLexer(Lexer): + """ + An IPython console lexer for IPython code-blocks and doctests, such as: + + .. code-block:: rst + + .. code-block:: ipythonconsole + + In [1]: a = 'foo' + + In [2]: a + Out[2]: 'foo' + + In [3]: print(a) + foo + + + Support is also provided for IPython exceptions: + + .. code-block:: rst + + .. code-block:: ipythonconsole + + In [1]: raise Exception + Traceback (most recent call last): + ... + Exception + + """ + name = 'IPython console session' + aliases = ['ipythonconsole'] + mimetypes = ['text/x-ipython-console'] + + # The regexps used to determine what is input and what is output. + # The default prompts for IPython are: + # + # in = 'In [#]: ' + # continuation = ' .D.: ' + # template = 'Out[#]: ' + # + # Where '#' is the 'prompt number' or 'execution count' and 'D' + # D is a number of dots matching the width of the execution count + # + in1_regex = r'In \[[0-9]+\]: ' + in2_regex = r' \.\.+\.: ' + out_regex = r'Out\[[0-9]+\]: ' + + #: The regex to determine when a traceback starts. + ipytb_start = re.compile(r'^(\^C)?(-+\n)|^( File)(.*)(, line )(\d+\n)') + + def __init__(self, **options): + """Initialize the IPython console lexer. + + Parameters + ---------- + python3 : bool + If `True`, then the console inputs are parsed using a Python 3 + lexer. Otherwise, they are parsed using a Python 2 lexer. + in1_regex : RegexObject + The compiled regular expression used to detect the start + of inputs. Although the IPython configuration setting may have a + trailing whitespace, do not include it in the regex. If `None`, + then the default input prompt is assumed. + in2_regex : RegexObject + The compiled regular expression used to detect the continuation + of inputs. Although the IPython configuration setting may have a + trailing whitespace, do not include it in the regex. If `None`, + then the default input prompt is assumed. + out_regex : RegexObject + The compiled regular expression used to detect outputs. If `None`, + then the default output prompt is assumed. + + """ + self.python3 = get_bool_opt(options, 'python3', False) + if self.python3: + self.aliases = ['ipython3console'] + else: + self.aliases = ['ipython2console', 'ipythonconsole'] + + in1_regex = options.get('in1_regex', self.in1_regex) + in2_regex = options.get('in2_regex', self.in2_regex) + out_regex = options.get('out_regex', self.out_regex) + + # So that we can work with input and output prompts which have been + # rstrip'd (possibly by editors) we also need rstrip'd variants. If + # we do not do this, then such prompts will be tagged as 'output'. + # The reason can't just use the rstrip'd variants instead is because + # we want any whitespace associated with the prompt to be inserted + # with the token. This allows formatted code to be modified so as hide + # the appearance of prompts, with the whitespace included. One example + # use of this is in copybutton.js from the standard lib Python docs. + in1_regex_rstrip = in1_regex.rstrip() + '\n' + in2_regex_rstrip = in2_regex.rstrip() + '\n' + out_regex_rstrip = out_regex.rstrip() + '\n' + + # Compile and save them all. + attrs = ['in1_regex', 'in2_regex', 'out_regex', + 'in1_regex_rstrip', 'in2_regex_rstrip', 'out_regex_rstrip'] + for attr in attrs: + self.__setattr__(attr, re.compile(locals()[attr])) + + Lexer.__init__(self, **options) + + if self.python3: + pylexer = IPython3Lexer + tblexer = IPythonTracebackLexer + else: + pylexer = IPythonLexer + tblexer = IPythonTracebackLexer + + self.pylexer = pylexer(**options) + self.tblexer = tblexer(**options) + + self.reset() + + def reset(self): + self.mode = 'output' + self.index = 0 + self.buffer = u'' + self.insertions = [] + + def buffered_tokens(self): + """ + Generator of unprocessed tokens after doing insertions and before + changing to a new state. + + """ + if self.mode == 'output': + tokens = [(0, Generic.Output, self.buffer)] + elif self.mode == 'input': + tokens = self.pylexer.get_tokens_unprocessed(self.buffer) + else: # traceback + tokens = self.tblexer.get_tokens_unprocessed(self.buffer) + + for i, t, v in do_insertions(self.insertions, tokens): + # All token indexes are relative to the buffer. + yield self.index + i, t, v + + # Clear it all + self.index += len(self.buffer) + self.buffer = u'' + self.insertions = [] + + def get_mci(self, line): + """ + Parses the line and returns a 3-tuple: (mode, code, insertion). + + `mode` is the next mode (or state) of the lexer, and is always equal + to 'input', 'output', or 'tb'. + + `code` is a portion of the line that should be added to the buffer + corresponding to the next mode and eventually lexed by another lexer. + For example, `code` could be Python code if `mode` were 'input'. + + `insertion` is a 3-tuple (index, token, text) representing an + unprocessed "token" that will be inserted into the stream of tokens + that are created from the buffer once we change modes. This is usually + the input or output prompt. + + In general, the next mode depends on current mode and on the contents + of `line`. + + """ + # To reduce the number of regex match checks, we have multiple + # 'if' blocks instead of 'if-elif' blocks. + + # Check for possible end of input + in2_match = self.in2_regex.match(line) + in2_match_rstrip = self.in2_regex_rstrip.match(line) + if (in2_match and in2_match.group().rstrip() == line.rstrip()) or \ + in2_match_rstrip: + end_input = True + else: + end_input = False + if end_input and self.mode != 'tb': + # Only look for an end of input when not in tb mode. + # An ellipsis could appear within the traceback. + mode = 'output' + code = u'' + insertion = (0, Generic.Prompt, line) + return mode, code, insertion + + # Check for output prompt + out_match = self.out_regex.match(line) + out_match_rstrip = self.out_regex_rstrip.match(line) + if out_match or out_match_rstrip: + mode = 'output' + if out_match: + idx = out_match.end() + else: + idx = out_match_rstrip.end() + code = line[idx:] + # Use the 'heading' token for output. We cannot use Generic.Error + # since it would conflict with exceptions. + insertion = (0, Generic.Heading, line[:idx]) + return mode, code, insertion + + + # Check for input or continuation prompt (non stripped version) + in1_match = self.in1_regex.match(line) + if in1_match or (in2_match and self.mode != 'tb'): + # New input or when not in tb, continued input. + # We do not check for continued input when in tb since it is + # allowable to replace a long stack with an ellipsis. + mode = 'input' + if in1_match: + idx = in1_match.end() + else: # in2_match + idx = in2_match.end() + code = line[idx:] + insertion = (0, Generic.Prompt, line[:idx]) + return mode, code, insertion + + # Check for input or continuation prompt (stripped version) + in1_match_rstrip = self.in1_regex_rstrip.match(line) + if in1_match_rstrip or (in2_match_rstrip and self.mode != 'tb'): + # New input or when not in tb, continued input. + # We do not check for continued input when in tb since it is + # allowable to replace a long stack with an ellipsis. + mode = 'input' + if in1_match_rstrip: + idx = in1_match_rstrip.end() + else: # in2_match + idx = in2_match_rstrip.end() + code = line[idx:] + insertion = (0, Generic.Prompt, line[:idx]) + return mode, code, insertion + + # Check for traceback + if self.ipytb_start.match(line): + mode = 'tb' + code = line + insertion = None + return mode, code, insertion + + # All other stuff... + if self.mode in ('input', 'output'): + # We assume all other text is output. Multiline input that + # does not use the continuation marker cannot be detected. + # For example, the 3 in the following is clearly output: + # + # In [1]: print(3) + # 3 + # + # But the following second line is part of the input: + # + # In [2]: while True: + # print(True) + # + # In both cases, the 2nd line will be 'output'. + # + mode = 'output' + else: + mode = 'tb' + + code = line + insertion = None + + return mode, code, insertion + + def get_tokens_unprocessed(self, text): + self.reset() + for match in line_re.finditer(text): + line = match.group() + mode, code, insertion = self.get_mci(line) + + if mode != self.mode: + # Yield buffered tokens before transitioning to new mode. + for token in self.buffered_tokens(): + yield token + self.mode = mode + + if insertion: + self.insertions.append((len(self.buffer), [insertion])) + self.buffer += code + + for token in self.buffered_tokens(): + yield token + +class IPyLexer(Lexer): + r""" + Primary lexer for all IPython-like code. + + This is a simple helper lexer. If the first line of the text begins with + "In \[[0-9]+\]:", then the entire text is parsed with an IPython console + lexer. If not, then the entire text is parsed with an IPython lexer. + + The goal is to reduce the number of lexers that are registered + with Pygments. + + """ + name = 'IPy session' + aliases = ['ipy'] + + def __init__(self, **options): + """ + Create a new IPyLexer instance which dispatch to either an + IPythonCOnsoleLexer (if In prompts are present) or and IPythonLexer (if + In prompts are not present). + """ + # init docstring is necessary for docs not to fail to build do to parent + # docs referenceing a section in pygments docs. + self.python3 = get_bool_opt(options, 'python3', False) + if self.python3: + self.aliases = ['ipy3'] + else: + self.aliases = ['ipy2', 'ipy'] + + Lexer.__init__(self, **options) + + self.IPythonLexer = IPythonLexer(**options) + self.IPythonConsoleLexer = IPythonConsoleLexer(**options) + + def get_tokens_unprocessed(self, text): + # Search for the input prompt anywhere...this allows code blocks to + # begin with comments as well. + if re.match(r'.*(In \[[0-9]+\]:)', text.strip(), re.DOTALL): + lex = self.IPythonConsoleLexer + else: + lex = self.IPythonLexer + for token in lex.get_tokens_unprocessed(text): + yield token + diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/pretty.py b/myenv/lib/python3.12/site-packages/IPython/lib/pretty.py new file mode 100644 index 00000000..a232e4ed --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/pretty.py @@ -0,0 +1,961 @@ +""" +Python advanced pretty printer. This pretty printer is intended to +replace the old `pprint` python module which does not allow developers +to provide their own pretty print callbacks. + +This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`. + + +Example Usage +------------- + +To directly print the representation of an object use `pprint`:: + + from pretty import pprint + pprint(complex_object) + +To get a string of the output use `pretty`:: + + from pretty import pretty + string = pretty(complex_object) + + +Extending +--------- + +The pretty library allows developers to add pretty printing rules for their +own objects. This process is straightforward. All you have to do is to +add a `_repr_pretty_` method to your object and call the methods on the +pretty printer passed:: + + class MyObject(object): + + def _repr_pretty_(self, p, cycle): + ... + +Here's an example for a class with a simple constructor:: + + class MySimpleObject: + + def __init__(self, a, b, *, c=None): + self.a = a + self.b = b + self.c = c + + def _repr_pretty_(self, p, cycle): + ctor = CallExpression.factory(self.__class__.__name__) + if self.c is None: + p.pretty(ctor(a, b)) + else: + p.pretty(ctor(a, b, c=c)) + +Here is an example implementation of a `_repr_pretty_` method for a list +subclass:: + + class MyList(list): + + def _repr_pretty_(self, p, cycle): + if cycle: + p.text('MyList(...)') + else: + with p.group(8, 'MyList([', '])'): + for idx, item in enumerate(self): + if idx: + p.text(',') + p.breakable() + p.pretty(item) + +The `cycle` parameter is `True` if pretty detected a cycle. You *have* to +react to that or the result is an infinite loop. `p.text()` just adds +non breaking text to the output, `p.breakable()` either adds a whitespace +or breaks here. If you pass it an argument it's used instead of the +default space. `p.pretty` prettyprints another object using the pretty print +method. + +The first parameter to the `group` function specifies the extra indentation +of the next line. In this example the next item will either be on the same +line (if the items are short enough) or aligned with the right edge of the +opening bracket of `MyList`. + +If you just want to indent something you can use the group function +without open / close parameters. You can also use this code:: + + with p.indent(2): + ... + +Inheritance diagram: + +.. inheritance-diagram:: IPython.lib.pretty + :parts: 3 + +:copyright: 2007 by Armin Ronacher. + Portions (c) 2009 by Robert Kern. +:license: BSD License. +""" + +from contextlib import contextmanager +import datetime +import os +import re +import sys +import types +from collections import deque +from inspect import signature +from io import StringIO +from warnings import warn + +from IPython.utils.decorators import undoc +from IPython.utils.py3compat import PYPY + +from typing import Dict + +__all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter', + 'for_type', 'for_type_by_name', 'RawText', 'RawStringLiteral', 'CallExpression'] + + +MAX_SEQ_LENGTH = 1000 +_re_pattern_type = type(re.compile('')) + +def _safe_getattr(obj, attr, default=None): + """Safe version of getattr. + + Same as getattr, but will return ``default`` on any Exception, + rather than raising. + """ + try: + return getattr(obj, attr, default) + except Exception: + return default + +@undoc +class CUnicodeIO(StringIO): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + warn(("CUnicodeIO is deprecated since IPython 6.0. " + "Please use io.StringIO instead."), + DeprecationWarning, stacklevel=2) + +def _sorted_for_pprint(items): + """ + Sort the given items for pretty printing. Since some predictable + sorting is better than no sorting at all, we sort on the string + representation if normal sorting fails. + """ + items = list(items) + try: + return sorted(items) + except Exception: + try: + return sorted(items, key=str) + except Exception: + return items + +def pretty(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH): + """ + Pretty print the object's representation. + """ + stream = StringIO() + printer = RepresentationPrinter(stream, verbose, max_width, newline, max_seq_length=max_seq_length) + printer.pretty(obj) + printer.flush() + return stream.getvalue() + + +def pprint(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH): + """ + Like `pretty` but print to stdout. + """ + printer = RepresentationPrinter(sys.stdout, verbose, max_width, newline, max_seq_length=max_seq_length) + printer.pretty(obj) + printer.flush() + sys.stdout.write(newline) + sys.stdout.flush() + +class _PrettyPrinterBase(object): + + @contextmanager + def indent(self, indent): + """with statement support for indenting/dedenting.""" + self.indentation += indent + try: + yield + finally: + self.indentation -= indent + + @contextmanager + def group(self, indent=0, open='', close=''): + """like begin_group / end_group but for the with statement.""" + self.begin_group(indent, open) + try: + yield + finally: + self.end_group(indent, close) + +class PrettyPrinter(_PrettyPrinterBase): + """ + Baseclass for the `RepresentationPrinter` prettyprinter that is used to + generate pretty reprs of objects. Contrary to the `RepresentationPrinter` + this printer knows nothing about the default pprinters or the `_repr_pretty_` + callback method. + """ + + def __init__(self, output, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH): + self.output = output + self.max_width = max_width + self.newline = newline + self.max_seq_length = max_seq_length + self.output_width = 0 + self.buffer_width = 0 + self.buffer = deque() + + root_group = Group(0) + self.group_stack = [root_group] + self.group_queue = GroupQueue(root_group) + self.indentation = 0 + + def _break_one_group(self, group): + while group.breakables: + x = self.buffer.popleft() + self.output_width = x.output(self.output, self.output_width) + self.buffer_width -= x.width + while self.buffer and isinstance(self.buffer[0], Text): + x = self.buffer.popleft() + self.output_width = x.output(self.output, self.output_width) + self.buffer_width -= x.width + + def _break_outer_groups(self): + while self.max_width < self.output_width + self.buffer_width: + group = self.group_queue.deq() + if not group: + return + self._break_one_group(group) + + def text(self, obj): + """Add literal text to the output.""" + width = len(obj) + if self.buffer: + text = self.buffer[-1] + if not isinstance(text, Text): + text = Text() + self.buffer.append(text) + text.add(obj, width) + self.buffer_width += width + self._break_outer_groups() + else: + self.output.write(obj) + self.output_width += width + + def breakable(self, sep=' '): + """ + Add a breakable separator to the output. This does not mean that it + will automatically break here. If no breaking on this position takes + place the `sep` is inserted which default to one space. + """ + width = len(sep) + group = self.group_stack[-1] + if group.want_break: + self.flush() + self.output.write(self.newline) + self.output.write(' ' * self.indentation) + self.output_width = self.indentation + self.buffer_width = 0 + else: + self.buffer.append(Breakable(sep, width, self)) + self.buffer_width += width + self._break_outer_groups() + + def break_(self): + """ + Explicitly insert a newline into the output, maintaining correct indentation. + """ + group = self.group_queue.deq() + if group: + self._break_one_group(group) + self.flush() + self.output.write(self.newline) + self.output.write(' ' * self.indentation) + self.output_width = self.indentation + self.buffer_width = 0 + + + def begin_group(self, indent=0, open=''): + """ + Begin a group. + The first parameter specifies the indentation for the next line (usually + the width of the opening text), the second the opening text. All + parameters are optional. + """ + if open: + self.text(open) + group = Group(self.group_stack[-1].depth + 1) + self.group_stack.append(group) + self.group_queue.enq(group) + self.indentation += indent + + def _enumerate(self, seq): + """like enumerate, but with an upper limit on the number of items""" + for idx, x in enumerate(seq): + if self.max_seq_length and idx >= self.max_seq_length: + self.text(',') + self.breakable() + self.text('...') + return + yield idx, x + + def end_group(self, dedent=0, close=''): + """End a group. See `begin_group` for more details.""" + self.indentation -= dedent + group = self.group_stack.pop() + if not group.breakables: + self.group_queue.remove(group) + if close: + self.text(close) + + def flush(self): + """Flush data that is left in the buffer.""" + for data in self.buffer: + self.output_width += data.output(self.output, self.output_width) + self.buffer.clear() + self.buffer_width = 0 + + +def _get_mro(obj_class): + """ Get a reasonable method resolution order of a class and its superclasses + for both old-style and new-style classes. + """ + if not hasattr(obj_class, '__mro__'): + # Old-style class. Mix in object to make a fake new-style class. + try: + obj_class = type(obj_class.__name__, (obj_class, object), {}) + except TypeError: + # Old-style extension type that does not descend from object. + # FIXME: try to construct a more thorough MRO. + mro = [obj_class] + else: + mro = obj_class.__mro__[1:-1] + else: + mro = obj_class.__mro__ + return mro + + +class RepresentationPrinter(PrettyPrinter): + """ + Special pretty printer that has a `pretty` method that calls the pretty + printer for a python object. + + This class stores processing data on `self` so you must *never* use + this class in a threaded environment. Always lock it or reinstanciate + it. + + Instances also have a verbose flag callbacks can access to control their + output. For example the default instance repr prints all attributes and + methods that are not prefixed by an underscore if the printer is in + verbose mode. + """ + + def __init__(self, output, verbose=False, max_width=79, newline='\n', + singleton_pprinters=None, type_pprinters=None, deferred_pprinters=None, + max_seq_length=MAX_SEQ_LENGTH): + + PrettyPrinter.__init__(self, output, max_width, newline, max_seq_length=max_seq_length) + self.verbose = verbose + self.stack = [] + if singleton_pprinters is None: + singleton_pprinters = _singleton_pprinters.copy() + self.singleton_pprinters = singleton_pprinters + if type_pprinters is None: + type_pprinters = _type_pprinters.copy() + self.type_pprinters = type_pprinters + if deferred_pprinters is None: + deferred_pprinters = _deferred_type_pprinters.copy() + self.deferred_pprinters = deferred_pprinters + + def pretty(self, obj): + """Pretty print the given object.""" + obj_id = id(obj) + cycle = obj_id in self.stack + self.stack.append(obj_id) + self.begin_group() + try: + obj_class = _safe_getattr(obj, '__class__', None) or type(obj) + # First try to find registered singleton printers for the type. + try: + printer = self.singleton_pprinters[obj_id] + except (TypeError, KeyError): + pass + else: + return printer(obj, self, cycle) + # Next walk the mro and check for either: + # 1) a registered printer + # 2) a _repr_pretty_ method + for cls in _get_mro(obj_class): + if cls in self.type_pprinters: + # printer registered in self.type_pprinters + return self.type_pprinters[cls](obj, self, cycle) + else: + # deferred printer + printer = self._in_deferred_types(cls) + if printer is not None: + return printer(obj, self, cycle) + else: + # Finally look for special method names. + # Some objects automatically create any requested + # attribute. Try to ignore most of them by checking for + # callability. + if '_repr_pretty_' in cls.__dict__: + meth = cls._repr_pretty_ + if callable(meth): + return meth(obj, self, cycle) + if ( + cls is not object + # check if cls defines __repr__ + and "__repr__" in cls.__dict__ + # check if __repr__ is callable. + # Note: we need to test getattr(cls, '__repr__') + # instead of cls.__dict__['__repr__'] + # in order to work with descriptors like partialmethod, + and callable(_safe_getattr(cls, "__repr__", None)) + ): + return _repr_pprint(obj, self, cycle) + + return _default_pprint(obj, self, cycle) + finally: + self.end_group() + self.stack.pop() + + def _in_deferred_types(self, cls): + """ + Check if the given class is specified in the deferred type registry. + + Returns the printer from the registry if it exists, and None if the + class is not in the registry. Successful matches will be moved to the + regular type registry for future use. + """ + mod = _safe_getattr(cls, '__module__', None) + name = _safe_getattr(cls, '__name__', None) + key = (mod, name) + printer = None + if key in self.deferred_pprinters: + # Move the printer over to the regular registry. + printer = self.deferred_pprinters.pop(key) + self.type_pprinters[cls] = printer + return printer + + +class Printable(object): + + def output(self, stream, output_width): + return output_width + + +class Text(Printable): + + def __init__(self): + self.objs = [] + self.width = 0 + + def output(self, stream, output_width): + for obj in self.objs: + stream.write(obj) + return output_width + self.width + + def add(self, obj, width): + self.objs.append(obj) + self.width += width + + +class Breakable(Printable): + + def __init__(self, seq, width, pretty): + self.obj = seq + self.width = width + self.pretty = pretty + self.indentation = pretty.indentation + self.group = pretty.group_stack[-1] + self.group.breakables.append(self) + + def output(self, stream, output_width): + self.group.breakables.popleft() + if self.group.want_break: + stream.write(self.pretty.newline) + stream.write(' ' * self.indentation) + return self.indentation + if not self.group.breakables: + self.pretty.group_queue.remove(self.group) + stream.write(self.obj) + return output_width + self.width + + +class Group(Printable): + + def __init__(self, depth): + self.depth = depth + self.breakables = deque() + self.want_break = False + + +class GroupQueue(object): + + def __init__(self, *groups): + self.queue = [] + for group in groups: + self.enq(group) + + def enq(self, group): + depth = group.depth + while depth > len(self.queue) - 1: + self.queue.append([]) + self.queue[depth].append(group) + + def deq(self): + for stack in self.queue: + for idx, group in enumerate(reversed(stack)): + if group.breakables: + del stack[idx] + group.want_break = True + return group + for group in stack: + group.want_break = True + del stack[:] + + def remove(self, group): + try: + self.queue[group.depth].remove(group) + except ValueError: + pass + + +class RawText: + """ Object such that ``p.pretty(RawText(value))`` is the same as ``p.text(value)``. + + An example usage of this would be to show a list as binary numbers, using + ``p.pretty([RawText(bin(i)) for i in integers])``. + """ + def __init__(self, value): + self.value = value + + def _repr_pretty_(self, p, cycle): + p.text(self.value) + + +class CallExpression: + """ Object which emits a line-wrapped call expression in the form `__name(*args, **kwargs)` """ + def __init__(__self, __name, *args, **kwargs): + # dunders are to avoid clashes with kwargs, as python's name managing + # will kick in. + self = __self + self.name = __name + self.args = args + self.kwargs = kwargs + + @classmethod + def factory(cls, name): + def inner(*args, **kwargs): + return cls(name, *args, **kwargs) + return inner + + def _repr_pretty_(self, p, cycle): + # dunders are to avoid clashes with kwargs, as python's name managing + # will kick in. + + started = False + def new_item(): + nonlocal started + if started: + p.text(",") + p.breakable() + started = True + + prefix = self.name + "(" + with p.group(len(prefix), prefix, ")"): + for arg in self.args: + new_item() + p.pretty(arg) + for arg_name, arg in self.kwargs.items(): + new_item() + arg_prefix = arg_name + "=" + with p.group(len(arg_prefix), arg_prefix): + p.pretty(arg) + + +class RawStringLiteral: + """ Wrapper that shows a string with a `r` prefix """ + def __init__(self, value): + self.value = value + + def _repr_pretty_(self, p, cycle): + base_repr = repr(self.value) + if base_repr[:1] in 'uU': + base_repr = base_repr[1:] + prefix = 'ur' + else: + prefix = 'r' + base_repr = prefix + base_repr.replace('\\\\', '\\') + p.text(base_repr) + + +def _default_pprint(obj, p, cycle): + """ + The default print function. Used if an object does not provide one and + it's none of the builtin objects. + """ + klass = _safe_getattr(obj, '__class__', None) or type(obj) + if _safe_getattr(klass, '__repr__', None) is not object.__repr__: + # A user-provided repr. Find newlines and replace them with p.break_() + _repr_pprint(obj, p, cycle) + return + p.begin_group(1, '<') + p.pretty(klass) + p.text(' at 0x%x' % id(obj)) + if cycle: + p.text(' ...') + elif p.verbose: + first = True + for key in dir(obj): + if not key.startswith('_'): + try: + value = getattr(obj, key) + except AttributeError: + continue + if isinstance(value, types.MethodType): + continue + if not first: + p.text(',') + p.breakable() + p.text(key) + p.text('=') + step = len(key) + 1 + p.indentation += step + p.pretty(value) + p.indentation -= step + first = False + p.end_group(1, '>') + + +def _seq_pprinter_factory(start, end): + """ + Factory that returns a pprint function useful for sequences. Used by + the default pprint for tuples and lists. + """ + def inner(obj, p, cycle): + if cycle: + return p.text(start + '...' + end) + step = len(start) + p.begin_group(step, start) + for idx, x in p._enumerate(obj): + if idx: + p.text(',') + p.breakable() + p.pretty(x) + if len(obj) == 1 and isinstance(obj, tuple): + # Special case for 1-item tuples. + p.text(',') + p.end_group(step, end) + return inner + + +def _set_pprinter_factory(start, end): + """ + Factory that returns a pprint function useful for sets and frozensets. + """ + def inner(obj, p, cycle): + if cycle: + return p.text(start + '...' + end) + if len(obj) == 0: + # Special case. + p.text(type(obj).__name__ + '()') + else: + step = len(start) + p.begin_group(step, start) + # Like dictionary keys, we will try to sort the items if there aren't too many + if not (p.max_seq_length and len(obj) >= p.max_seq_length): + items = _sorted_for_pprint(obj) + else: + items = obj + for idx, x in p._enumerate(items): + if idx: + p.text(',') + p.breakable() + p.pretty(x) + p.end_group(step, end) + return inner + + +def _dict_pprinter_factory(start, end): + """ + Factory that returns a pprint function used by the default pprint of + dicts and dict proxies. + """ + def inner(obj, p, cycle): + if cycle: + return p.text('{...}') + step = len(start) + p.begin_group(step, start) + keys = obj.keys() + for idx, key in p._enumerate(keys): + if idx: + p.text(',') + p.breakable() + p.pretty(key) + p.text(': ') + p.pretty(obj[key]) + p.end_group(step, end) + return inner + + +def _super_pprint(obj, p, cycle): + """The pprint for the super type.""" + p.begin_group(8, '') + + + +class _ReFlags: + def __init__(self, value): + self.value = value + + def _repr_pretty_(self, p, cycle): + done_one = False + for flag in ( + "IGNORECASE", + "LOCALE", + "MULTILINE", + "DOTALL", + "UNICODE", + "VERBOSE", + "DEBUG", + ): + if self.value & getattr(re, flag): + if done_one: + p.text('|') + p.text('re.' + flag) + done_one = True + + +def _re_pattern_pprint(obj, p, cycle): + """The pprint function for regular expression patterns.""" + re_compile = CallExpression.factory('re.compile') + if obj.flags: + p.pretty(re_compile(RawStringLiteral(obj.pattern), _ReFlags(obj.flags))) + else: + p.pretty(re_compile(RawStringLiteral(obj.pattern))) + + +def _types_simplenamespace_pprint(obj, p, cycle): + """The pprint function for types.SimpleNamespace.""" + namespace = CallExpression.factory('namespace') + if cycle: + p.pretty(namespace(RawText("..."))) + else: + p.pretty(namespace(**obj.__dict__)) + + +def _type_pprint(obj, p, cycle): + """The pprint for classes and types.""" + # Heap allocated types might not have the module attribute, + # and others may set it to None. + + # Checks for a __repr__ override in the metaclass. Can't compare the + # type(obj).__repr__ directly because in PyPy the representation function + # inherited from type isn't the same type.__repr__ + if [m for m in _get_mro(type(obj)) if "__repr__" in vars(m)][:1] != [type]: + _repr_pprint(obj, p, cycle) + return + + mod = _safe_getattr(obj, '__module__', None) + try: + name = obj.__qualname__ + if not isinstance(name, str): + # This can happen if the type implements __qualname__ as a property + # or other descriptor in Python 2. + raise Exception("Try __name__") + except Exception: + name = obj.__name__ + if not isinstance(name, str): + name = '' + + if mod in (None, '__builtin__', 'builtins', 'exceptions'): + p.text(name) + else: + p.text(mod + '.' + name) + + +def _repr_pprint(obj, p, cycle): + """A pprint that just redirects to the normal repr function.""" + # Find newlines and replace them with p.break_() + output = repr(obj) + lines = output.splitlines() + with p.group(): + for idx, output_line in enumerate(lines): + if idx: + p.break_() + p.text(output_line) + + +def _function_pprint(obj, p, cycle): + """Base pprint for all functions and builtin functions.""" + name = _safe_getattr(obj, '__qualname__', obj.__name__) + mod = obj.__module__ + if mod and mod not in ('__builtin__', 'builtins', 'exceptions'): + name = mod + '.' + name + try: + func_def = name + str(signature(obj)) + except ValueError: + func_def = name + p.text('' % func_def) + + +def _exception_pprint(obj, p, cycle): + """Base pprint for all exceptions.""" + name = getattr(obj.__class__, '__qualname__', obj.__class__.__name__) + if obj.__class__.__module__ not in ('exceptions', 'builtins'): + name = '%s.%s' % (obj.__class__.__module__, name) + + p.pretty(CallExpression(name, *getattr(obj, 'args', ()))) + + +#: the exception base +_exception_base: type +try: + _exception_base = BaseException +except NameError: + _exception_base = Exception + + +#: printers for builtin types +_type_pprinters = { + int: _repr_pprint, + float: _repr_pprint, + str: _repr_pprint, + tuple: _seq_pprinter_factory('(', ')'), + list: _seq_pprinter_factory('[', ']'), + dict: _dict_pprinter_factory('{', '}'), + set: _set_pprinter_factory('{', '}'), + frozenset: _set_pprinter_factory('frozenset({', '})'), + super: _super_pprint, + _re_pattern_type: _re_pattern_pprint, + type: _type_pprint, + types.FunctionType: _function_pprint, + types.BuiltinFunctionType: _function_pprint, + types.MethodType: _repr_pprint, + types.SimpleNamespace: _types_simplenamespace_pprint, + datetime.datetime: _repr_pprint, + datetime.timedelta: _repr_pprint, + _exception_base: _exception_pprint +} + +# render os.environ like a dict +_env_type = type(os.environ) +# future-proof in case os.environ becomes a plain dict? +if _env_type is not dict: + _type_pprinters[_env_type] = _dict_pprinter_factory('environ{', '}') + +_type_pprinters[types.MappingProxyType] = _dict_pprinter_factory("mappingproxy({", "})") +_type_pprinters[slice] = _repr_pprint + +_type_pprinters[range] = _repr_pprint +_type_pprinters[bytes] = _repr_pprint + +#: printers for types specified by name +_deferred_type_pprinters: Dict = {} + + +def for_type(typ, func): + """ + Add a pretty printer for a given type. + """ + oldfunc = _type_pprinters.get(typ, None) + if func is not None: + # To support easy restoration of old pprinters, we need to ignore Nones. + _type_pprinters[typ] = func + return oldfunc + +def for_type_by_name(type_module, type_name, func): + """ + Add a pretty printer for a type specified by the module and name of a type + rather than the type object itself. + """ + key = (type_module, type_name) + oldfunc = _deferred_type_pprinters.get(key, None) + if func is not None: + # To support easy restoration of old pprinters, we need to ignore Nones. + _deferred_type_pprinters[key] = func + return oldfunc + + +#: printers for the default singletons +_singleton_pprinters = dict.fromkeys(map(id, [None, True, False, Ellipsis, + NotImplemented]), _repr_pprint) + + +def _defaultdict_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + else: + p.pretty(cls_ctor(obj.default_factory, dict(obj))) + +def _ordereddict_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + elif len(obj): + p.pretty(cls_ctor(list(obj.items()))) + else: + p.pretty(cls_ctor()) + +def _deque_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + elif obj.maxlen is not None: + p.pretty(cls_ctor(list(obj), maxlen=obj.maxlen)) + else: + p.pretty(cls_ctor(list(obj))) + +def _counter_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + elif len(obj): + p.pretty(cls_ctor(dict(obj.most_common()))) + else: + p.pretty(cls_ctor()) + + +def _userlist_pprint(obj, p, cycle): + cls_ctor = CallExpression.factory(obj.__class__.__name__) + if cycle: + p.pretty(cls_ctor(RawText("..."))) + else: + p.pretty(cls_ctor(obj.data)) + + +for_type_by_name('collections', 'defaultdict', _defaultdict_pprint) +for_type_by_name('collections', 'OrderedDict', _ordereddict_pprint) +for_type_by_name('collections', 'deque', _deque_pprint) +for_type_by_name('collections', 'Counter', _counter_pprint) +for_type_by_name("collections", "UserList", _userlist_pprint) + +if __name__ == '__main__': + from random import randrange + class Foo(object): + def __init__(self): + self.foo = 1 + self.bar = re.compile(r'\s+') + self.blub = dict.fromkeys(range(30), randrange(1, 40)) + self.hehe = 23424.234234 + self.list = ["blub", "blah", self] + + def get_foo(self): + print("foo") + + pprint(Foo(), verbose=True) diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/tests/__init__.py b/myenv/lib/python3.12/site-packages/IPython/lib/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/tests/test.wav b/myenv/lib/python3.12/site-packages/IPython/lib/tests/test.wav new file mode 100644 index 00000000..aa74fc5b Binary files /dev/null and b/myenv/lib/python3.12/site-packages/IPython/lib/tests/test.wav differ diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_backgroundjobs.py b/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_backgroundjobs.py new file mode 100644 index 00000000..fc76ff19 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_backgroundjobs.py @@ -0,0 +1,85 @@ +"""Tests for pylab tools module. +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2011, the IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +# Stdlib imports +import time + +# Our own imports +from IPython.lib import backgroundjobs as bg + +#----------------------------------------------------------------------------- +# Globals and constants +#----------------------------------------------------------------------------- +t_short = 0.0001 # very short interval to wait on jobs + +#----------------------------------------------------------------------------- +# Local utilities +#----------------------------------------------------------------------------- +def sleeper(interval=t_short, *a, **kw): + args = dict(interval=interval, + other_args=a, + kw_args=kw) + time.sleep(interval) + return args + +def crasher(interval=t_short, *a, **kw): + time.sleep(interval) + raise Exception("Dead job with interval %s" % interval) + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +def test_result(): + """Test job submission and result retrieval""" + jobs = bg.BackgroundJobManager() + j = jobs.new(sleeper) + j.join() + assert j.result["interval"] == t_short + + +def test_flush(): + """Test job control""" + jobs = bg.BackgroundJobManager() + j = jobs.new(sleeper) + j.join() + assert len(jobs.completed) == 1 + assert len(jobs.dead) == 0 + jobs.flush() + assert len(jobs.completed) == 0 + + +def test_dead(): + """Test control of dead jobs""" + jobs = bg.BackgroundJobManager() + j = jobs.new(crasher) + j.join() + assert len(jobs.completed) == 0 + assert len(jobs.dead) == 1 + jobs.flush() + assert len(jobs.dead) == 0 + + +def test_longer(): + """Test control of longer-running jobs""" + jobs = bg.BackgroundJobManager() + # Sleep for long enough for the following two checks to still report the + # job as running, but not so long that it makes the test suite noticeably + # slower. + j = jobs.new(sleeper, 0.1) + assert len(jobs.running) == 1 + assert len(jobs.completed) == 0 + j.join() + assert len(jobs.running) == 0 + assert len(jobs.completed) == 1 diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_clipboard.py b/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_clipboard.py new file mode 100644 index 00000000..6597c946 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_clipboard.py @@ -0,0 +1,20 @@ +from IPython.core.error import TryNext +from IPython.lib.clipboard import ClipboardEmpty +from IPython.testing.decorators import skip_if_no_x11 + + +@skip_if_no_x11 +def test_clipboard_get(): + # Smoketest for clipboard access - we can't easily guarantee that the + # clipboard is accessible and has something on it, but this tries to + # exercise the relevant code anyway. + try: + a = get_ipython().hooks.clipboard_get() + except ClipboardEmpty: + # Nothing in clipboard to get + pass + except TryNext: + # No clipboard access API available + pass + else: + assert isinstance(a, str) diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_deepreload.py b/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_deepreload.py new file mode 100644 index 00000000..5da606cb --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_deepreload.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +"""Test suite for the deepreload module.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import types +from pathlib import Path + +import pytest +from tempfile import TemporaryDirectory + +from IPython.lib.deepreload import modules_reloading +from IPython.lib.deepreload import reload as dreload +from IPython.utils.syspathcontext import prepended_to_syspath + + +def test_deepreload(): + "Test that dreload does deep reloads and skips excluded modules." + with TemporaryDirectory() as tmpdir: + with prepended_to_syspath(tmpdir): + tmpdirpath = Path(tmpdir) + with open(tmpdirpath / "A.py", "w", encoding="utf-8") as f: + f.write("class Object:\n pass\nok = True\n") + with open(tmpdirpath / "B.py", "w", encoding="utf-8") as f: + f.write("import A\nassert A.ok, 'we are fine'\n") + import A + import B + + # Test that A is not reloaded. + obj = A.Object() + dreload(B, exclude=["A"]) + assert isinstance(obj, A.Object) is True + + # Test that an import failure will not blow-up us. + A.ok = False + with pytest.raises(AssertionError, match="we are fine"): + dreload(B, exclude=["A"]) + assert len(modules_reloading) == 0 + assert not A.ok + + # Test that A is reloaded. + obj = A.Object() + A.ok = False + dreload(B) + assert A.ok + assert isinstance(obj, A.Object) is False + + +def test_not_module(): + pytest.raises(TypeError, dreload, "modulename") + + +def test_not_in_sys_modules(): + fake_module = types.ModuleType("fake_module") + with pytest.raises(ImportError, match="not in sys.modules"): + dreload(fake_module) diff --git a/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_display.py b/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_display.py new file mode 100644 index 00000000..f5ed34c9 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/IPython/lib/tests/test_display.py @@ -0,0 +1,272 @@ +"""Tests for IPython.lib.display. + +""" +#----------------------------------------------------------------------------- +# Copyright (c) 2012, the IPython Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +from tempfile import NamedTemporaryFile, mkdtemp +from os.path import split, join as pjoin, dirname +import pathlib +from unittest import TestCase, mock +import struct +import wave +from io import BytesIO + +# Third-party imports +import pytest + +try: + import numpy +except ImportError: + pass + +# Our own imports +from IPython.lib import display + +from IPython.testing.decorators import skipif_not_numpy + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +#-------------------------- +# FileLink tests +#-------------------------- + +def test_instantiation_FileLink(): + """FileLink: Test class can be instantiated""" + fl = display.FileLink('example.txt') + # TODO: remove if when only Python >= 3.6 is supported + fl = display.FileLink(pathlib.PurePath('example.txt')) + +def test_warning_on_non_existent_path_FileLink(): + """FileLink: Calling _repr_html_ on non-existent files returns a warning""" + fl = display.FileLink("example.txt") + assert fl._repr_html_().startswith("Path (example.txt)") + + +def test_existing_path_FileLink(): + """FileLink: Calling _repr_html_ functions as expected on existing filepath + """ + tf = NamedTemporaryFile() + fl = display.FileLink(tf.name) + actual = fl._repr_html_() + expected = "%s
" % (tf.name, tf.name) + assert actual == expected + + +def test_existing_path_FileLink_repr(): + """FileLink: Calling repr() functions as expected on existing filepath + """ + tf = NamedTemporaryFile() + fl = display.FileLink(tf.name) + actual = repr(fl) + expected = tf.name + assert actual == expected + + +def test_error_on_directory_to_FileLink(): + """FileLink: Raises error when passed directory + """ + td = mkdtemp() + pytest.raises(ValueError, display.FileLink, td) + +#-------------------------- +# FileLinks tests +#-------------------------- + +def test_instantiation_FileLinks(): + """FileLinks: Test class can be instantiated + """ + fls = display.FileLinks('example') + +def test_warning_on_non_existent_path_FileLinks(): + """FileLinks: Calling _repr_html_ on non-existent files returns a warning""" + fls = display.FileLinks("example") + assert fls._repr_html_().startswith("Path (example)") + + +def test_existing_path_FileLinks(): + """FileLinks: Calling _repr_html_ functions as expected on existing dir + """ + td = mkdtemp() + tf1 = NamedTemporaryFile(dir=td) + tf2 = NamedTemporaryFile(dir=td) + fl = display.FileLinks(td) + actual = fl._repr_html_() + actual = actual.split('\n') + actual.sort() + # the links should always have forward slashes, even on windows, so replace + # backslashes with forward slashes here + expected = ["%s/
" % td, + "  %s
" %\ + (tf2.name.replace("\\","/"),split(tf2.name)[1]), + "  %s
" %\ + (tf1.name.replace("\\","/"),split(tf1.name)[1])] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_existing_path_FileLinks_alt_formatter(): + """FileLinks: Calling _repr_html_ functions as expected w/ an alt formatter + """ + td = mkdtemp() + tf1 = NamedTemporaryFile(dir=td) + tf2 = NamedTemporaryFile(dir=td) + def fake_formatter(dirname,fnames,included_suffixes): + return ["hello","world"] + fl = display.FileLinks(td,notebook_display_formatter=fake_formatter) + actual = fl._repr_html_() + actual = actual.split('\n') + actual.sort() + expected = ["hello","world"] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_existing_path_FileLinks_repr(): + """FileLinks: Calling repr() functions as expected on existing directory """ + td = mkdtemp() + tf1 = NamedTemporaryFile(dir=td) + tf2 = NamedTemporaryFile(dir=td) + fl = display.FileLinks(td) + actual = repr(fl) + actual = actual.split('\n') + actual.sort() + expected = ['%s/' % td, ' %s' % split(tf1.name)[1],' %s' % split(tf2.name)[1]] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_existing_path_FileLinks_repr_alt_formatter(): + """FileLinks: Calling repr() functions as expected w/ alt formatter + """ + td = mkdtemp() + tf1 = NamedTemporaryFile(dir=td) + tf2 = NamedTemporaryFile(dir=td) + def fake_formatter(dirname,fnames,included_suffixes): + return ["hello","world"] + fl = display.FileLinks(td,terminal_display_formatter=fake_formatter) + actual = repr(fl) + actual = actual.split('\n') + actual.sort() + expected = ["hello","world"] + expected.sort() + # We compare the sorted list of links here as that's more reliable + assert actual == expected + + +def test_error_on_file_to_FileLinks(): + """FileLinks: Raises error when passed file + """ + td = mkdtemp() + tf1 = NamedTemporaryFile(dir=td) + pytest.raises(ValueError, display.FileLinks, tf1.name) + + +def test_recursive_FileLinks(): + """FileLinks: Does not recurse when recursive=False + """ + td = mkdtemp() + tf = NamedTemporaryFile(dir=td) + subtd = mkdtemp(dir=td) + subtf = NamedTemporaryFile(dir=subtd) + fl = display.FileLinks(td) + actual = str(fl) + actual = actual.split('\n') + assert len(actual) == 4, actual + fl = display.FileLinks(td, recursive=False) + actual = str(fl) + actual = actual.split('\n') + assert len(actual) == 2, actual + +def test_audio_from_file(): + path = pjoin(dirname(__file__), 'test.wav') + display.Audio(filename=path) + +class TestAudioDataWithNumpy(TestCase): + + @skipif_not_numpy + def test_audio_from_numpy_array(self): + test_tone = get_test_tone() + audio = display.Audio(test_tone, rate=44100) + assert len(read_wav(audio.data)) == len(test_tone) + + @skipif_not_numpy + def test_audio_from_list(self): + test_tone = get_test_tone() + audio = display.Audio(list(test_tone), rate=44100) + assert len(read_wav(audio.data)) == len(test_tone) + + @skipif_not_numpy + def test_audio_from_numpy_array_without_rate_raises(self): + self.assertRaises(ValueError, display.Audio, get_test_tone()) + + @skipif_not_numpy + def test_audio_data_normalization(self): + expected_max_value = numpy.iinfo(numpy.int16).max + for scale in [1, 0.5, 2]: + audio = display.Audio(get_test_tone(scale), rate=44100) + actual_max_value = numpy.max(numpy.abs(read_wav(audio.data))) + assert actual_max_value == expected_max_value + + @skipif_not_numpy + def test_audio_data_without_normalization(self): + max_int16 = numpy.iinfo(numpy.int16).max + for scale in [1, 0.5, 0.2]: + test_tone = get_test_tone(scale) + test_tone_max_abs = numpy.max(numpy.abs(test_tone)) + expected_max_value = int(max_int16 * test_tone_max_abs) + audio = display.Audio(test_tone, rate=44100, normalize=False) + actual_max_value = numpy.max(numpy.abs(read_wav(audio.data))) + assert actual_max_value == expected_max_value + + def test_audio_data_without_normalization_raises_for_invalid_data(self): + self.assertRaises( + ValueError, + lambda: display.Audio([1.001], rate=44100, normalize=False)) + self.assertRaises( + ValueError, + lambda: display.Audio([-1.001], rate=44100, normalize=False)) + +def simulate_numpy_not_installed(): + try: + import numpy + return mock.patch('numpy.array', mock.MagicMock(side_effect=ImportError)) + except ModuleNotFoundError: + return lambda x:x + +@simulate_numpy_not_installed() +class TestAudioDataWithoutNumpy(TestAudioDataWithNumpy): + # All tests from `TestAudioDataWithNumpy` are inherited. + + @skipif_not_numpy + def test_audio_raises_for_nested_list(self): + stereo_signal = [list(get_test_tone())] * 2 + self.assertRaises(TypeError, lambda: display.Audio(stereo_signal, rate=44100)) + + +@skipif_not_numpy +def get_test_tone(scale=1): + return numpy.sin(2 * numpy.pi * 440 * numpy.linspace(0, 1, 44100)) * scale + +def read_wav(data): + with wave.open(BytesIO(data)) as wave_file: + wave_data = wave_file.readframes(wave_file.getnframes()) + num_samples = wave_file.getnframes() * wave_file.getnchannels() + return struct.unpack('<%sh' % num_samples, wave_data) + +def test_code_from_file(): + c = display.Code(filename=__file__) + assert c._repr_html_().startswith(' + + +

%(title)s

+ +''' + +DOC_HEADER_EXTERNALCSS = '''\ + + + + + %(title)s + + + + +

%(title)s

+ +''' + +DOC_FOOTER = '''\ + + +''' + + +class HtmlFormatter(Formatter): + r""" + Format tokens as HTML 4 ```` tags. By default, the content is enclosed + in a ``
`` tag, itself wrapped in a ``
`` tag (but see the `nowrap` option). + The ``
``'s CSS class can be set by the `cssclass` option. + + If the `linenos` option is set to ``"table"``, the ``
`` is
+    additionally wrapped inside a ```` which has one row and two
+    cells: one containing the line numbers and one containing the code.
+    Example:
+
+    .. sourcecode:: html
+
+        
+
+ + +
+
1
+            2
+
+
def foo(bar):
+              pass
+            
+
+ + (whitespace added to improve clarity). + + A list of lines can be specified using the `hl_lines` option to make these + lines highlighted (as of Pygments 0.11). + + With the `full` option, a complete HTML 4 document is output, including + the style definitions inside a `` + {% else %} + {{ head | safe }} + {% endif %} +{% if not embed %} + + +{% endif %} +{{ body | safe }} +{% for diagram in diagrams %} +
+

{{ diagram.title }}

+
{{ diagram.text }}
+
+ {{ diagram.svg }} +
+
+{% endfor %} +{% if not embed %} + + +{% endif %} +""" + +template = Template(jinja2_template_source) + +# Note: ideally this would be a dataclass, but we're supporting Python 3.5+ so we can't do this yet +NamedDiagram = NamedTuple( + "NamedDiagram", + [("name", str), ("diagram", typing.Optional[railroad.DiagramItem]), ("index", int)], +) +""" +A simple structure for associating a name with a railroad diagram +""" + +T = TypeVar("T") + + +class EachItem(railroad.Group): + """ + Custom railroad item to compose a: + - Group containing a + - OneOrMore containing a + - Choice of the elements in the Each + with the group label indicating that all must be matched + """ + + all_label = "[ALL]" + + def __init__(self, *items): + choice_item = railroad.Choice(len(items) - 1, *items) + one_or_more_item = railroad.OneOrMore(item=choice_item) + super().__init__(one_or_more_item, label=self.all_label) + + +class AnnotatedItem(railroad.Group): + """ + Simple subclass of Group that creates an annotation label + """ + + def __init__(self, label: str, item): + super().__init__(item=item, label="[{}]".format(label) if label else label) + + +class EditablePartial(Generic[T]): + """ + Acts like a functools.partial, but can be edited. In other words, it represents a type that hasn't yet been + constructed. + """ + + # We need this here because the railroad constructors actually transform the data, so can't be called until the + # entire tree is assembled + + def __init__(self, func: Callable[..., T], args: list, kwargs: dict): + self.func = func + self.args = args + self.kwargs = kwargs + + @classmethod + def from_call(cls, func: Callable[..., T], *args, **kwargs) -> "EditablePartial[T]": + """ + If you call this function in the same way that you would call the constructor, it will store the arguments + as you expect. For example EditablePartial.from_call(Fraction, 1, 3)() == Fraction(1, 3) + """ + return EditablePartial(func=func, args=list(args), kwargs=kwargs) + + @property + def name(self): + return self.kwargs["name"] + + def __call__(self) -> T: + """ + Evaluate the partial and return the result + """ + args = self.args.copy() + kwargs = self.kwargs.copy() + + # This is a helpful hack to allow you to specify varargs parameters (e.g. *args) as keyword args (e.g. + # args=['list', 'of', 'things']) + arg_spec = inspect.getfullargspec(self.func) + if arg_spec.varargs in self.kwargs: + args += kwargs.pop(arg_spec.varargs) + + return self.func(*args, **kwargs) + + +def railroad_to_html(diagrams: List[NamedDiagram], embed=False, **kwargs) -> str: + """ + Given a list of NamedDiagram, produce a single HTML string that visualises those diagrams + :params kwargs: kwargs to be passed in to the template + """ + data = [] + for diagram in diagrams: + if diagram.diagram is None: + continue + io = StringIO() + try: + css = kwargs.get('css') + diagram.diagram.writeStandalone(io.write, css=css) + except AttributeError: + diagram.diagram.writeSvg(io.write) + title = diagram.name + if diagram.index == 0: + title += " (root)" + data.append({"title": title, "text": "", "svg": io.getvalue()}) + + return template.render(diagrams=data, embed=embed, **kwargs) + + +def resolve_partial(partial: "EditablePartial[T]") -> T: + """ + Recursively resolves a collection of Partials into whatever type they are + """ + if isinstance(partial, EditablePartial): + partial.args = resolve_partial(partial.args) + partial.kwargs = resolve_partial(partial.kwargs) + return partial() + elif isinstance(partial, list): + return [resolve_partial(x) for x in partial] + elif isinstance(partial, dict): + return {key: resolve_partial(x) for key, x in partial.items()} + else: + return partial + + +def to_railroad( + element: pyparsing.ParserElement, + diagram_kwargs: typing.Optional[dict] = None, + vertical: int = 3, + show_results_names: bool = False, + show_groups: bool = False, +) -> List[NamedDiagram]: + """ + Convert a pyparsing element tree into a list of diagrams. This is the recommended entrypoint to diagram + creation if you want to access the Railroad tree before it is converted to HTML + :param element: base element of the parser being diagrammed + :param diagram_kwargs: kwargs to pass to the Diagram() constructor + :param vertical: (optional) - int - limit at which number of alternatives should be + shown vertically instead of horizontally + :param show_results_names - bool to indicate whether results name annotations should be + included in the diagram + :param show_groups - bool to indicate whether groups should be highlighted with an unlabeled + surrounding box + """ + # Convert the whole tree underneath the root + lookup = ConverterState(diagram_kwargs=diagram_kwargs or {}) + _to_diagram_element( + element, + lookup=lookup, + parent=None, + vertical=vertical, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + root_id = id(element) + # Convert the root if it hasn't been already + if root_id in lookup: + if not element.customName: + lookup[root_id].name = "" + lookup[root_id].mark_for_extraction(root_id, lookup, force=True) + + # Now that we're finished, we can convert from intermediate structures into Railroad elements + diags = list(lookup.diagrams.values()) + if len(diags) > 1: + # collapse out duplicate diags with the same name + seen = set() + deduped_diags = [] + for d in diags: + # don't extract SkipTo elements, they are uninformative as subdiagrams + if d.name == "...": + continue + if d.name is not None and d.name not in seen: + seen.add(d.name) + deduped_diags.append(d) + resolved = [resolve_partial(partial) for partial in deduped_diags] + else: + # special case - if just one diagram, always display it, even if + # it has no name + resolved = [resolve_partial(partial) for partial in diags] + return sorted(resolved, key=lambda diag: diag.index) + + +def _should_vertical( + specification: int, exprs: Iterable[pyparsing.ParserElement] +) -> bool: + """ + Returns true if we should return a vertical list of elements + """ + if specification is None: + return False + else: + return len(_visible_exprs(exprs)) >= specification + + +class ElementState: + """ + State recorded for an individual pyparsing Element + """ + + # Note: this should be a dataclass, but we have to support Python 3.5 + def __init__( + self, + element: pyparsing.ParserElement, + converted: EditablePartial, + parent: EditablePartial, + number: int, + name: str = None, + parent_index: typing.Optional[int] = None, + ): + #: The pyparsing element that this represents + self.element: pyparsing.ParserElement = element + #: The name of the element + self.name: typing.Optional[str] = name + #: The output Railroad element in an unconverted state + self.converted: EditablePartial = converted + #: The parent Railroad element, which we store so that we can extract this if it's duplicated + self.parent: EditablePartial = parent + #: The order in which we found this element, used for sorting diagrams if this is extracted into a diagram + self.number: int = number + #: The index of this inside its parent + self.parent_index: typing.Optional[int] = parent_index + #: If true, we should extract this out into a subdiagram + self.extract: bool = False + #: If true, all of this element's children have been filled out + self.complete: bool = False + + def mark_for_extraction( + self, el_id: int, state: "ConverterState", name: str = None, force: bool = False + ): + """ + Called when this instance has been seen twice, and thus should eventually be extracted into a sub-diagram + :param el_id: id of the element + :param state: element/diagram state tracker + :param name: name to use for this element's text + :param force: If true, force extraction now, regardless of the state of this. Only useful for extracting the + root element when we know we're finished + """ + self.extract = True + + # Set the name + if not self.name: + if name: + # Allow forcing a custom name + self.name = name + elif self.element.customName: + self.name = self.element.customName + else: + self.name = "" + + # Just because this is marked for extraction doesn't mean we can do it yet. We may have to wait for children + # to be added + # Also, if this is just a string literal etc, don't bother extracting it + if force or (self.complete and _worth_extracting(self.element)): + state.extract_into_diagram(el_id) + + +class ConverterState: + """ + Stores some state that persists between recursions into the element tree + """ + + def __init__(self, diagram_kwargs: typing.Optional[dict] = None): + #: A dictionary mapping ParserElements to state relating to them + self._element_diagram_states: Dict[int, ElementState] = {} + #: A dictionary mapping ParserElement IDs to subdiagrams generated from them + self.diagrams: Dict[int, EditablePartial[NamedDiagram]] = {} + #: The index of the next unnamed element + self.unnamed_index: int = 1 + #: The index of the next element. This is used for sorting + self.index: int = 0 + #: Shared kwargs that are used to customize the construction of diagrams + self.diagram_kwargs: dict = diagram_kwargs or {} + self.extracted_diagram_names: Set[str] = set() + + def __setitem__(self, key: int, value: ElementState): + self._element_diagram_states[key] = value + + def __getitem__(self, key: int) -> ElementState: + return self._element_diagram_states[key] + + def __delitem__(self, key: int): + del self._element_diagram_states[key] + + def __contains__(self, key: int): + return key in self._element_diagram_states + + def generate_unnamed(self) -> int: + """ + Generate a number used in the name of an otherwise unnamed diagram + """ + self.unnamed_index += 1 + return self.unnamed_index + + def generate_index(self) -> int: + """ + Generate a number used to index a diagram + """ + self.index += 1 + return self.index + + def extract_into_diagram(self, el_id: int): + """ + Used when we encounter the same token twice in the same tree. When this + happens, we replace all instances of that token with a terminal, and + create a new subdiagram for the token + """ + position = self[el_id] + + # Replace the original definition of this element with a regular block + if position.parent: + ret = EditablePartial.from_call(railroad.NonTerminal, text=position.name) + if "item" in position.parent.kwargs: + position.parent.kwargs["item"] = ret + elif "items" in position.parent.kwargs: + position.parent.kwargs["items"][position.parent_index] = ret + + # If the element we're extracting is a group, skip to its content but keep the title + if position.converted.func == railroad.Group: + content = position.converted.kwargs["item"] + else: + content = position.converted + + self.diagrams[el_id] = EditablePartial.from_call( + NamedDiagram, + name=position.name, + diagram=EditablePartial.from_call( + railroad.Diagram, content, **self.diagram_kwargs + ), + index=position.number, + ) + + del self[el_id] + + +def _worth_extracting(element: pyparsing.ParserElement) -> bool: + """ + Returns true if this element is worth having its own sub-diagram. Simply, if any of its children + themselves have children, then its complex enough to extract + """ + children = element.recurse() + return any(child.recurse() for child in children) + + +def _apply_diagram_item_enhancements(fn): + """ + decorator to ensure enhancements to a diagram item (such as results name annotations) + get applied on return from _to_diagram_element (we do this since there are several + returns in _to_diagram_element) + """ + + def _inner( + element: pyparsing.ParserElement, + parent: typing.Optional[EditablePartial], + lookup: ConverterState = None, + vertical: int = None, + index: int = 0, + name_hint: str = None, + show_results_names: bool = False, + show_groups: bool = False, + ) -> typing.Optional[EditablePartial]: + ret = fn( + element, + parent, + lookup, + vertical, + index, + name_hint, + show_results_names, + show_groups, + ) + + # apply annotation for results name, if present + if show_results_names and ret is not None: + element_results_name = element.resultsName + if element_results_name: + # add "*" to indicate if this is a "list all results" name + element_results_name += "" if element.modalResults else "*" + ret = EditablePartial.from_call( + railroad.Group, item=ret, label=element_results_name + ) + + return ret + + return _inner + + +def _visible_exprs(exprs: Iterable[pyparsing.ParserElement]): + non_diagramming_exprs = ( + pyparsing.ParseElementEnhance, + pyparsing.PositionToken, + pyparsing.And._ErrorStop, + ) + return [ + e + for e in exprs + if not (e.customName or e.resultsName or isinstance(e, non_diagramming_exprs)) + ] + + +@_apply_diagram_item_enhancements +def _to_diagram_element( + element: pyparsing.ParserElement, + parent: typing.Optional[EditablePartial], + lookup: ConverterState = None, + vertical: int = None, + index: int = 0, + name_hint: str = None, + show_results_names: bool = False, + show_groups: bool = False, +) -> typing.Optional[EditablePartial]: + """ + Recursively converts a PyParsing Element to a railroad Element + :param lookup: The shared converter state that keeps track of useful things + :param index: The index of this element within the parent + :param parent: The parent of this element in the output tree + :param vertical: Controls at what point we make a list of elements vertical. If this is an integer (the default), + it sets the threshold of the number of items before we go vertical. If True, always go vertical, if False, never + do so + :param name_hint: If provided, this will override the generated name + :param show_results_names: bool flag indicating whether to add annotations for results names + :returns: The converted version of the input element, but as a Partial that hasn't yet been constructed + :param show_groups: bool flag indicating whether to show groups using bounding box + """ + exprs = element.recurse() + name = name_hint or element.customName or element.__class__.__name__ + + # Python's id() is used to provide a unique identifier for elements + el_id = id(element) + + element_results_name = element.resultsName + + # Here we basically bypass processing certain wrapper elements if they contribute nothing to the diagram + if not element.customName: + if isinstance( + element, + ( + # pyparsing.TokenConverter, + # pyparsing.Forward, + pyparsing.Located, + ), + ): + # However, if this element has a useful custom name, and its child does not, we can pass it on to the child + if exprs: + if not exprs[0].customName: + propagated_name = name + else: + propagated_name = None + + return _to_diagram_element( + element.expr, + parent=parent, + lookup=lookup, + vertical=vertical, + index=index, + name_hint=propagated_name, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + # If the element isn't worth extracting, we always treat it as the first time we say it + if _worth_extracting(element): + if el_id in lookup: + # If we've seen this element exactly once before, we are only just now finding out that it's a duplicate, + # so we have to extract it into a new diagram. + looked_up = lookup[el_id] + looked_up.mark_for_extraction(el_id, lookup, name=name_hint) + ret = EditablePartial.from_call(railroad.NonTerminal, text=looked_up.name) + return ret + + elif el_id in lookup.diagrams: + # If we have seen the element at least twice before, and have already extracted it into a subdiagram, we + # just put in a marker element that refers to the sub-diagram + ret = EditablePartial.from_call( + railroad.NonTerminal, text=lookup.diagrams[el_id].kwargs["name"] + ) + return ret + + # Recursively convert child elements + # Here we find the most relevant Railroad element for matching pyparsing Element + # We use ``items=[]`` here to hold the place for where the child elements will go once created + if isinstance(element, pyparsing.And): + # detect And's created with ``expr*N`` notation - for these use a OneOrMore with a repeat + # (all will have the same name, and resultsName) + if not exprs: + return None + if len(set((e.name, e.resultsName) for e in exprs)) == 1: + ret = EditablePartial.from_call( + railroad.OneOrMore, item="", repeat=str(len(exprs)) + ) + elif _should_vertical(vertical, exprs): + ret = EditablePartial.from_call(railroad.Stack, items=[]) + else: + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + elif isinstance(element, (pyparsing.Or, pyparsing.MatchFirst)): + if not exprs: + return None + if _should_vertical(vertical, exprs): + ret = EditablePartial.from_call(railroad.Choice, 0, items=[]) + else: + ret = EditablePartial.from_call(railroad.HorizontalChoice, items=[]) + elif isinstance(element, pyparsing.Each): + if not exprs: + return None + ret = EditablePartial.from_call(EachItem, items=[]) + elif isinstance(element, pyparsing.NotAny): + ret = EditablePartial.from_call(AnnotatedItem, label="NOT", item="") + elif isinstance(element, pyparsing.FollowedBy): + ret = EditablePartial.from_call(AnnotatedItem, label="LOOKAHEAD", item="") + elif isinstance(element, pyparsing.PrecededBy): + ret = EditablePartial.from_call(AnnotatedItem, label="LOOKBEHIND", item="") + elif isinstance(element, pyparsing.Group): + if show_groups: + ret = EditablePartial.from_call(AnnotatedItem, label="", item="") + else: + ret = EditablePartial.from_call(railroad.Group, label="", item="") + elif isinstance(element, pyparsing.TokenConverter): + label = type(element).__name__.lower() + if label == "tokenconverter": + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + else: + ret = EditablePartial.from_call(AnnotatedItem, label=label, item="") + elif isinstance(element, pyparsing.Opt): + ret = EditablePartial.from_call(railroad.Optional, item="") + elif isinstance(element, pyparsing.OneOrMore): + ret = EditablePartial.from_call(railroad.OneOrMore, item="") + elif isinstance(element, pyparsing.ZeroOrMore): + ret = EditablePartial.from_call(railroad.ZeroOrMore, item="") + elif isinstance(element, pyparsing.Group): + ret = EditablePartial.from_call( + railroad.Group, item=None, label=element_results_name + ) + elif isinstance(element, pyparsing.Empty) and not element.customName: + # Skip unnamed "Empty" elements + ret = None + elif isinstance(element, pyparsing.ParseElementEnhance): + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + elif len(exprs) > 0 and not element_results_name: + ret = EditablePartial.from_call(railroad.Group, item="", label=name) + elif len(exprs) > 0: + ret = EditablePartial.from_call(railroad.Sequence, items=[]) + else: + terminal = EditablePartial.from_call(railroad.Terminal, element.defaultName) + ret = terminal + + if ret is None: + return + + # Indicate this element's position in the tree so we can extract it if necessary + lookup[el_id] = ElementState( + element=element, + converted=ret, + parent=parent, + parent_index=index, + number=lookup.generate_index(), + ) + if element.customName: + lookup[el_id].mark_for_extraction(el_id, lookup, element.customName) + + i = 0 + for expr in exprs: + # Add a placeholder index in case we have to extract the child before we even add it to the parent + if "items" in ret.kwargs: + ret.kwargs["items"].insert(i, None) + + item = _to_diagram_element( + expr, + parent=ret, + lookup=lookup, + vertical=vertical, + index=i, + show_results_names=show_results_names, + show_groups=show_groups, + ) + + # Some elements don't need to be shown in the diagram + if item is not None: + if "item" in ret.kwargs: + ret.kwargs["item"] = item + elif "items" in ret.kwargs: + # If we've already extracted the child, don't touch this index, since it's occupied by a nonterminal + ret.kwargs["items"][i] = item + i += 1 + elif "items" in ret.kwargs: + # If we're supposed to skip this element, remove it from the parent + del ret.kwargs["items"][i] + + # If all this items children are none, skip this item + if ret and ( + ("items" in ret.kwargs and len(ret.kwargs["items"]) == 0) + or ("item" in ret.kwargs and ret.kwargs["item"] is None) + ): + ret = EditablePartial.from_call(railroad.Terminal, name) + + # Mark this element as "complete", ie it has all of its children + if el_id in lookup: + lookup[el_id].complete = True + + if el_id in lookup and lookup[el_id].extract and lookup[el_id].complete: + lookup.extract_into_diagram(el_id) + if ret is not None: + ret = EditablePartial.from_call( + railroad.NonTerminal, text=lookup.diagrams[el_id].kwargs["name"] + ) + + return ret diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/exceptions.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/exceptions.py new file mode 100644 index 00000000..12219f12 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/exceptions.py @@ -0,0 +1,299 @@ +# exceptions.py + +import re +import sys +import typing + +from .util import ( + col, + line, + lineno, + _collapse_string_to_ranges, + replaced_by_pep8, +) +from .unicode import pyparsing_unicode as ppu + + +class ExceptionWordUnicode(ppu.Latin1, ppu.LatinA, ppu.LatinB, ppu.Greek, ppu.Cyrillic): + pass + + +_extract_alphanums = _collapse_string_to_ranges(ExceptionWordUnicode.alphanums) +_exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.") + + +class ParseBaseException(Exception): + """base exception class for all parsing runtime exceptions""" + + loc: int + msg: str + pstr: str + parser_element: typing.Any # "ParserElement" + args: typing.Tuple[str, int, typing.Optional[str]] + + __slots__ = ( + "loc", + "msg", + "pstr", + "parser_element", + "args", + ) + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( + self, + pstr: str, + loc: int = 0, + msg: typing.Optional[str] = None, + elem=None, + ): + self.loc = loc + if msg is None: + self.msg = pstr + self.pstr = "" + else: + self.msg = msg + self.pstr = pstr + self.parser_element = elem + self.args = (pstr, loc, msg) + + @staticmethod + def explain_exception(exc, depth=16): + """ + Method to take an exception and translate the Python internal traceback into a list + of the pyparsing expressions that caused the exception to be raised. + + Parameters: + + - exc - exception raised during parsing (need not be a ParseException, in support + of Python exceptions that might be raised in a parse action) + - depth (default=16) - number of levels back in the stack trace to list expression + and function names; if None, the full stack trace names will be listed; if 0, only + the failing input line, marker, and exception string will be shown + + Returns a multi-line string listing the ParserElements and/or function names in the + exception's stack trace. + """ + import inspect + from .core import ParserElement + + if depth is None: + depth = sys.getrecursionlimit() + ret = [] + if isinstance(exc, ParseBaseException): + ret.append(exc.line) + ret.append(" " * (exc.column - 1) + "^") + ret.append(f"{type(exc).__name__}: {exc}") + + if depth > 0: + callers = inspect.getinnerframes(exc.__traceback__, context=depth) + seen = set() + for i, ff in enumerate(callers[-depth:]): + frm = ff[0] + + f_self = frm.f_locals.get("self", None) + if isinstance(f_self, ParserElement): + if not frm.f_code.co_name.startswith( + ("parseImpl", "_parseNoCache") + ): + continue + if id(f_self) in seen: + continue + seen.add(id(f_self)) + + self_type = type(f_self) + ret.append( + f"{self_type.__module__}.{self_type.__name__} - {f_self}" + ) + + elif f_self is not None: + self_type = type(f_self) + ret.append(f"{self_type.__module__}.{self_type.__name__}") + + else: + code = frm.f_code + if code.co_name in ("wrapper", ""): + continue + + ret.append(code.co_name) + + depth -= 1 + if not depth: + break + + return "\n".join(ret) + + @classmethod + def _from_exception(cls, pe): + """ + internal factory method to simplify creating one type of ParseException + from another - avoids having __init__ signature conflicts among subclasses + """ + return cls(pe.pstr, pe.loc, pe.msg, pe.parser_element) + + @property + def line(self) -> str: + """ + Return the line of text where the exception occurred. + """ + return line(self.loc, self.pstr) + + @property + def lineno(self) -> int: + """ + Return the 1-based line number of text where the exception occurred. + """ + return lineno(self.loc, self.pstr) + + @property + def col(self) -> int: + """ + Return the 1-based column on the line of text where the exception occurred. + """ + return col(self.loc, self.pstr) + + @property + def column(self) -> int: + """ + Return the 1-based column on the line of text where the exception occurred. + """ + return col(self.loc, self.pstr) + + # pre-PEP8 compatibility + @property + def parserElement(self): + return self.parser_element + + @parserElement.setter + def parserElement(self, elem): + self.parser_element = elem + + def __str__(self) -> str: + if self.pstr: + if self.loc >= len(self.pstr): + foundstr = ", found end of text" + else: + # pull out next word at error location + found_match = _exception_word_extractor.match(self.pstr, self.loc) + if found_match is not None: + found = found_match.group(0) + else: + found = self.pstr[self.loc : self.loc + 1] + foundstr = (", found %r" % found).replace(r"\\", "\\") + else: + foundstr = "" + return f"{self.msg}{foundstr} (at char {self.loc}), (line:{self.lineno}, col:{self.column})" + + def __repr__(self): + return str(self) + + def mark_input_line( + self, marker_string: typing.Optional[str] = None, *, markerString: str = ">!<" + ) -> str: + """ + Extracts the exception line from the input string, and marks + the location of the exception with a special symbol. + """ + markerString = marker_string if marker_string is not None else markerString + line_str = self.line + line_column = self.column - 1 + if markerString: + line_str = "".join( + (line_str[:line_column], markerString, line_str[line_column:]) + ) + return line_str.strip() + + def explain(self, depth=16) -> str: + """ + Method to translate the Python internal traceback into a list + of the pyparsing expressions that caused the exception to be raised. + + Parameters: + + - depth (default=16) - number of levels back in the stack trace to list expression + and function names; if None, the full stack trace names will be listed; if 0, only + the failing input line, marker, and exception string will be shown + + Returns a multi-line string listing the ParserElements and/or function names in the + exception's stack trace. + + Example:: + + expr = pp.Word(pp.nums) * 3 + try: + expr.parse_string("123 456 A789") + except pp.ParseException as pe: + print(pe.explain(depth=0)) + + prints:: + + 123 456 A789 + ^ + ParseException: Expected W:(0-9), found 'A' (at char 8), (line:1, col:9) + + Note: the diagnostic output will include string representations of the expressions + that failed to parse. These representations will be more helpful if you use `set_name` to + give identifiable names to your expressions. Otherwise they will use the default string + forms, which may be cryptic to read. + + Note: pyparsing's default truncation of exception tracebacks may also truncate the + stack of expressions that are displayed in the ``explain`` output. To get the full listing + of parser expressions, you may have to set ``ParserElement.verbose_stacktrace = True`` + """ + return self.explain_exception(self, depth) + + # fmt: off + @replaced_by_pep8(mark_input_line) + def markInputline(self): ... + # fmt: on + + +class ParseException(ParseBaseException): + """ + Exception thrown when a parse expression doesn't match the input string + + Example:: + + try: + Word(nums).set_name("integer").parse_string("ABC") + except ParseException as pe: + print(pe) + print("column: {}".format(pe.column)) + + prints:: + + Expected integer (at char 0), (line:1, col:1) + column: 1 + + """ + + +class ParseFatalException(ParseBaseException): + """ + User-throwable exception thrown when inconsistent parse content + is found; stops all parsing immediately + """ + + +class ParseSyntaxException(ParseFatalException): + """ + Just like :class:`ParseFatalException`, but thrown internally + when an :class:`ErrorStop` ('-' operator) indicates + that parsing is to stop immediately because an unbacktrackable + syntax error has been found. + """ + + +class RecursiveGrammarException(Exception): + """ + Exception thrown by :class:`ParserElement.validate` if the + grammar could be left-recursive; parser may need to enable + left recursion using :class:`ParserElement.enable_left_recursion` + """ + + def __init__(self, parseElementList): + self.parseElementTrace = parseElementList + + def __str__(self) -> str: + return f"RecursiveGrammarException: {self.parseElementTrace}" diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/helpers.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/helpers.py new file mode 100644 index 00000000..018f0d6a --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/helpers.py @@ -0,0 +1,1100 @@ +# helpers.py +import html.entities +import re +import sys +import typing + +from . import __diag__ +from .core import * +from .util import ( + _bslash, + _flatten, + _escape_regex_range_chars, + replaced_by_pep8, +) + + +# +# global helpers +# +def counted_array( + expr: ParserElement, + int_expr: typing.Optional[ParserElement] = None, + *, + intExpr: typing.Optional[ParserElement] = None, +) -> ParserElement: + """Helper to define a counted list of expressions. + + This helper defines a pattern of the form:: + + integer expr expr expr... + + where the leading integer tells how many expr expressions follow. + The matched tokens returns the array of expr tokens as a list - the + leading count token is suppressed. + + If ``int_expr`` is specified, it should be a pyparsing expression + that produces an integer value. + + Example:: + + counted_array(Word(alphas)).parse_string('2 ab cd ef') # -> ['ab', 'cd'] + + # in this parser, the leading integer value is given in binary, + # '10' indicating that 2 values are in the array + binary_constant = Word('01').set_parse_action(lambda t: int(t[0], 2)) + counted_array(Word(alphas), int_expr=binary_constant).parse_string('10 ab cd ef') # -> ['ab', 'cd'] + + # if other fields must be parsed after the count but before the + # list items, give the fields results names and they will + # be preserved in the returned ParseResults: + count_with_metadata = integer + Word(alphas)("type") + typed_array = counted_array(Word(alphanums), int_expr=count_with_metadata)("items") + result = typed_array.parse_string("3 bool True True False") + print(result.dump()) + + # prints + # ['True', 'True', 'False'] + # - items: ['True', 'True', 'False'] + # - type: 'bool' + """ + intExpr = intExpr or int_expr + array_expr = Forward() + + def count_field_parse_action(s, l, t): + nonlocal array_expr + n = t[0] + array_expr <<= (expr * n) if n else Empty() + # clear list contents, but keep any named results + del t[:] + + if intExpr is None: + intExpr = Word(nums).set_parse_action(lambda t: int(t[0])) + else: + intExpr = intExpr.copy() + intExpr.set_name("arrayLen") + intExpr.add_parse_action(count_field_parse_action, call_during_try=True) + return (intExpr + array_expr).set_name("(len) " + str(expr) + "...") + + +def match_previous_literal(expr: ParserElement) -> ParserElement: + """Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks for + a 'repeat' of a previous expression. For example:: + + first = Word(nums) + second = match_previous_literal(first) + match_expr = first + ":" + second + + will match ``"1:1"``, but not ``"1:2"``. Because this + matches a previous literal, will also match the leading + ``"1:1"`` in ``"1:10"``. If this is not desired, use + :class:`match_previous_expr`. Do *not* use with packrat parsing + enabled. + """ + rep = Forward() + + def copy_token_to_repeater(s, l, t): + if t: + if len(t) == 1: + rep << t[0] + else: + # flatten t tokens + tflat = _flatten(t.as_list()) + rep << And(Literal(tt) for tt in tflat) + else: + rep << Empty() + + expr.add_parse_action(copy_token_to_repeater, callDuringTry=True) + rep.set_name("(prev) " + str(expr)) + return rep + + +def match_previous_expr(expr: ParserElement) -> ParserElement: + """Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks for + a 'repeat' of a previous expression. For example:: + + first = Word(nums) + second = match_previous_expr(first) + match_expr = first + ":" + second + + will match ``"1:1"``, but not ``"1:2"``. Because this + matches by expressions, will *not* match the leading ``"1:1"`` + in ``"1:10"``; the expressions are evaluated first, and then + compared, so ``"1"`` is compared with ``"10"``. Do *not* use + with packrat parsing enabled. + """ + rep = Forward() + e2 = expr.copy() + rep <<= e2 + + def copy_token_to_repeater(s, l, t): + matchTokens = _flatten(t.as_list()) + + def must_match_these_tokens(s, l, t): + theseTokens = _flatten(t.as_list()) + if theseTokens != matchTokens: + raise ParseException( + s, l, f"Expected {matchTokens}, found{theseTokens}" + ) + + rep.set_parse_action(must_match_these_tokens, callDuringTry=True) + + expr.add_parse_action(copy_token_to_repeater, callDuringTry=True) + rep.set_name("(prev) " + str(expr)) + return rep + + +def one_of( + strs: Union[typing.Iterable[str], str], + caseless: bool = False, + use_regex: bool = True, + as_keyword: bool = False, + *, + useRegex: bool = True, + asKeyword: bool = False, +) -> ParserElement: + """Helper to quickly define a set of alternative :class:`Literal` s, + and makes sure to do longest-first testing when there is a conflict, + regardless of the input order, but returns + a :class:`MatchFirst` for best performance. + + Parameters: + + - ``strs`` - a string of space-delimited literals, or a collection of + string literals + - ``caseless`` - treat all literals as caseless - (default= ``False``) + - ``use_regex`` - as an optimization, will + generate a :class:`Regex` object; otherwise, will generate + a :class:`MatchFirst` object (if ``caseless=True`` or ``as_keyword=True``, or if + creating a :class:`Regex` raises an exception) - (default= ``True``) + - ``as_keyword`` - enforce :class:`Keyword`-style matching on the + generated expressions - (default= ``False``) + - ``asKeyword`` and ``useRegex`` are retained for pre-PEP8 compatibility, + but will be removed in a future release + + Example:: + + comp_oper = one_of("< = > <= >= !=") + var = Word(alphas) + number = Word(nums) + term = var | number + comparison_expr = term + comp_oper + term + print(comparison_expr.search_string("B = 12 AA=23 B<=AA AA>12")) + + prints:: + + [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] + """ + asKeyword = asKeyword or as_keyword + useRegex = useRegex and use_regex + + if ( + isinstance(caseless, str_type) + and __diag__.warn_on_multiple_string_args_to_oneof + ): + warnings.warn( + "More than one string argument passed to one_of, pass" + " choices as a list or space-delimited string", + stacklevel=2, + ) + + if caseless: + isequal = lambda a, b: a.upper() == b.upper() + masks = lambda a, b: b.upper().startswith(a.upper()) + parseElementClass = CaselessKeyword if asKeyword else CaselessLiteral + else: + isequal = lambda a, b: a == b + masks = lambda a, b: b.startswith(a) + parseElementClass = Keyword if asKeyword else Literal + + symbols: List[str] = [] + if isinstance(strs, str_type): + strs = typing.cast(str, strs) + symbols = strs.split() + elif isinstance(strs, Iterable): + symbols = list(strs) + else: + raise TypeError("Invalid argument to one_of, expected string or iterable") + if not symbols: + return NoMatch() + + # reorder given symbols to take care to avoid masking longer choices with shorter ones + # (but only if the given symbols are not just single characters) + if any(len(sym) > 1 for sym in symbols): + i = 0 + while i < len(symbols) - 1: + cur = symbols[i] + for j, other in enumerate(symbols[i + 1 :]): + if isequal(other, cur): + del symbols[i + j + 1] + break + elif masks(cur, other): + del symbols[i + j + 1] + symbols.insert(i, other) + break + else: + i += 1 + + if useRegex: + re_flags: int = re.IGNORECASE if caseless else 0 + + try: + if all(len(sym) == 1 for sym in symbols): + # symbols are just single characters, create range regex pattern + patt = f"[{''.join(_escape_regex_range_chars(sym) for sym in symbols)}]" + else: + patt = "|".join(re.escape(sym) for sym in symbols) + + # wrap with \b word break markers if defining as keywords + if asKeyword: + patt = rf"\b(?:{patt})\b" + + ret = Regex(patt, flags=re_flags).set_name(" | ".join(symbols)) + + if caseless: + # add parse action to return symbols as specified, not in random + # casing as found in input string + symbol_map = {sym.lower(): sym for sym in symbols} + ret.add_parse_action(lambda s, l, t: symbol_map[t[0].lower()]) + + return ret + + except re.error: + warnings.warn( + "Exception creating Regex for one_of, building MatchFirst", stacklevel=2 + ) + + # last resort, just use MatchFirst + return MatchFirst(parseElementClass(sym) for sym in symbols).set_name( + " | ".join(symbols) + ) + + +def dict_of(key: ParserElement, value: ParserElement) -> ParserElement: + """Helper to easily and clearly define a dictionary by specifying + the respective patterns for the key and value. Takes care of + defining the :class:`Dict`, :class:`ZeroOrMore`, and + :class:`Group` tokens in the proper order. The key pattern + can include delimiting markers or punctuation, as long as they are + suppressed, thereby leaving the significant key text. The value + pattern can include named results, so that the :class:`Dict` results + can include named token fields. + + Example:: + + text = "shape: SQUARE posn: upper left color: light blue texture: burlap" + attr_expr = (label + Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join)) + print(attr_expr[1, ...].parse_string(text).dump()) + + attr_label = label + attr_value = Suppress(':') + OneOrMore(data_word, stop_on=label).set_parse_action(' '.join) + + # similar to Dict, but simpler call format + result = dict_of(attr_label, attr_value).parse_string(text) + print(result.dump()) + print(result['shape']) + print(result.shape) # object attribute access works too + print(result.as_dict()) + + prints:: + + [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] + - color: 'light blue' + - posn: 'upper left' + - shape: 'SQUARE' + - texture: 'burlap' + SQUARE + SQUARE + {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'} + """ + return Dict(OneOrMore(Group(key + value))) + + +def original_text_for( + expr: ParserElement, as_string: bool = True, *, asString: bool = True +) -> ParserElement: + """Helper to return the original, untokenized text for a given + expression. Useful to restore the parsed fields of an HTML start + tag into the raw tag text itself, or to revert separate tokens with + intervening whitespace back to the original matching input text. By + default, returns a string containing the original parsed text. + + If the optional ``as_string`` argument is passed as + ``False``, then the return value is + a :class:`ParseResults` containing any results names that + were originally matched, and a single token containing the original + matched text from the input string. So if the expression passed to + :class:`original_text_for` contains expressions with defined + results names, you must set ``as_string`` to ``False`` if you + want to preserve those results name values. + + The ``asString`` pre-PEP8 argument is retained for compatibility, + but will be removed in a future release. + + Example:: + + src = "this is test bold text normal text " + for tag in ("b", "i"): + opener, closer = make_html_tags(tag) + patt = original_text_for(opener + ... + closer) + print(patt.search_string(src)[0]) + + prints:: + + [' bold text '] + ['text'] + """ + asString = asString and as_string + + locMarker = Empty().set_parse_action(lambda s, loc, t: loc) + endlocMarker = locMarker.copy() + endlocMarker.callPreparse = False + matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") + if asString: + extractText = lambda s, l, t: s[t._original_start : t._original_end] + else: + + def extractText(s, l, t): + t[:] = [s[t.pop("_original_start") : t.pop("_original_end")]] + + matchExpr.set_parse_action(extractText) + matchExpr.ignoreExprs = expr.ignoreExprs + matchExpr.suppress_warning(Diagnostics.warn_ungrouped_named_tokens_in_collection) + return matchExpr + + +def ungroup(expr: ParserElement) -> ParserElement: + """Helper to undo pyparsing's default grouping of And expressions, + even if all but one are non-empty. + """ + return TokenConverter(expr).add_parse_action(lambda t: t[0]) + + +def locatedExpr(expr: ParserElement) -> ParserElement: + """ + (DEPRECATED - future code should use the :class:`Located` class) + Helper to decorate a returned token with its starting and ending + locations in the input string. + + This helper adds the following results names: + + - ``locn_start`` - location where matched expression begins + - ``locn_end`` - location where matched expression ends + - ``value`` - the actual parsed results + + Be careful if the input text contains ```` characters, you + may want to call :class:`ParserElement.parse_with_tabs` + + Example:: + + wd = Word(alphas) + for match in locatedExpr(wd).search_string("ljsdf123lksdjjf123lkkjj1222"): + print(match) + + prints:: + + [[0, 'ljsdf', 5]] + [[8, 'lksdjjf', 15]] + [[18, 'lkkjj', 23]] + """ + locator = Empty().set_parse_action(lambda ss, ll, tt: ll) + return Group( + locator("locn_start") + + expr("value") + + locator.copy().leaveWhitespace()("locn_end") + ) + + +def nested_expr( + opener: Union[str, ParserElement] = "(", + closer: Union[str, ParserElement] = ")", + content: typing.Optional[ParserElement] = None, + ignore_expr: ParserElement = quoted_string(), + *, + ignoreExpr: ParserElement = quoted_string(), +) -> ParserElement: + """Helper method for defining nested lists enclosed in opening and + closing delimiters (``"("`` and ``")"`` are the default). + + Parameters: + + - ``opener`` - opening character for a nested list + (default= ``"("``); can also be a pyparsing expression + - ``closer`` - closing character for a nested list + (default= ``")"``); can also be a pyparsing expression + - ``content`` - expression for items within the nested lists + (default= ``None``) + - ``ignore_expr`` - expression for ignoring opening and closing delimiters + (default= :class:`quoted_string`) + - ``ignoreExpr`` - this pre-PEP8 argument is retained for compatibility + but will be removed in a future release + + If an expression is not provided for the content argument, the + nested expression will capture all whitespace-delimited content + between delimiters as a list of separate values. + + Use the ``ignore_expr`` argument to define expressions that may + contain opening or closing characters that should not be treated as + opening or closing characters for nesting, such as quoted_string or + a comment expression. Specify multiple expressions using an + :class:`Or` or :class:`MatchFirst`. The default is + :class:`quoted_string`, but if no expressions are to be ignored, then + pass ``None`` for this argument. + + Example:: + + data_type = one_of("void int short long char float double") + decl_data_type = Combine(data_type + Opt(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR, RPAR = map(Suppress, "()") + + code_body = nested_expr('{', '}', ignore_expr=(quoted_string | c_style_comment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Opt(DelimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(c_style_comment) + + source_code = ''' + int is_odd(int x) { + return (x%2); + } + + int dec_to_hex(char hchar) { + if (hchar >= '0' && hchar <= '9') { + return (ord(hchar)-ord('0')); + } else { + return (10+ord(hchar)-ord('A')); + } + } + ''' + for func in c_function.search_string(source_code): + print("%(name)s (%(type)s) args: %(args)s" % func) + + + prints:: + + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] + """ + if ignoreExpr != ignore_expr: + ignoreExpr = ignore_expr if ignoreExpr == quoted_string() else ignoreExpr + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener, str_type) and isinstance(closer, str_type): + opener = typing.cast(str, opener) + closer = typing.cast(str, closer) + if len(opener) == 1 and len(closer) == 1: + if ignoreExpr is not None: + content = Combine( + OneOrMore( + ~ignoreExpr + + CharsNotIn( + opener + closer + ParserElement.DEFAULT_WHITE_CHARS, + exact=1, + ) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + content = empty.copy() + CharsNotIn( + opener + closer + ParserElement.DEFAULT_WHITE_CHARS + ).set_parse_action(lambda t: t[0].strip()) + else: + if ignoreExpr is not None: + content = Combine( + OneOrMore( + ~ignoreExpr + + ~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + content = Combine( + OneOrMore( + ~Literal(opener) + + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS, exact=1) + ) + ).set_parse_action(lambda t: t[0].strip()) + else: + raise ValueError( + "opening and closing arguments must be strings if no content expression is given" + ) + ret = Forward() + if ignoreExpr is not None: + ret <<= Group( + Suppress(opener) + ZeroOrMore(ignoreExpr | ret | content) + Suppress(closer) + ) + else: + ret <<= Group(Suppress(opener) + ZeroOrMore(ret | content) + Suppress(closer)) + ret.set_name("nested %s%s expression" % (opener, closer)) + return ret + + +def _makeTags(tagStr, xml, suppress_LT=Suppress("<"), suppress_GT=Suppress(">")): + """Internal helper to construct opening and closing tag expressions, given a tag name""" + if isinstance(tagStr, str_type): + resname = tagStr + tagStr = Keyword(tagStr, caseless=not xml) + else: + resname = tagStr.name + + tagAttrName = Word(alphas, alphanums + "_-:") + if xml: + tagAttrValue = dbl_quoted_string.copy().set_parse_action(remove_quotes) + openTag = ( + suppress_LT + + tagStr("tag") + + Dict(ZeroOrMore(Group(tagAttrName + Suppress("=") + tagAttrValue))) + + Opt("/", default=[False])("empty").set_parse_action( + lambda s, l, t: t[0] == "/" + ) + + suppress_GT + ) + else: + tagAttrValue = quoted_string.copy().set_parse_action(remove_quotes) | Word( + printables, exclude_chars=">" + ) + openTag = ( + suppress_LT + + tagStr("tag") + + Dict( + ZeroOrMore( + Group( + tagAttrName.set_parse_action(lambda t: t[0].lower()) + + Opt(Suppress("=") + tagAttrValue) + ) + ) + ) + + Opt("/", default=[False])("empty").set_parse_action( + lambda s, l, t: t[0] == "/" + ) + + suppress_GT + ) + closeTag = Combine(Literal("", adjacent=False) + + openTag.set_name("<%s>" % resname) + # add start results name in parse action now that ungrouped names are not reported at two levels + openTag.add_parse_action( + lambda t: t.__setitem__( + "start" + "".join(resname.replace(":", " ").title().split()), t.copy() + ) + ) + closeTag = closeTag( + "end" + "".join(resname.replace(":", " ").title().split()) + ).set_name("" % resname) + openTag.tag = resname + closeTag.tag = resname + openTag.tag_body = SkipTo(closeTag()) + return openTag, closeTag + + +def make_html_tags( + tag_str: Union[str, ParserElement] +) -> Tuple[ParserElement, ParserElement]: + """Helper to construct opening and closing tag expressions for HTML, + given a tag name. Matches tags in either upper or lower case, + attributes with namespaces and with quoted or unquoted values. + + Example:: + + text = 'More info at the pyparsing wiki page' + # make_html_tags returns pyparsing expressions for the opening and + # closing tags as a 2-tuple + a, a_end = make_html_tags("A") + link_expr = a + SkipTo(a_end)("link_text") + a_end + + for link in link_expr.search_string(text): + # attributes in the tag (like "href" shown here) are + # also accessible as named results + print(link.link_text, '->', link.href) + + prints:: + + pyparsing -> https://github.com/pyparsing/pyparsing/wiki + """ + return _makeTags(tag_str, False) + + +def make_xml_tags( + tag_str: Union[str, ParserElement] +) -> Tuple[ParserElement, ParserElement]: + """Helper to construct opening and closing tag expressions for XML, + given a tag name. Matches tags only in the given upper/lower case. + + Example: similar to :class:`make_html_tags` + """ + return _makeTags(tag_str, True) + + +any_open_tag: ParserElement +any_close_tag: ParserElement +any_open_tag, any_close_tag = make_html_tags( + Word(alphas, alphanums + "_:").set_name("any tag") +) + +_htmlEntityMap = {k.rstrip(";"): v for k, v in html.entities.html5.items()} +common_html_entity = Regex("&(?P" + "|".join(_htmlEntityMap) + ");").set_name( + "common HTML entity" +) + + +def replace_html_entity(s, l, t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + + +class OpAssoc(Enum): + """Enumeration of operator associativity + - used in constructing InfixNotationOperatorSpec for :class:`infix_notation`""" + + LEFT = 1 + RIGHT = 2 + + +InfixNotationOperatorArgType = Union[ + ParserElement, str, Tuple[Union[ParserElement, str], Union[ParserElement, str]] +] +InfixNotationOperatorSpec = Union[ + Tuple[ + InfixNotationOperatorArgType, + int, + OpAssoc, + typing.Optional[ParseAction], + ], + Tuple[ + InfixNotationOperatorArgType, + int, + OpAssoc, + ], +] + + +def infix_notation( + base_expr: ParserElement, + op_list: List[InfixNotationOperatorSpec], + lpar: Union[str, ParserElement] = Suppress("("), + rpar: Union[str, ParserElement] = Suppress(")"), +) -> ParserElement: + """Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary + or binary, left- or right-associative. Parse actions can also be + attached to operator expressions. The generated parser will also + recognize the use of parentheses to override operator precedences + (see example below). + + Note: if you define a deep operator list, you may see performance + issues when using infix_notation. See + :class:`ParserElement.enable_packrat` for a mechanism to potentially + improve your parser performance. + + Parameters: + + - ``base_expr`` - expression representing the most basic operand to + be used in the expression + - ``op_list`` - list of tuples, one for each operator precedence level + in the expression grammar; each tuple is of the form ``(op_expr, + num_operands, right_left_assoc, (optional)parse_action)``, where: + + - ``op_expr`` is the pyparsing expression for the operator; may also + be a string, which will be converted to a Literal; if ``num_operands`` + is 3, ``op_expr`` is a tuple of two expressions, for the two + operators separating the 3 terms + - ``num_operands`` is the number of terms for this operator (must be 1, + 2, or 3) + - ``right_left_assoc`` is the indicator whether the operator is right + or left associative, using the pyparsing-defined constants + ``OpAssoc.RIGHT`` and ``OpAssoc.LEFT``. + - ``parse_action`` is the parse action to be associated with + expressions matching this operator expression (the parse action + tuple member may be omitted); if the parse action is passed + a tuple or list of functions, this is equivalent to calling + ``set_parse_action(*fn)`` + (:class:`ParserElement.set_parse_action`) + - ``lpar`` - expression for matching left-parentheses; if passed as a + str, then will be parsed as ``Suppress(lpar)``. If lpar is passed as + an expression (such as ``Literal('(')``), then it will be kept in + the parsed results, and grouped with them. (default= ``Suppress('(')``) + - ``rpar`` - expression for matching right-parentheses; if passed as a + str, then will be parsed as ``Suppress(rpar)``. If rpar is passed as + an expression (such as ``Literal(')')``), then it will be kept in + the parsed results, and grouped with them. (default= ``Suppress(')')``) + + Example:: + + # simple example of four-function arithmetic with ints and + # variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infix_notation(integer | varname, + [ + ('-', 1, OpAssoc.RIGHT), + (one_of('* /'), 2, OpAssoc.LEFT), + (one_of('+ -'), 2, OpAssoc.LEFT), + ]) + + arith_expr.run_tests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', full_dump=False) + + prints:: + + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + (5+x)*y + [[[5, '+', 'x'], '*', 'y']] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + + # captive version of FollowedBy that does not do parse actions or capture results names + class _FB(FollowedBy): + def parseImpl(self, instring, loc, doActions=True): + self.expr.try_parse(instring, loc) + return loc, [] + + _FB.__name__ = "FollowedBy>" + + ret = Forward() + if isinstance(lpar, str): + lpar = Suppress(lpar) + if isinstance(rpar, str): + rpar = Suppress(rpar) + + # if lpar and rpar are not suppressed, wrap in group + if not (isinstance(rpar, Suppress) and isinstance(rpar, Suppress)): + lastExpr = base_expr | Group(lpar + ret + rpar) + else: + lastExpr = base_expr | (lpar + ret + rpar) + + arity: int + rightLeftAssoc: opAssoc + pa: typing.Optional[ParseAction] + opExpr1: ParserElement + opExpr2: ParserElement + for i, operDef in enumerate(op_list): + opExpr, arity, rightLeftAssoc, pa = (operDef + (None,))[:4] # type: ignore[assignment] + if isinstance(opExpr, str_type): + opExpr = ParserElement._literalStringClass(opExpr) + opExpr = typing.cast(ParserElement, opExpr) + if arity == 3: + if not isinstance(opExpr, (tuple, list)) or len(opExpr) != 2: + raise ValueError( + "if numterms=3, opExpr must be a tuple or list of two expressions" + ) + opExpr1, opExpr2 = opExpr + term_name = f"{opExpr1}{opExpr2} term" + else: + term_name = f"{opExpr} term" + + if not 1 <= arity <= 3: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + + if rightLeftAssoc not in (OpAssoc.LEFT, OpAssoc.RIGHT): + raise ValueError("operator must indicate right or left associativity") + + thisExpr: ParserElement = Forward().set_name(term_name) + thisExpr = typing.cast(Forward, thisExpr) + if rightLeftAssoc is OpAssoc.LEFT: + if arity == 1: + matchExpr = _FB(lastExpr + opExpr) + Group(lastExpr + opExpr[1, ...]) + elif arity == 2: + if opExpr is not None: + matchExpr = _FB(lastExpr + opExpr + lastExpr) + Group( + lastExpr + (opExpr + lastExpr)[1, ...] + ) + else: + matchExpr = _FB(lastExpr + lastExpr) + Group(lastExpr[2, ...]) + elif arity == 3: + matchExpr = _FB( + lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr + ) + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr)) + elif rightLeftAssoc is OpAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Opt): + opExpr = Opt(opExpr) + matchExpr = _FB(opExpr.expr + thisExpr) + Group(opExpr + thisExpr) + elif arity == 2: + if opExpr is not None: + matchExpr = _FB(lastExpr + opExpr + thisExpr) + Group( + lastExpr + (opExpr + thisExpr)[1, ...] + ) + else: + matchExpr = _FB(lastExpr + thisExpr) + Group( + lastExpr + thisExpr[1, ...] + ) + elif arity == 3: + matchExpr = _FB( + lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr + ) + Group(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.set_parse_action(*pa) + else: + matchExpr.set_parse_action(pa) + thisExpr <<= (matchExpr | lastExpr).setName(term_name) + lastExpr = thisExpr + ret <<= lastExpr + return ret + + +def indentedBlock(blockStatementExpr, indentStack, indent=True, backup_stacks=[]): + """ + (DEPRECATED - use :class:`IndentedBlock` class instead) + Helper method for defining space-delimited indentation blocks, + such as those used to define block statements in Python source code. + + Parameters: + + - ``blockStatementExpr`` - expression defining syntax of statement that + is repeated within the indented block + - ``indentStack`` - list created by caller to manage indentation stack + (multiple ``statementWithIndentedBlock`` expressions within a single + grammar should share a common ``indentStack``) + - ``indent`` - boolean indicating whether block must be indented beyond + the current level; set to ``False`` for block of left-most statements + (default= ``True``) + + A valid block must contain at least one ``blockStatement``. + + (Note that indentedBlock uses internal parse actions which make it + incompatible with packrat parsing.) + + Example:: + + data = ''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''' + + + indentStack = [1] + stmt = Forward() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group("(" + Opt(delimitedList(identifier)) + ")") + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group(funcDecl + func_body) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Opt(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << (funcDef | assignment | identifier) + + module_body = stmt[1, ...] + + parseTree = module_body.parseString(data) + parseTree.pprint() + + prints:: + + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] + """ + backup_stacks.append(indentStack[:]) + + def reset_stack(): + indentStack[:] = backup_stacks[-1] + + def checkPeerIndent(s, l, t): + if l >= len(s): + return + curCol = col(l, s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseException(s, l, "illegal nesting") + raise ParseException(s, l, "not a peer entry") + + def checkSubIndent(s, l, t): + curCol = col(l, s) + if curCol > indentStack[-1]: + indentStack.append(curCol) + else: + raise ParseException(s, l, "not a subentry") + + def checkUnindent(s, l, t): + if l >= len(s): + return + curCol = col(l, s) + if not (indentStack and curCol in indentStack): + raise ParseException(s, l, "not an unindent") + if curCol < indentStack[-1]: + indentStack.pop() + + NL = OneOrMore(LineEnd().set_whitespace_chars("\t ").suppress()) + INDENT = (Empty() + Empty().set_parse_action(checkSubIndent)).set_name("INDENT") + PEER = Empty().set_parse_action(checkPeerIndent).set_name("") + UNDENT = Empty().set_parse_action(checkUnindent).set_name("UNINDENT") + if indent: + smExpr = Group( + Opt(NL) + + INDENT + + OneOrMore(PEER + Group(blockStatementExpr) + Opt(NL)) + + UNDENT + ) + else: + smExpr = Group( + Opt(NL) + + OneOrMore(PEER + Group(blockStatementExpr) + Opt(NL)) + + Opt(UNDENT) + ) + + # add a parse action to remove backup_stack from list of backups + smExpr.add_parse_action( + lambda: backup_stacks.pop(-1) and None if backup_stacks else None + ) + smExpr.set_fail_action(lambda a, b, c, d: reset_stack()) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.set_name("indented block") + + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +c_style_comment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/").set_name( + "C style comment" +) +"Comment of the form ``/* ... */``" + +html_comment = Regex(r"").set_name("HTML comment") +"Comment of the form ````" + +rest_of_line = Regex(r".*").leave_whitespace().set_name("rest of line") +dbl_slash_comment = Regex(r"//(?:\\\n|[^\n])*").set_name("// comment") +"Comment of the form ``// ... (to end of line)``" + +cpp_style_comment = Combine( + Regex(r"/\*(?:[^*]|\*(?!/))*") + "*/" | dbl_slash_comment +).set_name("C++ style comment") +"Comment of either form :class:`c_style_comment` or :class:`dbl_slash_comment`" + +java_style_comment = cpp_style_comment +"Same as :class:`cpp_style_comment`" + +python_style_comment = Regex(r"#.*").set_name("Python style comment") +"Comment of the form ``# ... (to end of line)``" + + +# build list of built-in expressions, for future reference if a global default value +# gets updated +_builtin_exprs: List[ParserElement] = [ + v for v in vars().values() if isinstance(v, ParserElement) +] + + +# compatibility function, superseded by DelimitedList class +def delimited_list( + expr: Union[str, ParserElement], + delim: Union[str, ParserElement] = ",", + combine: bool = False, + min: typing.Optional[int] = None, + max: typing.Optional[int] = None, + *, + allow_trailing_delim: bool = False, +) -> ParserElement: + """(DEPRECATED - use :class:`DelimitedList` class)""" + return DelimitedList( + expr, delim, combine, min, max, allow_trailing_delim=allow_trailing_delim + ) + + +# pre-PEP8 compatible names +# fmt: off +opAssoc = OpAssoc +anyOpenTag = any_open_tag +anyCloseTag = any_close_tag +commonHTMLEntity = common_html_entity +cStyleComment = c_style_comment +htmlComment = html_comment +restOfLine = rest_of_line +dblSlashComment = dbl_slash_comment +cppStyleComment = cpp_style_comment +javaStyleComment = java_style_comment +pythonStyleComment = python_style_comment + +@replaced_by_pep8(DelimitedList) +def delimitedList(): ... + +@replaced_by_pep8(DelimitedList) +def delimited_list(): ... + +@replaced_by_pep8(counted_array) +def countedArray(): ... + +@replaced_by_pep8(match_previous_literal) +def matchPreviousLiteral(): ... + +@replaced_by_pep8(match_previous_expr) +def matchPreviousExpr(): ... + +@replaced_by_pep8(one_of) +def oneOf(): ... + +@replaced_by_pep8(dict_of) +def dictOf(): ... + +@replaced_by_pep8(original_text_for) +def originalTextFor(): ... + +@replaced_by_pep8(nested_expr) +def nestedExpr(): ... + +@replaced_by_pep8(make_html_tags) +def makeHTMLTags(): ... + +@replaced_by_pep8(make_xml_tags) +def makeXMLTags(): ... + +@replaced_by_pep8(replace_html_entity) +def replaceHTMLEntity(): ... + +@replaced_by_pep8(infix_notation) +def infixNotation(): ... +# fmt: on diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/results.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/results.py new file mode 100644 index 00000000..03130497 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/results.py @@ -0,0 +1,796 @@ +# results.py +from collections.abc import ( + MutableMapping, + Mapping, + MutableSequence, + Iterator, + Sequence, + Container, +) +import pprint +from typing import Tuple, Any, Dict, Set, List + +str_type: Tuple[type, ...] = (str, bytes) +_generator_type = type((_ for _ in ())) + + +class _ParseResultsWithOffset: + tup: Tuple["ParseResults", int] + __slots__ = ["tup"] + + def __init__(self, p1: "ParseResults", p2: int): + self.tup: Tuple[ParseResults, int] = (p1, p2) + + def __getitem__(self, i): + return self.tup[i] + + def __getstate__(self): + return self.tup + + def __setstate__(self, *args): + self.tup = args[0] + + +class ParseResults: + """Structured parse results, to provide multiple means of access to + the parsed data: + + - as a list (``len(results)``) + - by list index (``results[0], results[1]``, etc.) + - by attribute (``results.`` - see :class:`ParserElement.set_results_name`) + + Example:: + + integer = Word(nums) + date_str = (integer.set_results_name("year") + '/' + + integer.set_results_name("month") + '/' + + integer.set_results_name("day")) + # equivalent form: + # date_str = (integer("year") + '/' + # + integer("month") + '/' + # + integer("day")) + + # parse_string returns a ParseResults object + result = date_str.parse_string("1999/12/31") + + def test(s, fn=repr): + print(f"{s} -> {fn(eval(s))}") + test("list(result)") + test("result[0]") + test("result['month']") + test("result.day") + test("'month' in result") + test("'minutes' in result") + test("result.dump()", str) + + prints:: + + list(result) -> ['1999', '/', '12', '/', '31'] + result[0] -> '1999' + result['month'] -> '12' + result.day -> '31' + 'month' in result -> True + 'minutes' in result -> False + result.dump() -> ['1999', '/', '12', '/', '31'] + - day: '31' + - month: '12' + - year: '1999' + """ + + _null_values: Tuple[Any, ...] = (None, [], ()) + + _name: str + _parent: "ParseResults" + _all_names: Set[str] + _modal: bool + _toklist: List[Any] + _tokdict: Dict[str, Any] + + __slots__ = ( + "_name", + "_parent", + "_all_names", + "_modal", + "_toklist", + "_tokdict", + ) + + class List(list): + """ + Simple wrapper class to distinguish parsed list results that should be preserved + as actual Python lists, instead of being converted to :class:`ParseResults`:: + + LBRACK, RBRACK = map(pp.Suppress, "[]") + element = pp.Forward() + item = ppc.integer + element_list = LBRACK + pp.DelimitedList(element) + RBRACK + + # add parse actions to convert from ParseResults to actual Python collection types + def as_python_list(t): + return pp.ParseResults.List(t.as_list()) + element_list.add_parse_action(as_python_list) + + element <<= item | element_list + + element.run_tests(''' + 100 + [2,3,4] + [[2, 1],3,4] + [(2, 1),3,4] + (2,3,4) + ''', post_parse=lambda s, r: (r[0], type(r[0]))) + + prints:: + + 100 + (100, ) + + [2,3,4] + ([2, 3, 4], ) + + [[2, 1],3,4] + ([[2, 1], 3, 4], ) + + (Used internally by :class:`Group` when `aslist=True`.) + """ + + def __new__(cls, contained=None): + if contained is None: + contained = [] + + if not isinstance(contained, list): + raise TypeError( + f"{cls.__name__} may only be constructed with a list, not {type(contained).__name__}" + ) + + return list.__new__(cls) + + def __new__(cls, toklist=None, name=None, **kwargs): + if isinstance(toklist, ParseResults): + return toklist + self = object.__new__(cls) + self._name = None + self._parent = None + self._all_names = set() + + if toklist is None: + self._toklist = [] + elif isinstance(toklist, (list, _generator_type)): + self._toklist = ( + [toklist[:]] + if isinstance(toklist, ParseResults.List) + else list(toklist) + ) + else: + self._toklist = [toklist] + self._tokdict = dict() + return self + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( + self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance + ): + self._tokdict: Dict[str, _ParseResultsWithOffset] + self._modal = modal + if name is not None and name != "": + if isinstance(name, int): + name = str(name) + if not modal: + self._all_names = {name} + self._name = name + if toklist not in self._null_values: + if isinstance(toklist, (str_type, type)): + toklist = [toklist] + if asList: + if isinstance(toklist, ParseResults): + self[name] = _ParseResultsWithOffset( + ParseResults(toklist._toklist), 0 + ) + else: + self[name] = _ParseResultsWithOffset( + ParseResults(toklist[0]), 0 + ) + self[name]._name = name + else: + try: + self[name] = toklist[0] + except (KeyError, TypeError, IndexError): + if toklist is not self: + self[name] = toklist + else: + self._name = name + + def __getitem__(self, i): + if isinstance(i, (int, slice)): + return self._toklist[i] + else: + if i not in self._all_names: + return self._tokdict[i][-1][0] + else: + return ParseResults([v[0] for v in self._tokdict[i]]) + + def __setitem__(self, k, v, isinstance=isinstance): + if isinstance(v, _ParseResultsWithOffset): + self._tokdict[k] = self._tokdict.get(k, list()) + [v] + sub = v[0] + elif isinstance(k, (int, slice)): + self._toklist[k] = v + sub = v + else: + self._tokdict[k] = self._tokdict.get(k, list()) + [ + _ParseResultsWithOffset(v, 0) + ] + sub = v + if isinstance(sub, ParseResults): + sub._parent = self + + def __delitem__(self, i): + if isinstance(i, (int, slice)): + mylen = len(self._toklist) + del self._toklist[i] + + # convert int to slice + if isinstance(i, int): + if i < 0: + i += mylen + i = slice(i, i + 1) + # get removed indices + removed = list(range(*i.indices(mylen))) + removed.reverse() + # fixup indices in token dictionary + for name, occurrences in self._tokdict.items(): + for j in removed: + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset( + value, position - (position > j) + ) + else: + del self._tokdict[i] + + def __contains__(self, k) -> bool: + return k in self._tokdict + + def __len__(self) -> int: + return len(self._toklist) + + def __bool__(self) -> bool: + return not not (self._toklist or self._tokdict) + + def __iter__(self) -> Iterator: + return iter(self._toklist) + + def __reversed__(self) -> Iterator: + return iter(self._toklist[::-1]) + + def keys(self): + return iter(self._tokdict) + + def values(self): + return (self[k] for k in self.keys()) + + def items(self): + return ((k, self[k]) for k in self.keys()) + + def haskeys(self) -> bool: + """ + Since ``keys()`` returns an iterator, this method is helpful in bypassing + code that looks for the existence of any defined results names.""" + return not not self._tokdict + + def pop(self, *args, **kwargs): + """ + Removes and returns item at specified index (default= ``last``). + Supports both ``list`` and ``dict`` semantics for ``pop()``. If + passed no argument or an integer argument, it will use ``list`` + semantics and pop tokens from the list of parsed tokens. If passed + a non-integer argument (most likely a string), it will use ``dict`` + semantics and pop the corresponding value from any defined results + names. A second default return value argument is supported, just as in + ``dict.pop()``. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + + def remove_first(tokens): + tokens.pop(0) + numlist.add_parse_action(remove_first) + print(numlist.parse_string("0 123 321")) # -> ['123', '321'] + + label = Word(alphas) + patt = label("LABEL") + Word(nums)[1, ...] + print(patt.parse_string("AAB 123 321").dump()) + + # Use pop() in a parse action to remove named result (note that corresponding value is not + # removed from list form of results) + def remove_LABEL(tokens): + tokens.pop("LABEL") + return tokens + patt.add_parse_action(remove_LABEL) + print(patt.parse_string("AAB 123 321").dump()) + + prints:: + + ['AAB', '123', '321'] + - LABEL: 'AAB' + + ['AAB', '123', '321'] + """ + if not args: + args = [-1] + for k, v in kwargs.items(): + if k == "default": + args = (args[0], v) + else: + raise TypeError(f"pop() got an unexpected keyword argument {k!r}") + if isinstance(args[0], int) or len(args) == 1 or args[0] in self: + index = args[0] + ret = self[index] + del self[index] + return ret + else: + defaultvalue = args[1] + return defaultvalue + + def get(self, key, default_value=None): + """ + Returns named result matching the given key, or if there is no + such name, then returns the given ``default_value`` or ``None`` if no + ``default_value`` is specified. + + Similar to ``dict.get()``. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string("1999/12/31") + print(result.get("year")) # -> '1999' + print(result.get("hour", "not specified")) # -> 'not specified' + print(result.get("hour")) # -> None + """ + if key in self: + return self[key] + else: + return default_value + + def insert(self, index, ins_string): + """ + Inserts new element at location index in the list of parsed tokens. + + Similar to ``list.insert()``. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + + # use a parse action to insert the parse location in the front of the parsed results + def insert_locn(locn, tokens): + tokens.insert(0, locn) + numlist.add_parse_action(insert_locn) + print(numlist.parse_string("0 123 321")) # -> [0, '0', '123', '321'] + """ + self._toklist.insert(index, ins_string) + # fixup indices in token dictionary + for name, occurrences in self._tokdict.items(): + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset( + value, position + (position > index) + ) + + def append(self, item): + """ + Add single element to end of ``ParseResults`` list of elements. + + Example:: + + numlist = Word(nums)[...] + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321'] + + # use a parse action to compute the sum of the parsed integers, and add it to the end + def append_sum(tokens): + tokens.append(sum(map(int, tokens))) + numlist.add_parse_action(append_sum) + print(numlist.parse_string("0 123 321")) # -> ['0', '123', '321', 444] + """ + self._toklist.append(item) + + def extend(self, itemseq): + """ + Add sequence of elements to end of ``ParseResults`` list of elements. + + Example:: + + patt = Word(alphas)[1, ...] + + # use a parse action to append the reverse of the matched strings, to make a palindrome + def make_palindrome(tokens): + tokens.extend(reversed([t[::-1] for t in tokens])) + return ''.join(tokens) + patt.add_parse_action(make_palindrome) + print(patt.parse_string("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' + """ + if isinstance(itemseq, ParseResults): + self.__iadd__(itemseq) + else: + self._toklist.extend(itemseq) + + def clear(self): + """ + Clear all elements and results names. + """ + del self._toklist[:] + self._tokdict.clear() + + def __getattr__(self, name): + try: + return self[name] + except KeyError: + if name.startswith("__"): + raise AttributeError(name) + return "" + + def __add__(self, other: "ParseResults") -> "ParseResults": + ret = self.copy() + ret += other + return ret + + def __iadd__(self, other: "ParseResults") -> "ParseResults": + if not other: + return self + + if other._tokdict: + offset = len(self._toklist) + addoffset = lambda a: offset if a < 0 else a + offset + otheritems = other._tokdict.items() + otherdictitems = [ + (k, _ParseResultsWithOffset(v[0], addoffset(v[1]))) + for k, vlist in otheritems + for v in vlist + ] + for k, v in otherdictitems: + self[k] = v + if isinstance(v[0], ParseResults): + v[0]._parent = self + + self._toklist += other._toklist + self._all_names |= other._all_names + return self + + def __radd__(self, other) -> "ParseResults": + if isinstance(other, int) and other == 0: + # useful for merging many ParseResults using sum() builtin + return self.copy() + else: + # this may raise a TypeError - so be it + return other + self + + def __repr__(self) -> str: + return f"{type(self).__name__}({self._toklist!r}, {self.as_dict()})" + + def __str__(self) -> str: + return ( + "[" + + ", ".join( + [ + str(i) if isinstance(i, ParseResults) else repr(i) + for i in self._toklist + ] + ) + + "]" + ) + + def _asStringList(self, sep=""): + out = [] + for item in self._toklist: + if out and sep: + out.append(sep) + if isinstance(item, ParseResults): + out += item._asStringList() + else: + out.append(str(item)) + return out + + def as_list(self) -> list: + """ + Returns the parse results as a nested list of matching tokens, all converted to strings. + + Example:: + + patt = Word(alphas)[1, ...] + result = patt.parse_string("sldkj lsdkj sldkj") + # even though the result prints in string-like form, it is actually a pyparsing ParseResults + print(type(result), result) # -> ['sldkj', 'lsdkj', 'sldkj'] + + # Use as_list() to create an actual list + result_list = result.as_list() + print(type(result_list), result_list) # -> ['sldkj', 'lsdkj', 'sldkj'] + """ + return [ + res.as_list() if isinstance(res, ParseResults) else res + for res in self._toklist + ] + + def as_dict(self) -> dict: + """ + Returns the named parse results as a nested dictionary. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string('12/31/1999') + print(type(result), repr(result)) # -> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]}) + + result_dict = result.as_dict() + print(type(result_dict), repr(result_dict)) # -> {'day': '1999', 'year': '12', 'month': '31'} + + # even though a ParseResults supports dict-like access, sometime you just need to have a dict + import json + print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable + print(json.dumps(result.as_dict())) # -> {"month": "31", "day": "1999", "year": "12"} + """ + + def to_item(obj): + if isinstance(obj, ParseResults): + return obj.as_dict() if obj.haskeys() else [to_item(v) for v in obj] + else: + return obj + + return dict((k, to_item(v)) for k, v in self.items()) + + def copy(self) -> "ParseResults": + """ + Returns a new shallow copy of a :class:`ParseResults` object. `ParseResults` + items contained within the source are shared with the copy. Use + :class:`ParseResults.deepcopy()` to create a copy with its own separate + content values. + """ + ret = ParseResults(self._toklist) + ret._tokdict = self._tokdict.copy() + ret._parent = self._parent + ret._all_names |= self._all_names + ret._name = self._name + return ret + + def deepcopy(self) -> "ParseResults": + """ + Returns a new deep copy of a :class:`ParseResults` object. + """ + ret = self.copy() + # replace values with copies if they are of known mutable types + for i, obj in enumerate(self._toklist): + if isinstance(obj, ParseResults): + self._toklist[i] = obj.deepcopy() + elif isinstance(obj, (str, bytes)): + pass + elif isinstance(obj, MutableMapping): + self._toklist[i] = dest = type(obj)() + for k, v in obj.items(): + dest[k] = v.deepcopy() if isinstance(v, ParseResults) else v + elif isinstance(obj, Container): + self._toklist[i] = type(obj)( + v.deepcopy() if isinstance(v, ParseResults) else v for v in obj + ) + return ret + + def get_name(self): + r""" + Returns the results name for this token expression. Useful when several + different expressions might match at a particular location. + + Example:: + + integer = Word(nums) + ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d") + house_number_expr = Suppress('#') + Word(nums, alphanums) + user_data = (Group(house_number_expr)("house_number") + | Group(ssn_expr)("ssn") + | Group(integer)("age")) + user_info = user_data[1, ...] + + result = user_info.parse_string("22 111-22-3333 #221B") + for item in result: + print(item.get_name(), ':', item[0]) + + prints:: + + age : 22 + ssn : 111-22-3333 + house_number : 221B + """ + if self._name: + return self._name + elif self._parent: + par: "ParseResults" = self._parent + parent_tokdict_items = par._tokdict.items() + return next( + ( + k + for k, vlist in parent_tokdict_items + for v, loc in vlist + if v is self + ), + None, + ) + elif ( + len(self) == 1 + and len(self._tokdict) == 1 + and next(iter(self._tokdict.values()))[0][1] in (0, -1) + ): + return next(iter(self._tokdict.keys())) + else: + return None + + def dump(self, indent="", full=True, include_list=True, _depth=0) -> str: + """ + Diagnostic method for listing out the contents of + a :class:`ParseResults`. Accepts an optional ``indent`` argument so + that this string can be embedded in a nested display of other data. + + Example:: + + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parse_string('1999/12/31') + print(result.dump()) + + prints:: + + ['1999', '/', '12', '/', '31'] + - day: '31' + - month: '12' + - year: '1999' + """ + out = [] + NL = "\n" + out.append(indent + str(self.as_list()) if include_list else "") + + if full: + if self.haskeys(): + items = sorted((str(k), v) for k, v in self.items()) + for k, v in items: + if out: + out.append(NL) + out.append(f"{indent}{(' ' * _depth)}- {k}: ") + if isinstance(v, ParseResults): + if v: + out.append( + v.dump( + indent=indent, + full=full, + include_list=include_list, + _depth=_depth + 1, + ) + ) + else: + out.append(str(v)) + else: + out.append(repr(v)) + if any(isinstance(vv, ParseResults) for vv in self): + v = self + for i, vv in enumerate(v): + if isinstance(vv, ParseResults): + out.append( + "\n{}{}[{}]:\n{}{}{}".format( + indent, + (" " * (_depth)), + i, + indent, + (" " * (_depth + 1)), + vv.dump( + indent=indent, + full=full, + include_list=include_list, + _depth=_depth + 1, + ), + ) + ) + else: + out.append( + "\n%s%s[%d]:\n%s%s%s" + % ( + indent, + (" " * (_depth)), + i, + indent, + (" " * (_depth + 1)), + str(vv), + ) + ) + + return "".join(out) + + def pprint(self, *args, **kwargs): + """ + Pretty-printer for parsed results as a list, using the + `pprint `_ module. + Accepts additional positional or keyword args as defined for + `pprint.pprint `_ . + + Example:: + + ident = Word(alphas, alphanums) + num = Word(nums) + func = Forward() + term = ident | num | Group('(' + func + ')') + func <<= ident + Group(Optional(DelimitedList(term))) + result = func.parse_string("fna a,b,(fnb c,d,200),100") + result.pprint(width=40) + + prints:: + + ['fna', + ['a', + 'b', + ['(', 'fnb', ['c', 'd', '200'], ')'], + '100']] + """ + pprint.pprint(self.as_list(), *args, **kwargs) + + # add support for pickle protocol + def __getstate__(self): + return ( + self._toklist, + ( + self._tokdict.copy(), + None, + self._all_names, + self._name, + ), + ) + + def __setstate__(self, state): + self._toklist, (self._tokdict, par, inAccumNames, self._name) = state + self._all_names = set(inAccumNames) + self._parent = None + + def __getnewargs__(self): + return self._toklist, self._name + + def __dir__(self): + return dir(type(self)) + list(self.keys()) + + @classmethod + def from_dict(cls, other, name=None) -> "ParseResults": + """ + Helper classmethod to construct a ``ParseResults`` from a ``dict``, preserving the + name-value relations as results names. If an optional ``name`` argument is + given, a nested ``ParseResults`` will be returned. + """ + + def is_iterable(obj): + try: + iter(obj) + except Exception: + return False + # str's are iterable, but in pyparsing, we don't want to iterate over them + else: + return not isinstance(obj, str_type) + + ret = cls([]) + for k, v in other.items(): + if isinstance(v, Mapping): + ret += cls.from_dict(v, name=k) + else: + ret += cls([v], name=k, asList=is_iterable(v)) + if name is not None: + ret = cls([ret], name=name) + return ret + + asList = as_list + """Deprecated - use :class:`as_list`""" + asDict = as_dict + """Deprecated - use :class:`as_dict`""" + getName = get_name + """Deprecated - use :class:`get_name`""" + + +MutableMapping.register(ParseResults) +MutableSequence.register(ParseResults) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/testing.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/testing.py new file mode 100644 index 00000000..6a254c1c --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/testing.py @@ -0,0 +1,331 @@ +# testing.py + +from contextlib import contextmanager +import typing + +from .core import ( + ParserElement, + ParseException, + Keyword, + __diag__, + __compat__, +) + + +class pyparsing_test: + """ + namespace class for classes useful in writing unit tests + """ + + class reset_pyparsing_context: + """ + Context manager to be used when writing unit tests that modify pyparsing config values: + - packrat parsing + - bounded recursion parsing + - default whitespace characters. + - default keyword characters + - literal string auto-conversion class + - __diag__ settings + + Example:: + + with reset_pyparsing_context(): + # test that literals used to construct a grammar are automatically suppressed + ParserElement.inlineLiteralsUsing(Suppress) + + term = Word(alphas) | Word(nums) + group = Group('(' + term[...] + ')') + + # assert that the '()' characters are not included in the parsed tokens + self.assertParseAndCheckList(group, "(abc 123 def)", ['abc', '123', 'def']) + + # after exiting context manager, literals are converted to Literal expressions again + """ + + def __init__(self): + self._save_context = {} + + def save(self): + self._save_context["default_whitespace"] = ParserElement.DEFAULT_WHITE_CHARS + self._save_context["default_keyword_chars"] = Keyword.DEFAULT_KEYWORD_CHARS + + self._save_context[ + "literal_string_class" + ] = ParserElement._literalStringClass + + self._save_context["verbose_stacktrace"] = ParserElement.verbose_stacktrace + + self._save_context["packrat_enabled"] = ParserElement._packratEnabled + if ParserElement._packratEnabled: + self._save_context[ + "packrat_cache_size" + ] = ParserElement.packrat_cache.size + else: + self._save_context["packrat_cache_size"] = None + self._save_context["packrat_parse"] = ParserElement._parse + self._save_context[ + "recursion_enabled" + ] = ParserElement._left_recursion_enabled + + self._save_context["__diag__"] = { + name: getattr(__diag__, name) for name in __diag__._all_names + } + + self._save_context["__compat__"] = { + "collect_all_And_tokens": __compat__.collect_all_And_tokens + } + + return self + + def restore(self): + # reset pyparsing global state + if ( + ParserElement.DEFAULT_WHITE_CHARS + != self._save_context["default_whitespace"] + ): + ParserElement.set_default_whitespace_chars( + self._save_context["default_whitespace"] + ) + + ParserElement.verbose_stacktrace = self._save_context["verbose_stacktrace"] + + Keyword.DEFAULT_KEYWORD_CHARS = self._save_context["default_keyword_chars"] + ParserElement.inlineLiteralsUsing( + self._save_context["literal_string_class"] + ) + + for name, value in self._save_context["__diag__"].items(): + (__diag__.enable if value else __diag__.disable)(name) + + ParserElement._packratEnabled = False + if self._save_context["packrat_enabled"]: + ParserElement.enable_packrat(self._save_context["packrat_cache_size"]) + else: + ParserElement._parse = self._save_context["packrat_parse"] + ParserElement._left_recursion_enabled = self._save_context[ + "recursion_enabled" + ] + + __compat__.collect_all_And_tokens = self._save_context["__compat__"] + + return self + + def copy(self): + ret = type(self)() + ret._save_context.update(self._save_context) + return ret + + def __enter__(self): + return self.save() + + def __exit__(self, *args): + self.restore() + + class TestParseResultsAsserts: + """ + A mixin class to add parse results assertion methods to normal unittest.TestCase classes. + """ + + def assertParseResultsEquals( + self, result, expected_list=None, expected_dict=None, msg=None + ): + """ + Unit test assertion to compare a :class:`ParseResults` object with an optional ``expected_list``, + and compare any defined results names with an optional ``expected_dict``. + """ + if expected_list is not None: + self.assertEqual(expected_list, result.as_list(), msg=msg) + if expected_dict is not None: + self.assertEqual(expected_dict, result.as_dict(), msg=msg) + + def assertParseAndCheckList( + self, expr, test_string, expected_list, msg=None, verbose=True + ): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ``ParseResults.asList()`` is equal to the ``expected_list``. + """ + result = expr.parse_string(test_string, parse_all=True) + if verbose: + print(result.dump()) + else: + print(result.as_list()) + self.assertParseResultsEquals(result, expected_list=expected_list, msg=msg) + + def assertParseAndCheckDict( + self, expr, test_string, expected_dict, msg=None, verbose=True + ): + """ + Convenience wrapper assert to test a parser element and input string, and assert that + the resulting ``ParseResults.asDict()`` is equal to the ``expected_dict``. + """ + result = expr.parse_string(test_string, parseAll=True) + if verbose: + print(result.dump()) + else: + print(result.as_list()) + self.assertParseResultsEquals(result, expected_dict=expected_dict, msg=msg) + + def assertRunTestResults( + self, run_tests_report, expected_parse_results=None, msg=None + ): + """ + Unit test assertion to evaluate output of ``ParserElement.runTests()``. If a list of + list-dict tuples is given as the ``expected_parse_results`` argument, then these are zipped + with the report tuples returned by ``runTests`` and evaluated using ``assertParseResultsEquals``. + Finally, asserts that the overall ``runTests()`` success value is ``True``. + + :param run_tests_report: tuple(bool, [tuple(str, ParseResults or Exception)]) returned from runTests + :param expected_parse_results (optional): [tuple(str, list, dict, Exception)] + """ + run_test_success, run_test_results = run_tests_report + + if expected_parse_results is not None: + merged = [ + (*rpt, expected) + for rpt, expected in zip(run_test_results, expected_parse_results) + ] + for test_string, result, expected in merged: + # expected should be a tuple containing a list and/or a dict or an exception, + # and optional failure message string + # an empty tuple will skip any result validation + fail_msg = next( + (exp for exp in expected if isinstance(exp, str)), None + ) + expected_exception = next( + ( + exp + for exp in expected + if isinstance(exp, type) and issubclass(exp, Exception) + ), + None, + ) + if expected_exception is not None: + with self.assertRaises( + expected_exception=expected_exception, msg=fail_msg or msg + ): + if isinstance(result, Exception): + raise result + else: + expected_list = next( + (exp for exp in expected if isinstance(exp, list)), None + ) + expected_dict = next( + (exp for exp in expected if isinstance(exp, dict)), None + ) + if (expected_list, expected_dict) != (None, None): + self.assertParseResultsEquals( + result, + expected_list=expected_list, + expected_dict=expected_dict, + msg=fail_msg or msg, + ) + else: + # warning here maybe? + print(f"no validation for {test_string!r}") + + # do this last, in case some specific test results can be reported instead + self.assertTrue( + run_test_success, msg=msg if msg is not None else "failed runTests" + ) + + @contextmanager + def assertRaisesParseException(self, exc_type=ParseException, msg=None): + with self.assertRaises(exc_type, msg=msg): + yield + + @staticmethod + def with_line_numbers( + s: str, + start_line: typing.Optional[int] = None, + end_line: typing.Optional[int] = None, + expand_tabs: bool = True, + eol_mark: str = "|", + mark_spaces: typing.Optional[str] = None, + mark_control: typing.Optional[str] = None, + ) -> str: + """ + Helpful method for debugging a parser - prints a string with line and column numbers. + (Line and column numbers are 1-based.) + + :param s: tuple(bool, str - string to be printed with line and column numbers + :param start_line: int - (optional) starting line number in s to print (default=1) + :param end_line: int - (optional) ending line number in s to print (default=len(s)) + :param expand_tabs: bool - (optional) expand tabs to spaces, to match the pyparsing default + :param eol_mark: str - (optional) string to mark the end of lines, helps visualize trailing spaces (default="|") + :param mark_spaces: str - (optional) special character to display in place of spaces + :param mark_control: str - (optional) convert non-printing control characters to a placeholding + character; valid values: + - "unicode" - replaces control chars with Unicode symbols, such as "␍" and "␊" + - any single character string - replace control characters with given string + - None (default) - string is displayed as-is + + :return: str - input string with leading line numbers and column number headers + """ + if expand_tabs: + s = s.expandtabs() + if mark_control is not None: + mark_control = typing.cast(str, mark_control) + if mark_control == "unicode": + transtable_map = { + c: u for c, u in zip(range(0, 33), range(0x2400, 0x2433)) + } + transtable_map[127] = 0x2421 + tbl = str.maketrans(transtable_map) + eol_mark = "" + else: + ord_mark_control = ord(mark_control) + tbl = str.maketrans( + {c: ord_mark_control for c in list(range(0, 32)) + [127]} + ) + s = s.translate(tbl) + if mark_spaces is not None and mark_spaces != " ": + if mark_spaces == "unicode": + tbl = str.maketrans({9: 0x2409, 32: 0x2423}) + s = s.translate(tbl) + else: + s = s.replace(" ", mark_spaces) + if start_line is None: + start_line = 1 + if end_line is None: + end_line = len(s) + end_line = min(end_line, len(s)) + start_line = min(max(1, start_line), end_line) + + if mark_control != "unicode": + s_lines = s.splitlines()[start_line - 1 : end_line] + else: + s_lines = [line + "␊" for line in s.split("␊")[start_line - 1 : end_line]] + if not s_lines: + return "" + + lineno_width = len(str(end_line)) + max_line_len = max(len(line) for line in s_lines) + lead = " " * (lineno_width + 1) + if max_line_len >= 99: + header0 = ( + lead + + "".join( + f"{' ' * 99}{(i + 1) % 100}" + for i in range(max(max_line_len // 100, 1)) + ) + + "\n" + ) + else: + header0 = "" + header1 = ( + header0 + + lead + + "".join(f" {(i + 1) % 10}" for i in range(-(-max_line_len // 10))) + + "\n" + ) + header2 = lead + "1234567890" * (-(-max_line_len // 10)) + "\n" + return ( + header1 + + header2 + + "\n".join( + f"{i:{lineno_width}d}:{line}{eol_mark}" + for i, line in enumerate(s_lines, start=start_line) + ) + + "\n" + ) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/unicode.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/unicode.py new file mode 100644 index 00000000..ec0b3a4f --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/unicode.py @@ -0,0 +1,361 @@ +# unicode.py + +import sys +from itertools import filterfalse +from typing import List, Tuple, Union + + +class _lazyclassproperty: + def __init__(self, fn): + self.fn = fn + self.__doc__ = fn.__doc__ + self.__name__ = fn.__name__ + + def __get__(self, obj, cls): + if cls is None: + cls = type(obj) + if not hasattr(cls, "_intern") or any( + cls._intern is getattr(superclass, "_intern", []) + for superclass in cls.__mro__[1:] + ): + cls._intern = {} + attrname = self.fn.__name__ + if attrname not in cls._intern: + cls._intern[attrname] = self.fn(cls) + return cls._intern[attrname] + + +UnicodeRangeList = List[Union[Tuple[int, int], Tuple[int]]] + + +class unicode_set: + """ + A set of Unicode characters, for language-specific strings for + ``alphas``, ``nums``, ``alphanums``, and ``printables``. + A unicode_set is defined by a list of ranges in the Unicode character + set, in a class attribute ``_ranges``. Ranges can be specified using + 2-tuples or a 1-tuple, such as:: + + _ranges = [ + (0x0020, 0x007e), + (0x00a0, 0x00ff), + (0x0100,), + ] + + Ranges are left- and right-inclusive. A 1-tuple of (x,) is treated as (x, x). + + A unicode set can also be defined using multiple inheritance of other unicode sets:: + + class CJK(Chinese, Japanese, Korean): + pass + """ + + _ranges: UnicodeRangeList = [] + + @_lazyclassproperty + def _chars_for_ranges(cls): + ret = [] + for cc in cls.__mro__: + if cc is unicode_set: + break + for rr in getattr(cc, "_ranges", ()): + ret.extend(range(rr[0], rr[-1] + 1)) + return [chr(c) for c in sorted(set(ret))] + + @_lazyclassproperty + def printables(cls): + """all non-whitespace characters in this range""" + return "".join(filterfalse(str.isspace, cls._chars_for_ranges)) + + @_lazyclassproperty + def alphas(cls): + """all alphabetic characters in this range""" + return "".join(filter(str.isalpha, cls._chars_for_ranges)) + + @_lazyclassproperty + def nums(cls): + """all numeric digit characters in this range""" + return "".join(filter(str.isdigit, cls._chars_for_ranges)) + + @_lazyclassproperty + def alphanums(cls): + """all alphanumeric characters in this range""" + return cls.alphas + cls.nums + + @_lazyclassproperty + def identchars(cls): + """all characters in this range that are valid identifier characters, plus underscore '_'""" + return "".join( + sorted( + set( + "".join(filter(str.isidentifier, cls._chars_for_ranges)) + + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzªµº" + + "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ" + + "_" + ) + ) + ) + + @_lazyclassproperty + def identbodychars(cls): + """ + all characters in this range that are valid identifier body characters, + plus the digits 0-9, and · (Unicode MIDDLE DOT) + """ + return "".join( + sorted( + set( + cls.identchars + + "0123456789·" + + "".join( + [c for c in cls._chars_for_ranges if ("_" + c).isidentifier()] + ) + ) + ) + ) + + @_lazyclassproperty + def identifier(cls): + """ + a pyparsing Word expression for an identifier using this range's definitions for + identchars and identbodychars + """ + from pip._vendor.pyparsing import Word + + return Word(cls.identchars, cls.identbodychars) + + +class pyparsing_unicode(unicode_set): + """ + A namespace class for defining common language unicode_sets. + """ + + # fmt: off + + # define ranges in language character sets + _ranges: UnicodeRangeList = [ + (0x0020, sys.maxunicode), + ] + + class BasicMultilingualPlane(unicode_set): + """Unicode set for the Basic Multilingual Plane""" + _ranges: UnicodeRangeList = [ + (0x0020, 0xFFFF), + ] + + class Latin1(unicode_set): + """Unicode set for Latin-1 Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0020, 0x007E), + (0x00A0, 0x00FF), + ] + + class LatinA(unicode_set): + """Unicode set for Latin-A Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0100, 0x017F), + ] + + class LatinB(unicode_set): + """Unicode set for Latin-B Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0180, 0x024F), + ] + + class Greek(unicode_set): + """Unicode set for Greek Unicode Character Ranges""" + _ranges: UnicodeRangeList = [ + (0x0342, 0x0345), + (0x0370, 0x0377), + (0x037A, 0x037F), + (0x0384, 0x038A), + (0x038C,), + (0x038E, 0x03A1), + (0x03A3, 0x03E1), + (0x03F0, 0x03FF), + (0x1D26, 0x1D2A), + (0x1D5E,), + (0x1D60,), + (0x1D66, 0x1D6A), + (0x1F00, 0x1F15), + (0x1F18, 0x1F1D), + (0x1F20, 0x1F45), + (0x1F48, 0x1F4D), + (0x1F50, 0x1F57), + (0x1F59,), + (0x1F5B,), + (0x1F5D,), + (0x1F5F, 0x1F7D), + (0x1F80, 0x1FB4), + (0x1FB6, 0x1FC4), + (0x1FC6, 0x1FD3), + (0x1FD6, 0x1FDB), + (0x1FDD, 0x1FEF), + (0x1FF2, 0x1FF4), + (0x1FF6, 0x1FFE), + (0x2129,), + (0x2719, 0x271A), + (0xAB65,), + (0x10140, 0x1018D), + (0x101A0,), + (0x1D200, 0x1D245), + (0x1F7A1, 0x1F7A7), + ] + + class Cyrillic(unicode_set): + """Unicode set for Cyrillic Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0400, 0x052F), + (0x1C80, 0x1C88), + (0x1D2B,), + (0x1D78,), + (0x2DE0, 0x2DFF), + (0xA640, 0xA672), + (0xA674, 0xA69F), + (0xFE2E, 0xFE2F), + ] + + class Chinese(unicode_set): + """Unicode set for Chinese Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x2E80, 0x2E99), + (0x2E9B, 0x2EF3), + (0x31C0, 0x31E3), + (0x3400, 0x4DB5), + (0x4E00, 0x9FEF), + (0xA700, 0xA707), + (0xF900, 0xFA6D), + (0xFA70, 0xFAD9), + (0x16FE2, 0x16FE3), + (0x1F210, 0x1F212), + (0x1F214, 0x1F23B), + (0x1F240, 0x1F248), + (0x20000, 0x2A6D6), + (0x2A700, 0x2B734), + (0x2B740, 0x2B81D), + (0x2B820, 0x2CEA1), + (0x2CEB0, 0x2EBE0), + (0x2F800, 0x2FA1D), + ] + + class Japanese(unicode_set): + """Unicode set for Japanese Unicode Character Range, combining Kanji, Hiragana, and Katakana ranges""" + + class Kanji(unicode_set): + "Unicode set for Kanji Unicode Character Range" + _ranges: UnicodeRangeList = [ + (0x4E00, 0x9FBF), + (0x3000, 0x303F), + ] + + class Hiragana(unicode_set): + """Unicode set for Hiragana Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x3041, 0x3096), + (0x3099, 0x30A0), + (0x30FC,), + (0xFF70,), + (0x1B001,), + (0x1B150, 0x1B152), + (0x1F200,), + ] + + class Katakana(unicode_set): + """Unicode set for Katakana Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x3099, 0x309C), + (0x30A0, 0x30FF), + (0x31F0, 0x31FF), + (0x32D0, 0x32FE), + (0xFF65, 0xFF9F), + (0x1B000,), + (0x1B164, 0x1B167), + (0x1F201, 0x1F202), + (0x1F213,), + ] + + 漢字 = Kanji + カタカナ = Katakana + ひらがな = Hiragana + + _ranges = ( + Kanji._ranges + + Hiragana._ranges + + Katakana._ranges + ) + + class Hangul(unicode_set): + """Unicode set for Hangul (Korean) Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x1100, 0x11FF), + (0x302E, 0x302F), + (0x3131, 0x318E), + (0x3200, 0x321C), + (0x3260, 0x327B), + (0x327E,), + (0xA960, 0xA97C), + (0xAC00, 0xD7A3), + (0xD7B0, 0xD7C6), + (0xD7CB, 0xD7FB), + (0xFFA0, 0xFFBE), + (0xFFC2, 0xFFC7), + (0xFFCA, 0xFFCF), + (0xFFD2, 0xFFD7), + (0xFFDA, 0xFFDC), + ] + + Korean = Hangul + + class CJK(Chinese, Japanese, Hangul): + """Unicode set for combined Chinese, Japanese, and Korean (CJK) Unicode Character Range""" + + class Thai(unicode_set): + """Unicode set for Thai Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0E01, 0x0E3A), + (0x0E3F, 0x0E5B) + ] + + class Arabic(unicode_set): + """Unicode set for Arabic Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0600, 0x061B), + (0x061E, 0x06FF), + (0x0700, 0x077F), + ] + + class Hebrew(unicode_set): + """Unicode set for Hebrew Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0591, 0x05C7), + (0x05D0, 0x05EA), + (0x05EF, 0x05F4), + (0xFB1D, 0xFB36), + (0xFB38, 0xFB3C), + (0xFB3E,), + (0xFB40, 0xFB41), + (0xFB43, 0xFB44), + (0xFB46, 0xFB4F), + ] + + class Devanagari(unicode_set): + """Unicode set for Devanagari Unicode Character Range""" + _ranges: UnicodeRangeList = [ + (0x0900, 0x097F), + (0xA8E0, 0xA8FF) + ] + + BMP = BasicMultilingualPlane + + # add language identifiers using language Unicode + العربية = Arabic + 中文 = Chinese + кириллица = Cyrillic + Ελληνικά = Greek + עִברִית = Hebrew + 日本語 = Japanese + 한국어 = Korean + ไทย = Thai + देवनागरी = Devanagari + + # fmt: on diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/util.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/util.py new file mode 100644 index 00000000..d8d3f414 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyparsing/util.py @@ -0,0 +1,284 @@ +# util.py +import inspect +import warnings +import types +import collections +import itertools +from functools import lru_cache, wraps +from typing import Callable, List, Union, Iterable, TypeVar, cast + +_bslash = chr(92) +C = TypeVar("C", bound=Callable) + + +class __config_flags: + """Internal class for defining compatibility and debugging flags""" + + _all_names: List[str] = [] + _fixed_names: List[str] = [] + _type_desc = "configuration" + + @classmethod + def _set(cls, dname, value): + if dname in cls._fixed_names: + warnings.warn( + f"{cls.__name__}.{dname} {cls._type_desc} is {str(getattr(cls, dname)).upper()}" + f" and cannot be overridden", + stacklevel=3, + ) + return + if dname in cls._all_names: + setattr(cls, dname, value) + else: + raise ValueError(f"no such {cls._type_desc} {dname!r}") + + enable = classmethod(lambda cls, name: cls._set(name, True)) + disable = classmethod(lambda cls, name: cls._set(name, False)) + + +@lru_cache(maxsize=128) +def col(loc: int, strg: str) -> int: + """ + Returns current column within a string, counting newlines as line separators. + The first column is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See + :class:`ParserElement.parse_string` for more + information on parsing strings containing ```` s, and suggested + methods to maintain a consistent view of the parsed string, the parse + location, and line and column positions within the parsed string. + """ + s = strg + return 1 if 0 < loc < len(s) and s[loc - 1] == "\n" else loc - s.rfind("\n", 0, loc) + + +@lru_cache(maxsize=128) +def lineno(loc: int, strg: str) -> int: + """Returns current line number within a string, counting newlines as line separators. + The first line is number 1. + + Note - the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See :class:`ParserElement.parse_string` + for more information on parsing strings containing ```` s, and + suggested methods to maintain a consistent view of the parsed string, the + parse location, and line and column positions within the parsed string. + """ + return strg.count("\n", 0, loc) + 1 + + +@lru_cache(maxsize=128) +def line(loc: int, strg: str) -> str: + """ + Returns the line of text containing loc within a string, counting newlines as line separators. + """ + last_cr = strg.rfind("\n", 0, loc) + next_cr = strg.find("\n", loc) + return strg[last_cr + 1 : next_cr] if next_cr >= 0 else strg[last_cr + 1 :] + + +class _UnboundedCache: + def __init__(self): + cache = {} + cache_get = cache.get + self.not_in_cache = not_in_cache = object() + + def get(_, key): + return cache_get(key, not_in_cache) + + def set_(_, key, value): + cache[key] = value + + def clear(_): + cache.clear() + + self.size = None + self.get = types.MethodType(get, self) + self.set = types.MethodType(set_, self) + self.clear = types.MethodType(clear, self) + + +class _FifoCache: + def __init__(self, size): + self.not_in_cache = not_in_cache = object() + cache = {} + keyring = [object()] * size + cache_get = cache.get + cache_pop = cache.pop + keyiter = itertools.cycle(range(size)) + + def get(_, key): + return cache_get(key, not_in_cache) + + def set_(_, key, value): + cache[key] = value + i = next(keyiter) + cache_pop(keyring[i], None) + keyring[i] = key + + def clear(_): + cache.clear() + keyring[:] = [object()] * size + + self.size = size + self.get = types.MethodType(get, self) + self.set = types.MethodType(set_, self) + self.clear = types.MethodType(clear, self) + + +class LRUMemo: + """ + A memoizing mapping that retains `capacity` deleted items + + The memo tracks retained items by their access order; once `capacity` items + are retained, the least recently used item is discarded. + """ + + def __init__(self, capacity): + self._capacity = capacity + self._active = {} + self._memory = collections.OrderedDict() + + def __getitem__(self, key): + try: + return self._active[key] + except KeyError: + self._memory.move_to_end(key) + return self._memory[key] + + def __setitem__(self, key, value): + self._memory.pop(key, None) + self._active[key] = value + + def __delitem__(self, key): + try: + value = self._active.pop(key) + except KeyError: + pass + else: + while len(self._memory) >= self._capacity: + self._memory.popitem(last=False) + self._memory[key] = value + + def clear(self): + self._active.clear() + self._memory.clear() + + +class UnboundedMemo(dict): + """ + A memoizing mapping that retains all deleted items + """ + + def __delitem__(self, key): + pass + + +def _escape_regex_range_chars(s: str) -> str: + # escape these chars: ^-[] + for c in r"\^-[]": + s = s.replace(c, _bslash + c) + s = s.replace("\n", r"\n") + s = s.replace("\t", r"\t") + return str(s) + + +def _collapse_string_to_ranges( + s: Union[str, Iterable[str]], re_escape: bool = True +) -> str: + def is_consecutive(c): + c_int = ord(c) + is_consecutive.prev, prev = c_int, is_consecutive.prev + if c_int - prev > 1: + is_consecutive.value = next(is_consecutive.counter) + return is_consecutive.value + + is_consecutive.prev = 0 # type: ignore [attr-defined] + is_consecutive.counter = itertools.count() # type: ignore [attr-defined] + is_consecutive.value = -1 # type: ignore [attr-defined] + + def escape_re_range_char(c): + return "\\" + c if c in r"\^-][" else c + + def no_escape_re_range_char(c): + return c + + if not re_escape: + escape_re_range_char = no_escape_re_range_char + + ret = [] + s = "".join(sorted(set(s))) + if len(s) > 3: + for _, chars in itertools.groupby(s, key=is_consecutive): + first = last = next(chars) + last = collections.deque( + itertools.chain(iter([last]), chars), maxlen=1 + ).pop() + if first == last: + ret.append(escape_re_range_char(first)) + else: + sep = "" if ord(last) == ord(first) + 1 else "-" + ret.append( + f"{escape_re_range_char(first)}{sep}{escape_re_range_char(last)}" + ) + else: + ret = [escape_re_range_char(c) for c in s] + + return "".join(ret) + + +def _flatten(ll: list) -> list: + ret = [] + for i in ll: + if isinstance(i, list): + ret.extend(_flatten(i)) + else: + ret.append(i) + return ret + + +def _make_synonym_function(compat_name: str, fn: C) -> C: + # In a future version, uncomment the code in the internal _inner() functions + # to begin emitting DeprecationWarnings. + + # Unwrap staticmethod/classmethod + fn = getattr(fn, "__func__", fn) + + # (Presence of 'self' arg in signature is used by explain_exception() methods, so we take + # some extra steps to add it if present in decorated function.) + if "self" == list(inspect.signature(fn).parameters)[0]: + + @wraps(fn) + def _inner(self, *args, **kwargs): + # warnings.warn( + # f"Deprecated - use {fn.__name__}", DeprecationWarning, stacklevel=3 + # ) + return fn(self, *args, **kwargs) + + else: + + @wraps(fn) + def _inner(*args, **kwargs): + # warnings.warn( + # f"Deprecated - use {fn.__name__}", DeprecationWarning, stacklevel=3 + # ) + return fn(*args, **kwargs) + + _inner.__doc__ = f"""Deprecated - use :class:`{fn.__name__}`""" + _inner.__name__ = compat_name + _inner.__annotations__ = fn.__annotations__ + if isinstance(fn, types.FunctionType): + _inner.__kwdefaults__ = fn.__kwdefaults__ + elif isinstance(fn, type) and hasattr(fn, "__init__"): + _inner.__kwdefaults__ = fn.__init__.__kwdefaults__ + else: + _inner.__kwdefaults__ = None + _inner.__qualname__ = fn.__qualname__ + return cast(C, _inner) + + +def replaced_by_pep8(fn: C) -> Callable[[Callable], C]: + """ + Decorator for pre-PEP8 compatibility synonyms, to link them to the new function. + """ + return lambda other: _make_synonym_function(other.__name__, fn) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__init__.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__init__.py new file mode 100644 index 00000000..ddfcf7f7 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__init__.py @@ -0,0 +1,23 @@ +"""Wrappers to call pyproject.toml-based build backend hooks. +""" + +from ._impl import ( + BackendInvalid, + BackendUnavailable, + BuildBackendHookCaller, + HookMissing, + UnsupportedOperation, + default_subprocess_runner, + quiet_subprocess_runner, +) + +__version__ = '1.0.0' +__all__ = [ + 'BackendUnavailable', + 'BackendInvalid', + 'HookMissing', + 'UnsupportedOperation', + 'default_subprocess_runner', + 'quiet_subprocess_runner', + 'BuildBackendHookCaller', +] diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_compat.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_compat.py new file mode 100644 index 00000000..95e509c0 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_compat.py @@ -0,0 +1,8 @@ +__all__ = ("tomllib",) + +import sys + +if sys.version_info >= (3, 11): + import tomllib +else: + from pip._vendor import tomli as tomllib diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py new file mode 100644 index 00000000..37b0e653 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py @@ -0,0 +1,330 @@ +import json +import os +import sys +import tempfile +from contextlib import contextmanager +from os.path import abspath +from os.path import join as pjoin +from subprocess import STDOUT, check_call, check_output + +from ._in_process import _in_proc_script_path + + +def write_json(obj, path, **kwargs): + with open(path, 'w', encoding='utf-8') as f: + json.dump(obj, f, **kwargs) + + +def read_json(path): + with open(path, encoding='utf-8') as f: + return json.load(f) + + +class BackendUnavailable(Exception): + """Will be raised if the backend cannot be imported in the hook process.""" + def __init__(self, traceback): + self.traceback = traceback + + +class BackendInvalid(Exception): + """Will be raised if the backend is invalid.""" + def __init__(self, backend_name, backend_path, message): + super().__init__(message) + self.backend_name = backend_name + self.backend_path = backend_path + + +class HookMissing(Exception): + """Will be raised on missing hooks (if a fallback can't be used).""" + def __init__(self, hook_name): + super().__init__(hook_name) + self.hook_name = hook_name + + +class UnsupportedOperation(Exception): + """May be raised by build_sdist if the backend indicates that it can't.""" + def __init__(self, traceback): + self.traceback = traceback + + +def default_subprocess_runner(cmd, cwd=None, extra_environ=None): + """The default method of calling the wrapper subprocess. + + This uses :func:`subprocess.check_call` under the hood. + """ + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + + check_call(cmd, cwd=cwd, env=env) + + +def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None): + """Call the subprocess while suppressing output. + + This uses :func:`subprocess.check_output` under the hood. + """ + env = os.environ.copy() + if extra_environ: + env.update(extra_environ) + + check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) + + +def norm_and_check(source_tree, requested): + """Normalise and check a backend path. + + Ensure that the requested backend path is specified as a relative path, + and resolves to a location under the given source tree. + + Return an absolute version of the requested path. + """ + if os.path.isabs(requested): + raise ValueError("paths must be relative") + + abs_source = os.path.abspath(source_tree) + abs_requested = os.path.normpath(os.path.join(abs_source, requested)) + # We have to use commonprefix for Python 2.7 compatibility. So we + # normalise case to avoid problems because commonprefix is a character + # based comparison :-( + norm_source = os.path.normcase(abs_source) + norm_requested = os.path.normcase(abs_requested) + if os.path.commonprefix([norm_source, norm_requested]) != norm_source: + raise ValueError("paths must be inside source tree") + + return abs_requested + + +class BuildBackendHookCaller: + """A wrapper to call the build backend hooks for a source directory. + """ + + def __init__( + self, + source_dir, + build_backend, + backend_path=None, + runner=None, + python_executable=None, + ): + """ + :param source_dir: The source directory to invoke the build backend for + :param build_backend: The build backend spec + :param backend_path: Additional path entries for the build backend spec + :param runner: The :ref:`subprocess runner ` to use + :param python_executable: + The Python executable used to invoke the build backend + """ + if runner is None: + runner = default_subprocess_runner + + self.source_dir = abspath(source_dir) + self.build_backend = build_backend + if backend_path: + backend_path = [ + norm_and_check(self.source_dir, p) for p in backend_path + ] + self.backend_path = backend_path + self._subprocess_runner = runner + if not python_executable: + python_executable = sys.executable + self.python_executable = python_executable + + @contextmanager + def subprocess_runner(self, runner): + """A context manager for temporarily overriding the default + :ref:`subprocess runner `. + + .. code-block:: python + + hook_caller = BuildBackendHookCaller(...) + with hook_caller.subprocess_runner(quiet_subprocess_runner): + ... + """ + prev = self._subprocess_runner + self._subprocess_runner = runner + try: + yield + finally: + self._subprocess_runner = prev + + def _supported_features(self): + """Return the list of optional features supported by the backend.""" + return self._call_hook('_supported_features', {}) + + def get_requires_for_build_wheel(self, config_settings=None): + """Get additional dependencies required for building a wheel. + + :returns: A list of :pep:`dependency specifiers <508>`. + :rtype: list[str] + + .. admonition:: Fallback + + If the build backend does not defined a hook with this name, an + empty list will be returned. + """ + return self._call_hook('get_requires_for_build_wheel', { + 'config_settings': config_settings + }) + + def prepare_metadata_for_build_wheel( + self, metadata_directory, config_settings=None, + _allow_fallback=True): + """Prepare a ``*.dist-info`` folder with metadata for this project. + + :returns: Name of the newly created subfolder within + ``metadata_directory``, containing the metadata. + :rtype: str + + .. admonition:: Fallback + + If the build backend does not define a hook with this name and + ``_allow_fallback`` is truthy, the backend will be asked to build a + wheel via the ``build_wheel`` hook and the dist-info extracted from + that will be returned. + """ + return self._call_hook('prepare_metadata_for_build_wheel', { + 'metadata_directory': abspath(metadata_directory), + 'config_settings': config_settings, + '_allow_fallback': _allow_fallback, + }) + + def build_wheel( + self, wheel_directory, config_settings=None, + metadata_directory=None): + """Build a wheel from this project. + + :returns: + The name of the newly created wheel within ``wheel_directory``. + + .. admonition:: Interaction with fallback + + If the ``build_wheel`` hook was called in the fallback for + :meth:`prepare_metadata_for_build_wheel`, the build backend would + not be invoked. Instead, the previously built wheel will be copied + to ``wheel_directory`` and the name of that file will be returned. + """ + if metadata_directory is not None: + metadata_directory = abspath(metadata_directory) + return self._call_hook('build_wheel', { + 'wheel_directory': abspath(wheel_directory), + 'config_settings': config_settings, + 'metadata_directory': metadata_directory, + }) + + def get_requires_for_build_editable(self, config_settings=None): + """Get additional dependencies required for building an editable wheel. + + :returns: A list of :pep:`dependency specifiers <508>`. + :rtype: list[str] + + .. admonition:: Fallback + + If the build backend does not defined a hook with this name, an + empty list will be returned. + """ + return self._call_hook('get_requires_for_build_editable', { + 'config_settings': config_settings + }) + + def prepare_metadata_for_build_editable( + self, metadata_directory, config_settings=None, + _allow_fallback=True): + """Prepare a ``*.dist-info`` folder with metadata for this project. + + :returns: Name of the newly created subfolder within + ``metadata_directory``, containing the metadata. + :rtype: str + + .. admonition:: Fallback + + If the build backend does not define a hook with this name and + ``_allow_fallback`` is truthy, the backend will be asked to build a + wheel via the ``build_editable`` hook and the dist-info + extracted from that will be returned. + """ + return self._call_hook('prepare_metadata_for_build_editable', { + 'metadata_directory': abspath(metadata_directory), + 'config_settings': config_settings, + '_allow_fallback': _allow_fallback, + }) + + def build_editable( + self, wheel_directory, config_settings=None, + metadata_directory=None): + """Build an editable wheel from this project. + + :returns: + The name of the newly created wheel within ``wheel_directory``. + + .. admonition:: Interaction with fallback + + If the ``build_editable`` hook was called in the fallback for + :meth:`prepare_metadata_for_build_editable`, the build backend + would not be invoked. Instead, the previously built wheel will be + copied to ``wheel_directory`` and the name of that file will be + returned. + """ + if metadata_directory is not None: + metadata_directory = abspath(metadata_directory) + return self._call_hook('build_editable', { + 'wheel_directory': abspath(wheel_directory), + 'config_settings': config_settings, + 'metadata_directory': metadata_directory, + }) + + def get_requires_for_build_sdist(self, config_settings=None): + """Get additional dependencies required for building an sdist. + + :returns: A list of :pep:`dependency specifiers <508>`. + :rtype: list[str] + """ + return self._call_hook('get_requires_for_build_sdist', { + 'config_settings': config_settings + }) + + def build_sdist(self, sdist_directory, config_settings=None): + """Build an sdist from this project. + + :returns: + The name of the newly created sdist within ``wheel_directory``. + """ + return self._call_hook('build_sdist', { + 'sdist_directory': abspath(sdist_directory), + 'config_settings': config_settings, + }) + + def _call_hook(self, hook_name, kwargs): + extra_environ = {'PEP517_BUILD_BACKEND': self.build_backend} + + if self.backend_path: + backend_path = os.pathsep.join(self.backend_path) + extra_environ['PEP517_BACKEND_PATH'] = backend_path + + with tempfile.TemporaryDirectory() as td: + hook_input = {'kwargs': kwargs} + write_json(hook_input, pjoin(td, 'input.json'), indent=2) + + # Run the hook in a subprocess + with _in_proc_script_path() as script: + python = self.python_executable + self._subprocess_runner( + [python, abspath(str(script)), hook_name, td], + cwd=self.source_dir, + extra_environ=extra_environ + ) + + data = read_json(pjoin(td, 'output.json')) + if data.get('unsupported'): + raise UnsupportedOperation(data.get('traceback', '')) + if data.get('no_backend'): + raise BackendUnavailable(data.get('traceback', '')) + if data.get('backend_invalid'): + raise BackendInvalid( + backend_name=self.build_backend, + backend_path=self.backend_path, + message=data.get('backend_error', '') + ) + if data.get('hook_missing'): + raise HookMissing(data.get('missing_hook_name') or hook_name) + return data['return_val'] diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py new file mode 100644 index 00000000..917fa065 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py @@ -0,0 +1,18 @@ +"""This is a subpackage because the directory is on sys.path for _in_process.py + +The subpackage should stay as empty as possible to avoid shadowing modules that +the backend might import. +""" + +import importlib.resources as resources + +try: + resources.files +except AttributeError: + # Python 3.8 compatibility + def _in_proc_script_path(): + return resources.path(__package__, '_in_process.py') +else: + def _in_proc_script_path(): + return resources.as_file( + resources.files(__package__).joinpath('_in_process.py')) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py b/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py new file mode 100644 index 00000000..ee511ff2 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py @@ -0,0 +1,353 @@ +"""This is invoked in a subprocess to call the build backend hooks. + +It expects: +- Command line args: hook_name, control_dir +- Environment variables: + PEP517_BUILD_BACKEND=entry.point:spec + PEP517_BACKEND_PATH=paths (separated with os.pathsep) +- control_dir/input.json: + - {"kwargs": {...}} + +Results: +- control_dir/output.json + - {"return_val": ...} +""" +import json +import os +import os.path +import re +import shutil +import sys +import traceback +from glob import glob +from importlib import import_module +from os.path import join as pjoin + +# This file is run as a script, and `import wrappers` is not zip-safe, so we +# include write_json() and read_json() from wrappers.py. + + +def write_json(obj, path, **kwargs): + with open(path, 'w', encoding='utf-8') as f: + json.dump(obj, f, **kwargs) + + +def read_json(path): + with open(path, encoding='utf-8') as f: + return json.load(f) + + +class BackendUnavailable(Exception): + """Raised if we cannot import the backend""" + def __init__(self, traceback): + self.traceback = traceback + + +class BackendInvalid(Exception): + """Raised if the backend is invalid""" + def __init__(self, message): + self.message = message + + +class HookMissing(Exception): + """Raised if a hook is missing and we are not executing the fallback""" + def __init__(self, hook_name=None): + super().__init__(hook_name) + self.hook_name = hook_name + + +def contained_in(filename, directory): + """Test if a file is located within the given directory.""" + filename = os.path.normcase(os.path.abspath(filename)) + directory = os.path.normcase(os.path.abspath(directory)) + return os.path.commonprefix([filename, directory]) == directory + + +def _build_backend(): + """Find and load the build backend""" + # Add in-tree backend directories to the front of sys.path. + backend_path = os.environ.get('PEP517_BACKEND_PATH') + if backend_path: + extra_pathitems = backend_path.split(os.pathsep) + sys.path[:0] = extra_pathitems + + ep = os.environ['PEP517_BUILD_BACKEND'] + mod_path, _, obj_path = ep.partition(':') + try: + obj = import_module(mod_path) + except ImportError: + raise BackendUnavailable(traceback.format_exc()) + + if backend_path: + if not any( + contained_in(obj.__file__, path) + for path in extra_pathitems + ): + raise BackendInvalid("Backend was not loaded from backend-path") + + if obj_path: + for path_part in obj_path.split('.'): + obj = getattr(obj, path_part) + return obj + + +def _supported_features(): + """Return the list of options features supported by the backend. + + Returns a list of strings. + The only possible value is 'build_editable'. + """ + backend = _build_backend() + features = [] + if hasattr(backend, "build_editable"): + features.append("build_editable") + return features + + +def get_requires_for_build_wheel(config_settings): + """Invoke the optional get_requires_for_build_wheel hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_wheel + except AttributeError: + return [] + else: + return hook(config_settings) + + +def get_requires_for_build_editable(config_settings): + """Invoke the optional get_requires_for_build_editable hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_editable + except AttributeError: + return [] + else: + return hook(config_settings) + + +def prepare_metadata_for_build_wheel( + metadata_directory, config_settings, _allow_fallback): + """Invoke optional prepare_metadata_for_build_wheel + + Implements a fallback by building a wheel if the hook isn't defined, + unless _allow_fallback is False in which case HookMissing is raised. + """ + backend = _build_backend() + try: + hook = backend.prepare_metadata_for_build_wheel + except AttributeError: + if not _allow_fallback: + raise HookMissing() + else: + return hook(metadata_directory, config_settings) + # fallback to build_wheel outside the try block to avoid exception chaining + # which can be confusing to users and is not relevant + whl_basename = backend.build_wheel(metadata_directory, config_settings) + return _get_wheel_metadata_from_wheel(whl_basename, metadata_directory, + config_settings) + + +def prepare_metadata_for_build_editable( + metadata_directory, config_settings, _allow_fallback): + """Invoke optional prepare_metadata_for_build_editable + + Implements a fallback by building an editable wheel if the hook isn't + defined, unless _allow_fallback is False in which case HookMissing is + raised. + """ + backend = _build_backend() + try: + hook = backend.prepare_metadata_for_build_editable + except AttributeError: + if not _allow_fallback: + raise HookMissing() + try: + build_hook = backend.build_editable + except AttributeError: + raise HookMissing(hook_name='build_editable') + else: + whl_basename = build_hook(metadata_directory, config_settings) + return _get_wheel_metadata_from_wheel(whl_basename, + metadata_directory, + config_settings) + else: + return hook(metadata_directory, config_settings) + + +WHEEL_BUILT_MARKER = 'PEP517_ALREADY_BUILT_WHEEL' + + +def _dist_info_files(whl_zip): + """Identify the .dist-info folder inside a wheel ZipFile.""" + res = [] + for path in whl_zip.namelist(): + m = re.match(r'[^/\\]+-[^/\\]+\.dist-info/', path) + if m: + res.append(path) + if res: + return res + raise Exception("No .dist-info folder found in wheel") + + +def _get_wheel_metadata_from_wheel( + whl_basename, metadata_directory, config_settings): + """Extract the metadata from a wheel. + + Fallback for when the build backend does not + define the 'get_wheel_metadata' hook. + """ + from zipfile import ZipFile + with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'): + pass # Touch marker file + + whl_file = os.path.join(metadata_directory, whl_basename) + with ZipFile(whl_file) as zipf: + dist_info = _dist_info_files(zipf) + zipf.extractall(path=metadata_directory, members=dist_info) + return dist_info[0].split('/')[0] + + +def _find_already_built_wheel(metadata_directory): + """Check for a wheel already built during the get_wheel_metadata hook. + """ + if not metadata_directory: + return None + metadata_parent = os.path.dirname(metadata_directory) + if not os.path.isfile(pjoin(metadata_parent, WHEEL_BUILT_MARKER)): + return None + + whl_files = glob(os.path.join(metadata_parent, '*.whl')) + if not whl_files: + print('Found wheel built marker, but no .whl files') + return None + if len(whl_files) > 1: + print('Found multiple .whl files; unspecified behaviour. ' + 'Will call build_wheel.') + return None + + # Exactly one .whl file + return whl_files[0] + + +def build_wheel(wheel_directory, config_settings, metadata_directory=None): + """Invoke the mandatory build_wheel hook. + + If a wheel was already built in the + prepare_metadata_for_build_wheel fallback, this + will copy it rather than rebuilding the wheel. + """ + prebuilt_whl = _find_already_built_wheel(metadata_directory) + if prebuilt_whl: + shutil.copy2(prebuilt_whl, wheel_directory) + return os.path.basename(prebuilt_whl) + + return _build_backend().build_wheel(wheel_directory, config_settings, + metadata_directory) + + +def build_editable(wheel_directory, config_settings, metadata_directory=None): + """Invoke the optional build_editable hook. + + If a wheel was already built in the + prepare_metadata_for_build_editable fallback, this + will copy it rather than rebuilding the wheel. + """ + backend = _build_backend() + try: + hook = backend.build_editable + except AttributeError: + raise HookMissing() + else: + prebuilt_whl = _find_already_built_wheel(metadata_directory) + if prebuilt_whl: + shutil.copy2(prebuilt_whl, wheel_directory) + return os.path.basename(prebuilt_whl) + + return hook(wheel_directory, config_settings, metadata_directory) + + +def get_requires_for_build_sdist(config_settings): + """Invoke the optional get_requires_for_build_wheel hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_sdist + except AttributeError: + return [] + else: + return hook(config_settings) + + +class _DummyException(Exception): + """Nothing should ever raise this exception""" + + +class GotUnsupportedOperation(Exception): + """For internal use when backend raises UnsupportedOperation""" + def __init__(self, traceback): + self.traceback = traceback + + +def build_sdist(sdist_directory, config_settings): + """Invoke the mandatory build_sdist hook.""" + backend = _build_backend() + try: + return backend.build_sdist(sdist_directory, config_settings) + except getattr(backend, 'UnsupportedOperation', _DummyException): + raise GotUnsupportedOperation(traceback.format_exc()) + + +HOOK_NAMES = { + 'get_requires_for_build_wheel', + 'prepare_metadata_for_build_wheel', + 'build_wheel', + 'get_requires_for_build_editable', + 'prepare_metadata_for_build_editable', + 'build_editable', + 'get_requires_for_build_sdist', + 'build_sdist', + '_supported_features', +} + + +def main(): + if len(sys.argv) < 3: + sys.exit("Needs args: hook_name, control_dir") + hook_name = sys.argv[1] + control_dir = sys.argv[2] + if hook_name not in HOOK_NAMES: + sys.exit("Unknown hook: %s" % hook_name) + hook = globals()[hook_name] + + hook_input = read_json(pjoin(control_dir, 'input.json')) + + json_out = {'unsupported': False, 'return_val': None} + try: + json_out['return_val'] = hook(**hook_input['kwargs']) + except BackendUnavailable as e: + json_out['no_backend'] = True + json_out['traceback'] = e.traceback + except BackendInvalid as e: + json_out['backend_invalid'] = True + json_out['backend_error'] = e.message + except GotUnsupportedOperation as e: + json_out['unsupported'] = True + json_out['traceback'] = e.traceback + except HookMissing as e: + json_out['hook_missing'] = True + json_out['missing_hook_name'] = e.hook_name or hook_name + + write_json(json_out, pjoin(control_dir, 'output.json'), indent=2) + + +if __name__ == '__main__': + main() diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/__init__.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/__init__.py new file mode 100644 index 00000000..10ff67ff --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/__init__.py @@ -0,0 +1,182 @@ +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP Library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. +Basic GET usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> b'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('https://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key1": "value1", + "key2": "value2" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at . + +:copyright: (c) 2017 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. +""" + +import warnings + +from pip._vendor import urllib3 + +from .exceptions import RequestsDependencyWarning + +charset_normalizer_version = None + +try: + from pip._vendor.chardet import __version__ as chardet_version +except ImportError: + chardet_version = None + + +def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version): + urllib3_version = urllib3_version.split(".") + assert urllib3_version != ["dev"] # Verify urllib3 isn't installed from git. + + # Sometimes, urllib3 only reports its version as 16.1. + if len(urllib3_version) == 2: + urllib3_version.append("0") + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1 + assert major >= 1 + if major == 1: + assert minor >= 21 + + # Check charset_normalizer for compatibility. + if chardet_version: + major, minor, patch = chardet_version.split(".")[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet_version >= 3.0.2, < 6.0.0 + assert (3, 0, 2) <= (major, minor, patch) < (6, 0, 0) + elif charset_normalizer_version: + major, minor, patch = charset_normalizer_version.split(".")[:3] + major, minor, patch = int(major), int(minor), int(patch) + # charset_normalizer >= 2.0.0 < 4.0.0 + assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0) + else: + raise Exception("You need either charset_normalizer or chardet installed") + + +def _check_cryptography(cryptography_version): + # cryptography < 1.3.4 + try: + cryptography_version = list(map(int, cryptography_version.split("."))) + except ValueError: + return + + if cryptography_version < [1, 3, 4]: + warning = "Old version of cryptography ({}) may cause slowdown.".format( + cryptography_version + ) + warnings.warn(warning, RequestsDependencyWarning) + + +# Check imported dependencies for compatibility. +try: + check_compatibility( + urllib3.__version__, chardet_version, charset_normalizer_version + ) +except (AssertionError, ValueError): + warnings.warn( + "urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported " + "version!".format( + urllib3.__version__, chardet_version, charset_normalizer_version + ), + RequestsDependencyWarning, + ) + +# Attempt to enable urllib3's fallback for SNI support +# if the standard library doesn't support SNI or the +# 'ssl' library isn't available. +try: + # Note: This logic prevents upgrading cryptography on Windows, if imported + # as part of pip. + from pip._internal.utils.compat import WINDOWS + if not WINDOWS: + raise ImportError("pip internals: don't import cryptography on Windows") + try: + import ssl + except ImportError: + ssl = None + + if not getattr(ssl, "HAS_SNI", False): + from pip._vendor.urllib3.contrib import pyopenssl + + pyopenssl.inject_into_urllib3() + + # Check cryptography version + from cryptography import __version__ as cryptography_version + + _check_cryptography(cryptography_version) +except ImportError: + pass + +# urllib3's DependencyWarnings should be silenced. +from pip._vendor.urllib3.exceptions import DependencyWarning + +warnings.simplefilter("ignore", DependencyWarning) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +from . import packages, utils +from .__version__ import ( + __author__, + __author_email__, + __build__, + __cake__, + __copyright__, + __description__, + __license__, + __title__, + __url__, + __version__, +) +from .api import delete, get, head, options, patch, post, put, request +from .exceptions import ( + ConnectionError, + ConnectTimeout, + FileModeWarning, + HTTPError, + JSONDecodeError, + ReadTimeout, + RequestException, + Timeout, + TooManyRedirects, + URLRequired, +) +from .models import PreparedRequest, Request, Response +from .sessions import Session, session +from .status_codes import codes + +logging.getLogger(__name__).addHandler(NullHandler()) + +# FileModeWarnings go off per the default. +warnings.simplefilter("default", FileModeWarning, append=True) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/__version__.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/__version__.py new file mode 100644 index 00000000..5063c3f8 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/__version__.py @@ -0,0 +1,14 @@ +# .-. .-. .-. . . .-. .-. .-. .-. +# |( |- |.| | | |- `-. | `-. +# ' ' `-' `-`.`-' `-' `-' ' `-' + +__title__ = "requests" +__description__ = "Python HTTP for Humans." +__url__ = "https://requests.readthedocs.io" +__version__ = "2.31.0" +__build__ = 0x023100 +__author__ = "Kenneth Reitz" +__author_email__ = "me@kennethreitz.org" +__license__ = "Apache 2.0" +__copyright__ = "Copyright Kenneth Reitz" +__cake__ = "\u2728 \U0001f370 \u2728" diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/_internal_utils.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/_internal_utils.py new file mode 100644 index 00000000..f2cf635e --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/_internal_utils.py @@ -0,0 +1,50 @@ +""" +requests._internal_utils +~~~~~~~~~~~~~~ + +Provides utility functions that are consumed internally by Requests +which depend on extremely few external helpers (such as compat) +""" +import re + +from .compat import builtin_str + +_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*$") +_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*$") +_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*$|^$") +_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*$|^$") + +_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR) +_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE) +HEADER_VALIDATORS = { + bytes: _HEADER_VALIDATORS_BYTE, + str: _HEADER_VALIDATORS_STR, +} + + +def to_native_string(string, encoding="ascii"): + """Given a string object, regardless of type, returns a representation of + that string in the native string type, encoding and decoding where + necessary. This assumes ASCII unless told otherwise. + """ + if isinstance(string, builtin_str): + out = string + else: + out = string.decode(encoding) + + return out + + +def unicode_is_ascii(u_string): + """Determine if unicode string only contains ASCII characters. + + :param str u_string: unicode string to check. Must be unicode + and not Python 2 `str`. + :rtype: bool + """ + assert isinstance(u_string, str) + try: + u_string.encode("ascii") + return True + except UnicodeEncodeError: + return False diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/adapters.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/adapters.py new file mode 100644 index 00000000..10c17679 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/adapters.py @@ -0,0 +1,538 @@ +""" +requests.adapters +~~~~~~~~~~~~~~~~~ + +This module contains the transport adapters that Requests uses to define +and maintain connections. +""" + +import os.path +import socket # noqa: F401 + +from pip._vendor.urllib3.exceptions import ClosedPoolError, ConnectTimeoutError +from pip._vendor.urllib3.exceptions import HTTPError as _HTTPError +from pip._vendor.urllib3.exceptions import InvalidHeader as _InvalidHeader +from pip._vendor.urllib3.exceptions import ( + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, +) +from pip._vendor.urllib3.exceptions import ProxyError as _ProxyError +from pip._vendor.urllib3.exceptions import ReadTimeoutError, ResponseError +from pip._vendor.urllib3.exceptions import SSLError as _SSLError +from pip._vendor.urllib3.poolmanager import PoolManager, proxy_from_url +from pip._vendor.urllib3.util import Timeout as TimeoutSauce +from pip._vendor.urllib3.util import parse_url +from pip._vendor.urllib3.util.retry import Retry + +from .auth import _basic_auth_str +from .compat import basestring, urlparse +from .cookies import extract_cookies_to_jar +from .exceptions import ( + ConnectionError, + ConnectTimeout, + InvalidHeader, + InvalidProxyURL, + InvalidSchema, + InvalidURL, + ProxyError, + ReadTimeout, + RetryError, + SSLError, +) +from .models import Response +from .structures import CaseInsensitiveDict +from .utils import ( + DEFAULT_CA_BUNDLE_PATH, + extract_zipped_paths, + get_auth_from_url, + get_encoding_from_headers, + prepend_scheme_if_needed, + select_proxy, + urldefragauth, +) + +try: + from pip._vendor.urllib3.contrib.socks import SOCKSProxyManager +except ImportError: + + def SOCKSProxyManager(*args, **kwargs): + raise InvalidSchema("Missing dependencies for SOCKS support.") + + +DEFAULT_POOLBLOCK = False +DEFAULT_POOLSIZE = 10 +DEFAULT_RETRIES = 0 +DEFAULT_POOL_TIMEOUT = None + + +class BaseAdapter: + """The Base Transport Adapter""" + + def __init__(self): + super().__init__() + + def send( + self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None + ): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + raise NotImplementedError + + def close(self): + """Cleans up adapter specific items.""" + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session ` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> a = requests.adapters.HTTPAdapter(max_retries=3) + >>> s.mount('http://', a) + """ + + __attrs__ = [ + "max_retries", + "config", + "_pool_connections", + "_pool_maxsize", + "_pool_block", + ] + + def __init__( + self, + pool_connections=DEFAULT_POOLSIZE, + pool_maxsize=DEFAULT_POOLSIZE, + max_retries=DEFAULT_RETRIES, + pool_block=DEFAULT_POOLBLOCK, + ): + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super().__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self): + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager( + self._pool_connections, self._pool_maxsize, block=self._pool_block + ) + + def init_poolmanager( + self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs + ): + """Initializes a urllib3 PoolManager. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + self.poolmanager = PoolManager( + num_pools=connections, + maxsize=maxsize, + block=block, + **pool_kwargs, + ) + + def proxy_manager_for(self, proxy, **proxy_kwargs): + """Return urllib3 ProxyManager for the given proxy. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + :rtype: urllib3.ProxyManager + """ + if proxy in self.proxy_manager: + manager = self.proxy_manager[proxy] + elif proxy.lower().startswith("socks"): + username, password = get_auth_from_url(proxy) + manager = self.proxy_manager[proxy] = SOCKSProxyManager( + proxy, + username=username, + password=password, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs, + ) + else: + proxy_headers = self.proxy_headers(proxy) + manager = self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs, + ) + + return manager + + def cert_verify(self, conn, url, verify, cert): + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith("https") and verify: + + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH) + + if not cert_loc or not os.path.exists(cert_loc): + raise OSError( + f"Could not find a suitable TLS CA certificate bundle, " + f"invalid path: {cert_loc}" + ) + + conn.cert_reqs = "CERT_REQUIRED" + + if not os.path.isdir(cert_loc): + conn.ca_certs = cert_loc + else: + conn.ca_cert_dir = cert_loc + else: + conn.cert_reqs = "CERT_NONE" + conn.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + conn.cert_file = cert + conn.key_file = None + if conn.cert_file and not os.path.exists(conn.cert_file): + raise OSError( + f"Could not find the TLS certificate file, " + f"invalid path: {conn.cert_file}" + ) + if conn.key_file and not os.path.exists(conn.key_file): + raise OSError( + f"Could not find the TLS key file, invalid path: {conn.key_file}" + ) + + def build_response(self, req, resp): + """Builds a :class:`Response ` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter ` + + :param req: The :class:`PreparedRequest ` used to generate the response. + :param resp: The urllib3 response object. + :rtype: requests.Response + """ + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, "status", None) + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, "headers", {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode("utf-8") + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def get_connection(self, url, proxies=None): + """Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + :rtype: urllib3.ConnectionPool + """ + proxy = select_proxy(url, proxies) + + if proxy: + proxy = prepend_scheme_if_needed(proxy, "http") + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL( + "Please check proxy URL. It is malformed " + "and could be missing the host." + ) + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self): + """Disposes of any internal state. + + Currently, this closes the PoolManager and any active ProxyManager, + which closes any pooled connections. + """ + self.poolmanager.clear() + for proxy in self.proxy_manager.values(): + proxy.clear() + + def request_url(self, request, proxies): + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + :rtype: str + """ + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + + is_proxied_http_request = proxy and scheme != "https" + using_socks_proxy = False + if proxy: + proxy_scheme = urlparse(proxy).scheme.lower() + using_socks_proxy = proxy_scheme.startswith("socks") + + url = request.path_url + if is_proxied_http_request and not using_socks_proxy: + url = urldefragauth(request.url) + + return url + + def add_headers(self, request, **kwargs): + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter `. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy): + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The url of the proxy being used for this request. + :rtype: dict + """ + headers = {} + username, password = get_auth_from_url(proxy) + + if username: + headers["Proxy-Authorization"] = _basic_auth_str(username, password) + + return headers + + def send( + self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None + ): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple or urllib3 Timeout object + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + :rtype: requests.Response + """ + + try: + conn = self.get_connection(request.url, proxies) + except LocationValueError as e: + raise InvalidURL(e, request=request) + + self.cert_verify(conn, request.url, verify, cert) + url = self.request_url(request, proxies) + self.add_headers( + request, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + ) + + chunked = not (request.body is None or "Content-Length" in request.headers) + + if isinstance(timeout, tuple): + try: + connect, read = timeout + timeout = TimeoutSauce(connect=connect, read=read) + except ValueError: + raise ValueError( + f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, " + f"or a single float to set both timeouts to the same value." + ) + elif isinstance(timeout, TimeoutSauce): + pass + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout, + chunked=chunked, + ) + + except (ProtocolError, OSError) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + if isinstance(e.reason, _ProxyError): + raise ProxyError(e, request=request) + + if isinstance(e.reason, _SSLError): + # This branch is for urllib3 v1.22 and later. + raise SSLError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + # This branch is for urllib3 versions earlier than v1.22 + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + elif isinstance(e, _InvalidHeader): + raise InvalidHeader(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/api.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/api.py new file mode 100644 index 00000000..cd0b3eea --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/api.py @@ -0,0 +1,157 @@ +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. +""" + +from . import sessions + + +def request(method, url, **kwargs): + """Constructs and sends a :class:`Request `. + + :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. + ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` + or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string + defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers + to add for the file. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response ` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'https://httpbin.org/get') + >>> req + + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get(url, params=None, **kwargs): + r"""Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("get", url, params=params, **kwargs) + + +def options(url, **kwargs): + r"""Sends an OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("options", url, **kwargs) + + +def head(url, **kwargs): + r"""Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. If + `allow_redirects` is not provided, it will be set to `False` (as + opposed to the default :meth:`request` behavior). + :return: :class:`Response ` object + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", False) + return request("head", url, **kwargs) + + +def post(url, data=None, json=None, **kwargs): + r"""Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("post", url, data=data, json=json, **kwargs) + + +def put(url, data=None, **kwargs): + r"""Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("put", url, data=data, **kwargs) + + +def patch(url, data=None, **kwargs): + r"""Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("patch", url, data=data, **kwargs) + + +def delete(url, **kwargs): + r"""Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("delete", url, **kwargs) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/auth.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/auth.py new file mode 100644 index 00000000..9733686d --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/auth.py @@ -0,0 +1,315 @@ +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +import hashlib +import os +import re +import threading +import time +import warnings +from base64 import b64encode + +from ._internal_utils import to_native_string +from .compat import basestring, str, urlparse +from .cookies import extract_cookies_to_jar +from .utils import parse_dict_header + +CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded" +CONTENT_TYPE_MULTI_PART = "multipart/form-data" + + +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + # "I want us to put a big-ol' comment on top of it that + # says that this behaviour is dumb but we need to preserve + # it because people are relying on it." + # - Lukasa + # + # These are here solely to maintain backwards compatibility + # for things like ints. This will be removed in 3.0.0. + if not isinstance(username, basestring): + warnings.warn( + "Non-string usernames will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(username), + category=DeprecationWarning, + ) + username = str(username) + + if not isinstance(password, basestring): + warnings.warn( + "Non-string passwords will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(type(password)), + category=DeprecationWarning, + ) + password = str(password) + # -- End Removal -- + + if isinstance(username, str): + username = username.encode("latin1") + + if isinstance(password, str): + password = password.encode("latin1") + + authstr = "Basic " + to_native_string( + b64encode(b":".join((username, password))).strip() + ) + + return authstr + + +class AuthBase: + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError("Auth hooks must be callable.") + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + + def __eq__(self, other): + return all( + [ + self.username == getattr(other, "username", None), + self.password == getattr(other, "password", None), + ] + ) + + def __ne__(self, other): + return not self == other + + def __call__(self, r): + r.headers["Authorization"] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPProxyAuth(HTTPBasicAuth): + """Attaches HTTP Proxy Authentication to a given Request object.""" + + def __call__(self, r): + r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self): + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, "init"): + self._thread_local.init = True + self._thread_local.last_nonce = "" + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method, url): + """ + :rtype: str + """ + + realm = self._thread_local.chal["realm"] + nonce = self._thread_local.chal["nonce"] + qop = self._thread_local.chal.get("qop") + algorithm = self._thread_local.chal.get("algorithm") + opaque = self._thread_local.chal.get("opaque") + hash_utf8 = None + + if algorithm is None: + _algorithm = "MD5" + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == "MD5" or _algorithm == "MD5-SESS": + + def md5_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.md5(x).hexdigest() + + hash_utf8 = md5_utf8 + elif _algorithm == "SHA": + + def sha_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha1(x).hexdigest() + + hash_utf8 = sha_utf8 + elif _algorithm == "SHA-256": + + def sha256_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha256(x).hexdigest() + + hash_utf8 = sha256_utf8 + elif _algorithm == "SHA-512": + + def sha512_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha512(x).hexdigest() + + hash_utf8 = sha512_utf8 + + KD = lambda s, d: hash_utf8(f"{s}:{d}") # noqa:E731 + + if hash_utf8 is None: + return None + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: path is request-uri defined in RFC 2616 which should not be empty + path = p_parsed.path or "/" + if p_parsed.query: + path += f"?{p_parsed.query}" + + A1 = f"{self.username}:{realm}:{self.password}" + A2 = f"{method}:{path}" + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + self._thread_local.nonce_count += 1 + else: + self._thread_local.nonce_count = 1 + ncvalue = f"{self._thread_local.nonce_count:08x}" + s = str(self._thread_local.nonce_count).encode("utf-8") + s += nonce.encode("utf-8") + s += time.ctime().encode("utf-8") + s += os.urandom(8) + + cnonce = hashlib.sha1(s).hexdigest()[:16] + if _algorithm == "MD5-SESS": + HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}") + + if not qop: + respdig = KD(HA1, f"{nonce}:{HA2}") + elif qop == "auth" or "auth" in qop.split(","): + noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}" + respdig = KD(HA1, noncebit) + else: + # XXX handle auth-int. + return None + + self._thread_local.last_nonce = nonce + + # XXX should the partial digests be encoded too? + base = ( + f'username="{self.username}", realm="{realm}", nonce="{nonce}", ' + f'uri="{path}", response="{respdig}"' + ) + if opaque: + base += f', opaque="{opaque}"' + if algorithm: + base += f', algorithm="{algorithm}"' + if entdig: + base += f', digest="{entdig}"' + if qop: + base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"' + + return f"Digest {base}" + + def handle_redirect(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r, **kwargs): + """ + Takes the given response and tries digest-auth, if needed. + + :rtype: requests.Response + """ + + # If response is not 4xx, do not auth + # See https://github.com/psf/requests/issues/3772 + if not 400 <= r.status_code < 500: + self._thread_local.num_401_calls = 1 + return r + + if self._thread_local.pos is not None: + # Rewind the file position indicator of the body to where + # it was to resend the request. + r.request.body.seek(self._thread_local.pos) + s_auth = r.headers.get("www-authenticate", "") + + if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2: + + self._thread_local.num_401_calls += 1 + pat = re.compile(r"digest ", flags=re.IGNORECASE) + self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + extract_cookies_to_jar(prep._cookies, r.request, r.raw) + prep.prepare_cookies(prep._cookies) + + prep.headers["Authorization"] = self.build_digest_header( + prep.method, prep.url + ) + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r): + # Initialize per-thread state, if needed + self.init_per_thread_state() + # If we have a saved nonce, skip the 401 + if self._thread_local.last_nonce: + r.headers["Authorization"] = self.build_digest_header(r.method, r.url) + try: + self._thread_local.pos = r.body.tell() + except AttributeError: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # file position of the previous body. Ensure it's set to + # None. + self._thread_local.pos = None + r.register_hook("response", self.handle_401) + r.register_hook("response", self.handle_redirect) + self._thread_local.num_401_calls = 1 + + return r + + def __eq__(self, other): + return all( + [ + self.username == getattr(other, "username", None), + self.password == getattr(other, "password", None), + ] + ) + + def __ne__(self, other): + return not self == other diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/certs.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/certs.py new file mode 100644 index 00000000..38696a1f --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/certs.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +""" +requests.certs +~~~~~~~~~~~~~~ + +This module returns the preferred default CA certificate bundle. There is +only one — the one from the certifi package. + +If you are packaging Requests, e.g., for a Linux distribution or a managed +environment, you can change the definition of where() to return a separately +packaged CA bundle. +""" + +import os + +if "_PIP_STANDALONE_CERT" not in os.environ: + from pip._vendor.certifi import where +else: + def where(): + return os.environ["_PIP_STANDALONE_CERT"] + +if __name__ == "__main__": + print(where()) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/compat.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/compat.py new file mode 100644 index 00000000..9ab2bb48 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/compat.py @@ -0,0 +1,67 @@ +""" +requests.compat +~~~~~~~~~~~~~~~ + +This module previously handled import compatibility issues +between Python 2 and Python 3. It remains for backwards +compatibility until the next major version. +""" + +from pip._vendor import chardet + +import sys + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = _ver[0] == 2 + +#: Python 3.x? +is_py3 = _ver[0] == 3 + +# Note: We've patched out simplejson support in pip because it prevents +# upgrading simplejson on Windows. +import json +from json import JSONDecodeError + +# Keep OrderedDict for backwards compatibility. +from collections import OrderedDict +from collections.abc import Callable, Mapping, MutableMapping +from http import cookiejar as cookielib +from http.cookies import Morsel +from io import StringIO + +# -------------- +# Legacy Imports +# -------------- +from urllib.parse import ( + quote, + quote_plus, + unquote, + unquote_plus, + urldefrag, + urlencode, + urljoin, + urlparse, + urlsplit, + urlunparse, +) +from urllib.request import ( + getproxies, + getproxies_environment, + parse_http_list, + proxy_bypass, + proxy_bypass_environment, +) + +builtin_str = str +str = str +bytes = bytes +basestring = (str, bytes) +numeric_types = (int, float) +integer_types = (int,) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/cookies.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/cookies.py new file mode 100644 index 00000000..bf54ab23 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/cookies.py @@ -0,0 +1,561 @@ +""" +requests.cookies +~~~~~~~~~~~~~~~~ + +Compatibility code to be able to use `cookielib.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +import calendar +import copy +import time + +from ._internal_utils import to_native_string +from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse + +try: + import threading +except ImportError: + import dummy_threading as threading + + +class MockRequest: + """Wraps a `requests.Request` to mimic a `urllib2.Request`. + + The code in `cookielib.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + def __init__(self, request): + self._r = request + self._new_headers = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self): + return self.type + + def get_host(self): + return urlparse(self._r.url).netloc + + def get_origin_req_host(self): + return self.get_host() + + def get_full_url(self): + # Only return the response's URL if the user hadn't set the Host + # header + if not self._r.headers.get("Host"): + return self._r.url + # If they did set it, retrieve it and reconstruct the expected domain + host = to_native_string(self._r.headers["Host"], encoding="utf-8") + parsed = urlparse(self._r.url) + # Reconstruct the URL as we expect it + return urlunparse( + [ + parsed.scheme, + host, + parsed.path, + parsed.params, + parsed.query, + parsed.fragment, + ] + ) + + def is_unverifiable(self): + return True + + def has_header(self, name): + return name in self._r.headers or name in self._new_headers + + def get_header(self, name, default=None): + return self._r.headers.get(name, self._new_headers.get(name, default)) + + def add_header(self, key, val): + """cookielib has no legitimate use for this method; add it back if you find one.""" + raise NotImplementedError( + "Cookie headers should be added with add_unredirected_header()" + ) + + def add_unredirected_header(self, name, value): + self._new_headers[name] = value + + def get_new_headers(self): + return self._new_headers + + @property + def unverifiable(self): + return self.is_unverifiable() + + @property + def origin_req_host(self): + return self.get_origin_req_host() + + @property + def host(self): + return self.get_host() + + +class MockResponse: + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `cookielib` expects to see them. + """ + + def __init__(self, headers): + """Make a MockResponse for `cookielib` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self): + return self._headers + + def getheaders(self, name): + self._headers.getheaders(name) + + +def extract_cookies_to_jar(jar, request, response): + """Extract the cookies from the response into a CookieJar. + + :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, "_original_response") and response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) + + +def get_cookie_header(jar, request): + """ + Produce an appropriate Cookie header string to be sent with `request`, or None. + + :rtype: str + """ + r = MockRequest(request) + jar.add_cookie_header(r) + return r.get_new_headers().get("Cookie") + + +def remove_cookie_by_name(cookiejar, name, domain=None, path=None): + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific. + """ + + +class RequestsCookieJar(cookielib.CookieJar, MutableMapping): + """Compatibility class; is a cookielib.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + + def get(self, name, default=None, domain=None, path=None): + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1). + """ + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set(self, name, value, **kwargs): + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + """ + # support client code that unsets cookies by assignment of a None value: + if value is None: + remove_cookie_by_name( + self, name, domain=kwargs.get("domain"), path=kwargs.get("path") + ) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self): + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. + + .. seealso:: itervalues() and iteritems(). + """ + for cookie in iter(self): + yield cookie.name + + def keys(self): + """Dict-like keys() that returns a list of names of cookies from the + jar. + + .. seealso:: values() and items(). + """ + return list(self.iterkeys()) + + def itervalues(self): + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. + + .. seealso:: iterkeys() and iteritems(). + """ + for cookie in iter(self): + yield cookie.value + + def values(self): + """Dict-like values() that returns a list of values of cookies from the + jar. + + .. seealso:: keys() and items(). + """ + return list(self.itervalues()) + + def iteritems(self): + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. + + .. seealso:: iterkeys() and itervalues(). + """ + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self): + """Dict-like items() that returns a list of name-value tuples from the + jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a + vanilla python dict of key value pairs. + + .. seealso:: keys() and values(). + """ + return list(self.iteritems()) + + def list_domains(self): + """Utility method to list all the domains in the jar.""" + domains = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self): + """Utility method to list all the paths in the jar.""" + paths = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self): + """Returns True if there are multiple domains in the jar. + Returns False otherwise. + + :rtype: bool + """ + domains = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict(self, domain=None, path=None): + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements. + + :rtype: dict + """ + dictionary = {} + for cookie in iter(self): + if (domain is None or cookie.domain == domain) and ( + path is None or cookie.path == path + ): + dictionary[cookie.name] = cookie.value + return dictionary + + def __contains__(self, name): + try: + return super().__contains__(name) + except CookieConflictError: + return True + + def __getitem__(self, name): + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1). + """ + return self._find_no_duplicates(name) + + def __setitem__(self, name, value): + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead. + """ + self.set(name, value) + + def __delitem__(self, name): + """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s + ``remove_cookie_by_name()``. + """ + remove_cookie_by_name(self, name) + + def set_cookie(self, cookie, *args, **kwargs): + if ( + hasattr(cookie.value, "startswith") + and cookie.value.startswith('"') + and cookie.value.endswith('"') + ): + cookie.value = cookie.value.replace('\\"', "") + return super().set_cookie(cookie, *args, **kwargs) + + def update(self, other): + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super().update(other) + + def _find(self, name, domain=None, path=None): + """Requests uses this method internally to get cookie values. + + If there are conflicting cookies, _find arbitrarily chooses one. + See _find_no_duplicates if you want an exception thrown if there are + conflicting cookies. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :return: cookie.value + """ + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") + + def _find_no_duplicates(self, name, domain=None, path=None): + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :raises KeyError: if cookie is not found + :raises CookieConflictError: if there are multiple cookies + that match name and optionally domain and path + :return: cookie.value + """ + toReturn = None + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if toReturn is not None: + # if there are multiple cookies that meet passed in criteria + raise CookieConflictError( + f"There are multiple cookies with name, {name!r}" + ) + # we will eventually return this as long as no cookie conflict + toReturn = cookie.value + + if toReturn: + return toReturn + raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + state.pop("_cookies_lock") + return state + + def __setstate__(self, state): + """Unlike a normal CookieJar, this class is pickleable.""" + self.__dict__.update(state) + if "_cookies_lock" not in self.__dict__: + self._cookies_lock = threading.RLock() + + def copy(self): + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.set_policy(self.get_policy()) + new_cj.update(self) + return new_cj + + def get_policy(self): + """Return the CookiePolicy instance used.""" + return self._policy + + +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, "copy"): + # We're dealing with an instance of RequestsCookieJar + return jar.copy() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name, value, **kwargs): + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + and sent on every request (this is sometimes called a "supercookie"). + """ + result = { + "version": 0, + "name": name, + "value": value, + "port": None, + "domain": "", + "path": "/", + "secure": False, + "expires": None, + "discard": True, + "comment": None, + "comment_url": None, + "rest": {"HttpOnly": None}, + "rfc2109": False, + } + + badargs = set(kwargs) - set(result) + if badargs: + raise TypeError( + f"create_cookie() got unexpected keyword arguments: {list(badargs)}" + ) + + result.update(kwargs) + result["port_specified"] = bool(result["port"]) + result["domain_specified"] = bool(result["domain"]) + result["domain_initial_dot"] = result["domain"].startswith(".") + result["path_specified"] = bool(result["path"]) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel): + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires = None + if morsel["max-age"]: + try: + expires = int(time.time() + int(morsel["max-age"])) + except ValueError: + raise TypeError(f"max-age: {morsel['max-age']} must be integer") + elif morsel["expires"]: + time_template = "%a, %d-%b-%Y %H:%M:%S GMT" + expires = calendar.timegm(time.strptime(morsel["expires"], time_template)) + return create_cookie( + comment=morsel["comment"], + comment_url=bool(morsel["comment"]), + discard=False, + domain=morsel["domain"], + expires=expires, + name=morsel.key, + path=morsel["path"], + port=None, + rest={"HttpOnly": morsel["httponly"]}, + rfc2109=False, + secure=bool(morsel["secure"]), + value=morsel.value, + version=morsel["version"] or 0, + ) + + +def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + :rtype: CookieJar + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies(cookiejar, cookies): + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + :rtype: CookieJar + """ + if not isinstance(cookiejar, cookielib.CookieJar): + raise ValueError("You can only merge into CookieJar") + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + try: + cookiejar.update(cookies) + except AttributeError: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/exceptions.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/exceptions.py new file mode 100644 index 00000000..168d0739 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/exceptions.py @@ -0,0 +1,141 @@ +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. +""" +from pip._vendor.urllib3.exceptions import HTTPError as BaseHTTPError + +from .compat import JSONDecodeError as CompatJSONDecodeError + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request. + """ + + def __init__(self, *args, **kwargs): + """Initialize RequestException with `request` and `response` objects.""" + response = kwargs.pop("response", None) + self.response = response + self.request = kwargs.pop("request", None) + if response is not None and not self.request and hasattr(response, "request"): + self.request = self.response.request + super().__init__(*args, **kwargs) + + +class InvalidJSONError(RequestException): + """A JSON error occurred.""" + + +class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError): + """Couldn't decode the text into json""" + + def __init__(self, *args, **kwargs): + """ + Construct the JSONDecodeError instance first with all + args. Then use it's args to construct the IOError so that + the json specific args aren't used as IOError specific args + and the error message from JSONDecodeError is preserved. + """ + CompatJSONDecodeError.__init__(self, *args) + InvalidJSONError.__init__(self, *self.args, **kwargs) + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL scheme (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """The URL scheme provided is either invalid or unsupported.""" + + +class InvalidURL(RequestException, ValueError): + """The URL provided was somehow invalid.""" + + +class InvalidHeader(RequestException, ValueError): + """The header value provided was somehow invalid.""" + + +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content.""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed.""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +class UnrewindableBodyError(RequestException): + """Requests encountered an error when trying to rewind a body.""" + + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """A file was opened in text mode, but Requests determined its binary length.""" + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/help.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/help.py new file mode 100644 index 00000000..2d292c2f --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/help.py @@ -0,0 +1,131 @@ +"""Module containing bug report helper(s).""" + +import json +import platform +import ssl +import sys + +from pip._vendor import idna +from pip._vendor import urllib3 + +from . import __version__ as requests_version + +charset_normalizer = None + +try: + from pip._vendor import chardet +except ImportError: + chardet = None + +try: + from pip._vendor.urllib3.contrib import pyopenssl +except ImportError: + pyopenssl = None + OpenSSL = None + cryptography = None +else: + import cryptography + import OpenSSL + + +def _implementation(): + """Return a dict with the Python implementation and version. + + Provide both the name and the version of the Python implementation + currently running. For example, on CPython 3.10.3 it will return + {'name': 'CPython', 'version': '3.10.3'}. + + This function works best on CPython and PyPy: in particular, it probably + doesn't work for Jython or IronPython. Future investigation should be done + to work out the correct shape of the code for those platforms. + """ + implementation = platform.python_implementation() + + if implementation == "CPython": + implementation_version = platform.python_version() + elif implementation == "PyPy": + implementation_version = "{}.{}.{}".format( + sys.pypy_version_info.major, + sys.pypy_version_info.minor, + sys.pypy_version_info.micro, + ) + if sys.pypy_version_info.releaselevel != "final": + implementation_version = "".join( + [implementation_version, sys.pypy_version_info.releaselevel] + ) + elif implementation == "Jython": + implementation_version = platform.python_version() # Complete Guess + elif implementation == "IronPython": + implementation_version = platform.python_version() # Complete Guess + else: + implementation_version = "Unknown" + + return {"name": implementation, "version": implementation_version} + + +def info(): + """Generate information for a bug report.""" + try: + platform_info = { + "system": platform.system(), + "release": platform.release(), + } + except OSError: + platform_info = { + "system": "Unknown", + "release": "Unknown", + } + + implementation_info = _implementation() + urllib3_info = {"version": urllib3.__version__} + charset_normalizer_info = {"version": None} + chardet_info = {"version": None} + if charset_normalizer: + charset_normalizer_info = {"version": charset_normalizer.__version__} + if chardet: + chardet_info = {"version": chardet.__version__} + + pyopenssl_info = { + "version": None, + "openssl_version": "", + } + if OpenSSL: + pyopenssl_info = { + "version": OpenSSL.__version__, + "openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}", + } + cryptography_info = { + "version": getattr(cryptography, "__version__", ""), + } + idna_info = { + "version": getattr(idna, "__version__", ""), + } + + system_ssl = ssl.OPENSSL_VERSION_NUMBER + system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""} + + return { + "platform": platform_info, + "implementation": implementation_info, + "system_ssl": system_ssl_info, + "using_pyopenssl": pyopenssl is not None, + "using_charset_normalizer": chardet is None, + "pyOpenSSL": pyopenssl_info, + "urllib3": urllib3_info, + "chardet": chardet_info, + "charset_normalizer": charset_normalizer_info, + "cryptography": cryptography_info, + "idna": idna_info, + "requests": { + "version": requests_version, + }, + } + + +def main(): + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/hooks.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/hooks.py new file mode 100644 index 00000000..d181ba2e --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/hooks.py @@ -0,0 +1,33 @@ +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. +""" +HOOKS = ["response"] + + +def default_hooks(): + return {event: [] for event in HOOKS} + + +# TODO: response is the only one + + +def dispatch_hook(key, hooks, hook_data, **kwargs): + """Dispatches a hook dictionary on a given piece of data.""" + hooks = hooks or {} + hooks = hooks.get(key) + if hooks: + if hasattr(hooks, "__call__"): + hooks = [hooks] + for hook in hooks: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/models.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/models.py new file mode 100644 index 00000000..76e6f199 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/models.py @@ -0,0 +1,1034 @@ +""" +requests.models +~~~~~~~~~~~~~~~ + +This module contains the primary objects that power Requests. +""" + +import datetime + +# Import encoding now, to avoid implicit import later. +# Implicit import within threads may cause LookupError when standard library is in a ZIP, +# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. +import encodings.idna # noqa: F401 +from io import UnsupportedOperation + +from pip._vendor.urllib3.exceptions import ( + DecodeError, + LocationParseError, + ProtocolError, + ReadTimeoutError, + SSLError, +) +from pip._vendor.urllib3.fields import RequestField +from pip._vendor.urllib3.filepost import encode_multipart_formdata +from pip._vendor.urllib3.util import parse_url + +from ._internal_utils import to_native_string, unicode_is_ascii +from .auth import HTTPBasicAuth +from .compat import ( + Callable, + JSONDecodeError, + Mapping, + basestring, + builtin_str, + chardet, + cookielib, +) +from .compat import json as complexjson +from .compat import urlencode, urlsplit, urlunparse +from .cookies import _copy_cookie_jar, cookiejar_from_dict, get_cookie_header +from .exceptions import ( + ChunkedEncodingError, + ConnectionError, + ContentDecodingError, + HTTPError, + InvalidJSONError, + InvalidURL, +) +from .exceptions import JSONDecodeError as RequestsJSONDecodeError +from .exceptions import MissingSchema +from .exceptions import SSLError as RequestsSSLError +from .exceptions import StreamConsumedError +from .hooks import default_hooks +from .status_codes import codes +from .structures import CaseInsensitiveDict +from .utils import ( + check_header_validity, + get_auth_from_url, + guess_filename, + guess_json_utf, + iter_slices, + parse_header_links, + requote_uri, + stream_decode_response_unicode, + super_len, + to_key_val_list, +) + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT = 30 +CONTENT_CHUNK_SIZE = 10 * 1024 +ITER_CHUNK_SIZE = 512 + + +class RequestEncodingMixin: + @property + def path_url(self): + """Build the path URL to use.""" + + url = [] + + p = urlsplit(self.url) + + path = p.path + if not path: + path = "/" + + url.append(path) + + query = p.query + if query: + url.append("?") + url.append(query) + + return "".join(url) + + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, "read"): + return data + elif hasattr(data, "__iter__"): + result = [] + for k, vs in to_key_val_list(data): + if isinstance(vs, basestring) or not hasattr(vs, "__iter__"): + vs = [vs] + for v in vs: + if v is not None: + result.append( + ( + k.encode("utf-8") if isinstance(k, str) else k, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + return urlencode(result, doseq=True) + else: + return data + + @staticmethod + def _encode_files(files, data): + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + tuples. Order is retained if data is a list of tuples but arbitrary + if parameters are supplied as a dict. + The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) + or 4-tuples (filename, fileobj, contentype, custom_headers). + """ + if not files: + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, "__iter__"): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + v = str(v) + + new_fields.append( + ( + field.decode("utf-8") + if isinstance(field, bytes) + else field, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + + for (k, v) in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + elif hasattr(fp, "read"): + fdata = fp.read() + elif fp is None: + continue + else: + fdata = fp + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin: + def register_hook(self, event, hook): + """Properly register a hook.""" + + if event not in self.hooks: + raise ValueError(f'Unsupported event specified, with event name "{event}"') + + if isinstance(hook, Callable): + self.hooks[event].append(hook) + elif hasattr(hook, "__iter__"): + self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) + + def deregister_hook(self, event, hook): + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request ` object. + + Used to prepare a :class:`PreparedRequest `, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: URL parameters to append to the URL. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> req.prepare() + + """ + + def __init__( + self, + method=None, + url=None, + headers=None, + files=None, + data=None, + params=None, + auth=None, + cookies=None, + hooks=None, + json=None, + ): + + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for (k, v) in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self): + return f"" + + def prepare(self): + """Constructs a :class:`PreparedRequest ` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest ` object, + containing the exact bytes that will be sent to the server. + + Instances are generated from a :class:`Request ` object, and + should not be instantiated manually; doing so may produce undesirable + effects. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> r = req.prepare() + >>> r + + + >>> s = requests.Session() + >>> s.send(r) + + """ + + def __init__(self): + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + #: integer denoting starting position of a readable file-like body. + self._body_position = None + + def prepare( + self, + method=None, + url=None, + headers=None, + files=None, + data=None, + params=None, + auth=None, + cookies=None, + hooks=None, + json=None, + ): + """Prepares the entire request with the given parameters.""" + + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self): + return f"" + + def copy(self): + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + p._body_position = self._body_position + return p + + def prepare_method(self, method): + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + @staticmethod + def _get_idna_encoded_host(host): + from pip._vendor import idna + + try: + host = idna.encode(host, uts46=True).decode("utf-8") + except idna.IDNAError: + raise UnicodeError + return host + + def prepare_url(self, url, params): + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/psf/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode("utf8") + else: + url = str(url) + + # Remove leading whitespaces from url + url = url.lstrip() + + # Don't do any URL preparation for non-HTTP schemes like `mailto`, + # `data` etc to work around exceptions from `url_parse`, which + # handles RFC 3986 only. + if ":" in url and not url.lower().startswith("http"): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + raise InvalidURL(*e.args) + + if not scheme: + raise MissingSchema( + f"Invalid URL {url!r}: No scheme supplied. " + f"Perhaps you meant https://{url}?" + ) + + if not host: + raise InvalidURL(f"Invalid URL {url!r}: No host supplied") + + # In general, we want to try IDNA encoding the hostname if the string contains + # non-ASCII characters. This allows users to automatically get the correct IDNA + # behaviour. For strings containing only ASCII characters, we need to also verify + # it doesn't start with a wildcard (*), before allowing the unencoded hostname. + if not unicode_is_ascii(host): + try: + host = self._get_idna_encoded_host(host) + except UnicodeError: + raise InvalidURL("URL has an invalid label.") + elif host.startswith(("*", ".")): + raise InvalidURL("URL has an invalid label.") + + # Carefully reconstruct the network location + netloc = auth or "" + if netloc: + netloc += "@" + netloc += host + if port: + netloc += f":{port}" + + # Bare domains aren't valid URLs. + if not path: + path = "/" + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + enc_params = self._encode_params(params) + if enc_params: + if query: + query = f"{query}&{enc_params}" + else: + query = enc_params + + url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) + self.url = url + + def prepare_headers(self, headers): + """Prepares the given HTTP headers.""" + + self.headers = CaseInsensitiveDict() + if headers: + for header in headers.items(): + # Raise exception on invalid header value. + check_header_validity(header) + name, value = header + self.headers[to_native_string(name)] = value + + def prepare_body(self, data, files, json=None): + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + + if not data and json is not None: + # urllib3 requires a bytes-like body. Python 2's json.dumps + # provides this natively, but Python 3 gives a Unicode string. + content_type = "application/json" + + try: + body = complexjson.dumps(json, allow_nan=False) + except ValueError as ve: + raise InvalidJSONError(ve, request=self) + + if not isinstance(body, bytes): + body = body.encode("utf-8") + + is_stream = all( + [ + hasattr(data, "__iter__"), + not isinstance(data, (basestring, list, tuple, Mapping)), + ] + ) + + if is_stream: + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + body = data + + if getattr(body, "tell", None) is not None: + # Record the current file position before reading. + # This will allow us to rewind a file in the event + # of a redirect. + try: + self._body_position = body.tell() + except OSError: + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body + self._body_position = object() + + if files: + raise NotImplementedError( + "Streamed bodies and files are mutually exclusive." + ) + + if length: + self.headers["Content-Length"] = builtin_str(length) + else: + self.headers["Transfer-Encoding"] = "chunked" + else: + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, data) + else: + if data: + body = self._encode_params(data) + if isinstance(data, basestring) or hasattr(data, "read"): + content_type = None + else: + content_type = "application/x-www-form-urlencoded" + + self.prepare_content_length(body) + + # Add content-type if it wasn't explicitly provided. + if content_type and ("content-type" not in self.headers): + self.headers["Content-Type"] = content_type + + self.body = body + + def prepare_content_length(self, body): + """Prepare Content-Length header based on request method and body""" + if body is not None: + length = super_len(body) + if length: + # If length exists, set it. Otherwise, we fallback + # to Transfer-Encoding: chunked. + self.headers["Content-Length"] = builtin_str(length) + elif ( + self.method not in ("GET", "HEAD") + and self.headers.get("Content-Length") is None + ): + # Set Content-Length to 0 for methods that can have a body + # but don't provide one. (i.e. not GET or HEAD) + self.headers["Content-Length"] = "0" + + def prepare_auth(self, auth, url=""): + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(self.url) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: + # special-case basic HTTP auth + auth = HTTPBasicAuth(*auth) + + # Allow auth to make its changes. + r = auth(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies(self, cookies): + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest ` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand. + """ + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers["Cookie"] = cookie_header + + def prepare_hooks(self, hooks): + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or [] + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response: + """The :class:`Response ` object, which contains a + server's response to an HTTP request. + """ + + __attrs__ = [ + "_content", + "status_code", + "headers", + "url", + "history", + "encoding", + "reason", + "cookies", + "elapsed", + "request", + ] + + def __init__(self): + self._content = False + self._content_consumed = False + self._next = None + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + #: This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response ` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest ` object to which this + #: is a response. + self.request = None + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, "_content_consumed", True) + setattr(self, "raw", None) + + def __repr__(self): + return f"" + + def __bool__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __nonzero__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __iter__(self): + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self): + """Returns True if :attr:`status_code` is less than 400, False if not. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self): + """True if this Response is a well-formed HTTP redirect that could have + been processed automatically (by :meth:`Session.resolve_redirects`). + """ + return "location" in self.headers and self.status_code in REDIRECT_STATI + + @property + def is_permanent_redirect(self): + """True if this Response one of the permanent versions of redirect.""" + return "location" in self.headers and self.status_code in ( + codes.moved_permanently, + codes.permanent_redirect, + ) + + @property + def next(self): + """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" + return self._next + + @property + def apparent_encoding(self): + """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" + return chardet.detect(self.content)["encoding"] + + def iter_content(self, chunk_size=1, decode_unicode=False): + """Iterates over the response data. When stream=True is set on the + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ + + def generate(): + # Special case for urllib3. + if hasattr(self.raw, "stream"): + try: + yield from self.raw.stream(chunk_size, decode_content=True) + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + except SSLError as e: + raise RequestsSSLError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + if self._content_consumed and isinstance(self._content, bool): + raise StreamConsumedError() + elif chunk_size is not None and not isinstance(chunk_size, int): + raise TypeError( + f"chunk_size must be an int, it is instead a {type(chunk_size)}." + ) + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + def iter_lines( + self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None + ): + """Iterates over the response data, one line at a time. When + stream=True is set on the request, this avoids reading the + content at once into memory for large responses. + + .. note:: This method is not reentrant safe. + """ + + pending = None + + for chunk in self.iter_content( + chunk_size=chunk_size, decode_unicode=decode_unicode + ): + + if pending is not None: + chunk = pending + chunk + + if delimiter: + lines = chunk.split(delimiter) + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + yield from lines + + if pending is not None: + yield pending + + @property + def content(self): + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + if self._content_consumed: + raise RuntimeError("The content for this response was already consumed") + + if self.status_code == 0 or self.raw is None: + self._content = None + else: + self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b"" + + self._content_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content + + @property + def text(self): + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``charset_normalizer`` or ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return "" + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + content = str(self.content, encoding, errors="replace") + except (LookupError, TypeError): + # A LookupError is raised if the encoding was not found which could + # indicate a misspelling or similar mistake. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors="replace") + + return content + + def json(self, **kwargs): + r"""Returns the json-encoded content of a response, if any. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + :raises requests.exceptions.JSONDecodeError: If the response body does not + contain valid json. + """ + + if not self.encoding and self.content and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using charset_normalizer to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + return complexjson.loads(self.content.decode(encoding), **kwargs) + except UnicodeDecodeError: + # Wrong UTF codec detected; usually because it's not UTF-8 + # but some other 8-bit codec. This is an RFC violation, + # and the server didn't bother to tell us what codec *was* + # used. + pass + except JSONDecodeError as e: + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + try: + return complexjson.loads(self.text, **kwargs) + except JSONDecodeError as e: + # Catch JSON-related errors and raise as requests.JSONDecodeError + # This aliases json.JSONDecodeError and simplejson.JSONDecodeError + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + @property + def links(self): + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get("link") + + resolved_links = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get("rel") or link.get("url") + resolved_links[key] = link + + return resolved_links + + def raise_for_status(self): + """Raises :class:`HTTPError`, if one occurred.""" + + http_error_msg = "" + if isinstance(self.reason, bytes): + # We attempt to decode utf-8 first because some servers + # choose to localize their reason strings. If the string + # isn't utf-8, we fall back to iso-8859-1 for all other + # encodings. (See PR #3538) + try: + reason = self.reason.decode("utf-8") + except UnicodeDecodeError: + reason = self.reason.decode("iso-8859-1") + else: + reason = self.reason + + if 400 <= self.status_code < 500: + http_error_msg = ( + f"{self.status_code} Client Error: {reason} for url: {self.url}" + ) + + elif 500 <= self.status_code < 600: + http_error_msg = ( + f"{self.status_code} Server Error: {reason} for url: {self.url}" + ) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self): + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + self.raw.close() + + release_conn = getattr(self.raw, "release_conn", None) + if release_conn is not None: + release_conn() diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/packages.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/packages.py new file mode 100644 index 00000000..9582fa73 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/packages.py @@ -0,0 +1,16 @@ +import sys + +# This code exists for backwards compatibility reasons. +# I don't like it either. Just look the other way. :) + +for package in ('urllib3', 'idna', 'chardet'): + vendored_package = "pip._vendor." + package + locals()[package] = __import__(vendored_package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == vendored_package or mod.startswith(vendored_package + '.'): + unprefixed_mod = mod[len("pip._vendor."):] + sys.modules['pip._vendor.requests.packages.' + unprefixed_mod] = sys.modules[mod] + +# Kinda cool, though, right? diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/sessions.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/sessions.py new file mode 100644 index 00000000..dbcf2a7b --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/sessions.py @@ -0,0 +1,833 @@ +""" +requests.sessions +~~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). +""" +import os +import sys +import time +from collections import OrderedDict +from datetime import timedelta + +from ._internal_utils import to_native_string +from .adapters import HTTPAdapter +from .auth import _basic_auth_str +from .compat import Mapping, cookielib, urljoin, urlparse +from .cookies import ( + RequestsCookieJar, + cookiejar_from_dict, + extract_cookies_to_jar, + merge_cookies, +) +from .exceptions import ( + ChunkedEncodingError, + ContentDecodingError, + InvalidSchema, + TooManyRedirects, +) +from .hooks import default_hooks, dispatch_hook + +# formerly defined here, reexposed here for backward compatibility +from .models import ( # noqa: F401 + DEFAULT_REDIRECT_LIMIT, + REDIRECT_STATI, + PreparedRequest, + Request, +) +from .status_codes import codes +from .structures import CaseInsensitiveDict +from .utils import ( # noqa: F401 + DEFAULT_PORTS, + default_headers, + get_auth_from_url, + get_environ_proxies, + get_netrc_auth, + requote_uri, + resolve_proxies, + rewind_body, + should_bypass_proxies, + to_key_val_list, +) + +# Preferred clock, based on which one is more accurate on a given system. +if sys.platform == "win32": + preferred_clock = time.perf_counter +else: + preferred_clock = time.time + + +def merge_setting(request_setting, session_setting, dict_class=OrderedDict): + """Determines appropriate setting for a given request, taking into account + the explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) + merged_setting.update(to_key_val_list(request_setting)) + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + """Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get("response") == []: + return request_hooks + + if request_hooks is None or request_hooks.get("response") == []: + return session_hooks + + return merge_setting(request_hooks, session_hooks, dict_class) + + +class SessionRedirectMixin: + def get_redirect_target(self, resp): + """Receives a Response. Returns a redirect URI or ``None``""" + # Due to the nature of how requests processes redirects this method will + # be called at least once upon the original response and at least twice + # on each subsequent redirect response (if any). + # If a custom mixin is used to handle this logic, it may be advantageous + # to cache the redirect location onto the response object as a private + # attribute. + if resp.is_redirect: + location = resp.headers["location"] + # Currently the underlying http module on py3 decode headers + # in latin1, but empirical evidence suggests that latin1 is very + # rarely used with non-ASCII characters in HTTP headers. + # It is more likely to get UTF8 header rather than latin1. + # This causes incorrect handling of UTF8 encoded location headers. + # To solve this, we re-encode the location in latin1. + location = location.encode("latin1") + return to_native_string(location, "utf8") + return None + + def should_strip_auth(self, old_url, new_url): + """Decide whether Authorization header should be removed when redirecting""" + old_parsed = urlparse(old_url) + new_parsed = urlparse(new_url) + if old_parsed.hostname != new_parsed.hostname: + return True + # Special case: allow http -> https redirect when using the standard + # ports. This isn't specified by RFC 7235, but is kept to avoid + # breaking backwards compatibility with older versions of requests + # that allowed any redirects on the same host. + if ( + old_parsed.scheme == "http" + and old_parsed.port in (80, None) + and new_parsed.scheme == "https" + and new_parsed.port in (443, None) + ): + return False + + # Handle default port usage corresponding to scheme. + changed_port = old_parsed.port != new_parsed.port + changed_scheme = old_parsed.scheme != new_parsed.scheme + default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) + if ( + not changed_scheme + and old_parsed.port in default_port + and new_parsed.port in default_port + ): + return False + + # Standard case: root URI must match + return changed_port or changed_scheme + + def resolve_redirects( + self, + resp, + req, + stream=False, + timeout=None, + verify=True, + cert=None, + proxies=None, + yield_requests=False, + **adapter_kwargs, + ): + """Receives a Response. Returns a generator of Responses or Requests.""" + + hist = [] # keep track of history + + url = self.get_redirect_target(resp) + previous_fragment = urlparse(req.url).fragment + while url: + prepared_request = req.copy() + + # Update history and keep track of redirects. + # resp.history must ignore the original request in this loop + hist.append(resp) + resp.history = hist[1:] + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if len(resp.history) >= self.max_redirects: + raise TooManyRedirects( + f"Exceeded {self.max_redirects} redirects.", response=resp + ) + + # Release the connection back into the pool. + resp.close() + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith("//"): + parsed_rurl = urlparse(resp.url) + url = ":".join([to_native_string(parsed_rurl.scheme), url]) + + # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) + parsed = urlparse(url) + if parsed.fragment == "" and previous_fragment: + parsed = parsed._replace(fragment=previous_fragment) + elif parsed.fragment: + previous_fragment = parsed.fragment + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + + self.rebuild_method(prepared_request, resp) + + # https://github.com/psf/requests/issues/1084 + if resp.status_code not in ( + codes.temporary_redirect, + codes.permanent_redirect, + ): + # https://github.com/psf/requests/issues/3490 + purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding") + for header in purged_headers: + prepared_request.headers.pop(header, None) + prepared_request.body = None + + headers = prepared_request.headers + headers.pop("Cookie", None) + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) + merge_cookies(prepared_request._cookies, self.cookies) + prepared_request.prepare_cookies(prepared_request._cookies) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # A failed tell() sets `_body_position` to `object()`. This non-None + # value ensures `rewindable` will be True, allowing us to raise an + # UnrewindableBodyError, instead of hanging the connection. + rewindable = prepared_request._body_position is not None and ( + "Content-Length" in headers or "Transfer-Encoding" in headers + ) + + # Attempt to rewind consumed file-like object. + if rewindable: + rewind_body(prepared_request) + + # Override the original request. + req = prepared_request + + if yield_requests: + yield req + else: + + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs, + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + # extract redirect url, if any, for the next loop + url = self.get_redirect_target(resp) + yield resp + + def rebuild_auth(self, prepared_request, response): + """When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.url + + if "Authorization" in headers and self.should_strip_auth( + response.request.url, url + ): + # If we get redirected to a new host, we should strip out any + # authentication headers. + del headers["Authorization"] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + + def rebuild_proxies(self, prepared_request, proxies): + """This method re-evaluates the proxy configuration by considering the + environment variables. If we are redirected to a URL covered by + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + + :rtype: dict + """ + headers = prepared_request.headers + scheme = urlparse(prepared_request.url).scheme + new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env) + + if "Proxy-Authorization" in headers: + del headers["Proxy-Authorization"] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + # urllib3 handles proxy authorization for us in the standard adapter. + # Avoid appending this to TLS tunneled requests where it may be leaked. + if not scheme.startswith('https') and username and password: + headers["Proxy-Authorization"] = _basic_auth_str(username, password) + + return new_proxies + + def rebuild_method(self, prepared_request, response): + """When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + method = prepared_request.method + + # https://tools.ietf.org/html/rfc7231#section-6.4.4 + if response.status_code == codes.see_other and method != "HEAD": + method = "GET" + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + if response.status_code == codes.found and method != "HEAD": + method = "GET" + + # Second, if a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in Issue 1704. + if response.status_code == codes.moved and method == "POST": + method = "GET" + + prepared_request.method = method + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('https://httpbin.org/get') + + + Or as a context manager:: + + >>> with requests.Session() as s: + ... s.get('https://httpbin.org/get') + + """ + + __attrs__ = [ + "headers", + "cookies", + "auth", + "proxies", + "hooks", + "params", + "verify", + "cert", + "adapters", + "stream", + "trust_env", + "max_redirects", + ] + + def __init__(self): + + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request ` sent from this + #: :class:`Session `. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request `. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request `. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request `. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + #: Defaults to `True`, requiring requests to verify the TLS certificate at the + #: remote end. + #: If verify is set to `False`, requests will accept any TLS certificate + #: presented by the server, and will ignore hostname mismatches and/or + #: expired certificates, which will make your application vulnerable to + #: man-in-the-middle (MitM) attacks. + #: Only set this to `False` for testing. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar `, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount("https://", HTTPAdapter()) + self.mount("http://", HTTPAdapter()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def prepare_request(self, request): + """Constructs a :class:`PreparedRequest ` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request ` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + :rtype: requests.PreparedRequest + """ + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies + ) + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(request.url) + + p = PreparedRequest() + p.prepare( + method=request.method.upper(), + url=request.url, + files=request.files, + data=request.data, + json=request.json, + headers=merge_setting( + request.headers, self.headers, dict_class=CaseInsensitiveDict + ), + params=merge_setting(request.params, self.params), + auth=merge_setting(auth, self.auth), + cookies=merged_cookies, + hooks=merge_hooks(request.hooks, self.hooks), + ) + return p + + def request( + self, + method, + url, + params=None, + data=None, + headers=None, + cookies=None, + files=None, + auth=None, + timeout=None, + allow_redirects=True, + proxies=None, + hooks=None, + stream=None, + verify=None, + cert=None, + json=None, + ): + """Constructs a :class:`Request `, prepares it and sends it. + Returns :class:`Response ` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ + # Create the Request. + req = Request( + method=method.upper(), + url=url, + headers=headers, + files=files, + data=data or {}, + json=json, + params=params or {}, + auth=auth, + cookies=cookies, + hooks=hooks, + ) + prep = self.prepare_request(req) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + "timeout": timeout, + "allow_redirects": allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get(self, url, **kwargs): + r"""Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", True) + return self.request("GET", url, **kwargs) + + def options(self, url, **kwargs): + r"""Sends a OPTIONS request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", True) + return self.request("OPTIONS", url, **kwargs) + + def head(self, url, **kwargs): + r"""Sends a HEAD request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", False) + return self.request("HEAD", url, **kwargs) + + def post(self, url, data=None, json=None, **kwargs): + r"""Sends a POST request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("POST", url, data=data, json=json, **kwargs) + + def put(self, url, data=None, **kwargs): + r"""Sends a PUT request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("PUT", url, data=data, **kwargs) + + def patch(self, url, data=None, **kwargs): + r"""Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("PATCH", url, data=data, **kwargs) + + def delete(self, url, **kwargs): + r"""Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("DELETE", url, **kwargs) + + def send(self, request, **kwargs): + """Send a given PreparedRequest. + + :rtype: requests.Response + """ + # Set defaults that the hooks can utilize to ensure they always have + # the correct parameters to reproduce the previous request. + kwargs.setdefault("stream", self.stream) + kwargs.setdefault("verify", self.verify) + kwargs.setdefault("cert", self.cert) + if "proxies" not in kwargs: + kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env) + + # It's possible that users might accidentally send a Request object. + # Guard against that specific failure case. + if isinstance(request, Request): + raise ValueError("You can only send PreparedRequests.") + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop("allow_redirects", True) + stream = kwargs.get("stream") + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = preferred_clock() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + elapsed = preferred_clock() - start + r.elapsed = timedelta(seconds=elapsed) + + # Response manipulation hooks + r = dispatch_hook("response", hooks, r, **kwargs) + + # Persist cookies + if r.history: + + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Resolve redirects if allowed. + if allow_redirects: + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + history = [resp for resp in gen] + else: + history = [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + # If redirects aren't being followed, store the response on the Request for Response.next(). + if not allow_redirects: + try: + r._next = next( + self.resolve_redirects(r, request, yield_requests=True, **kwargs) + ) + except StopIteration: + pass + + if not stream: + r.content + + return r + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """ + Check the environment and merge it with some settings. + + :rtype: dict + """ + # Gather clues from the surrounding environment. + if self.trust_env: + # Set environment's proxies. + no_proxy = proxies.get("no_proxy") if proxies is not None else None + env_proxies = get_environ_proxies(url, no_proxy=no_proxy) + for (k, v) in env_proxies.items(): + proxies.setdefault(k, v) + + # Look for requests environment configuration + # and be compatible with cURL. + if verify is True or verify is None: + verify = ( + os.environ.get("REQUESTS_CA_BUNDLE") + or os.environ.get("CURL_CA_BUNDLE") + or verify + ) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert} + + def get_adapter(self, url): + """ + Returns the appropriate connection adapter for the given URL. + + :rtype: requests.adapters.BaseAdapter + """ + for (prefix, adapter) in self.adapters.items(): + + if url.lower().startswith(prefix.lower()): + return adapter + + # Nothing matches :-/ + raise InvalidSchema(f"No connection adapters were found for {url!r}") + + def close(self): + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix, adapter): + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by prefix length. + """ + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self): + state = {attr: getattr(self, attr, None) for attr in self.__attrs__} + return state + + def __setstate__(self, state): + for attr, value in state.items(): + setattr(self, attr, value) + + +def session(): + """ + Returns a :class:`Session` for context-management. + + .. deprecated:: 1.0.0 + + This method has been deprecated since version 1.0.0 and is only kept for + backwards compatibility. New code should use :class:`~requests.sessions.Session` + to create a session. This may be removed at a future date. + + :rtype: Session + """ + return Session() diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/status_codes.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/status_codes.py new file mode 100644 index 00000000..4bd072be --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/status_codes.py @@ -0,0 +1,128 @@ +r""" +The ``codes`` object defines a mapping from common names for HTTP statuses +to their numerical codes, accessible either as attributes or as dictionary +items. + +Example:: + + >>> import requests + >>> requests.codes['temporary_redirect'] + 307 + >>> requests.codes.teapot + 418 + >>> requests.codes['\o/'] + 200 + +Some codes have multiple names, and both upper- and lower-case versions of +the names are allowed. For example, ``codes.ok``, ``codes.OK``, and +``codes.okay`` all correspond to the HTTP status code 200. +""" + +from .structures import LookupDict + +_codes = { + # Informational. + 100: ("continue",), + 101: ("switching_protocols",), + 102: ("processing",), + 103: ("checkpoint",), + 122: ("uri_too_long", "request_uri_too_long"), + 200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", "✓"), + 201: ("created",), + 202: ("accepted",), + 203: ("non_authoritative_info", "non_authoritative_information"), + 204: ("no_content",), + 205: ("reset_content", "reset"), + 206: ("partial_content", "partial"), + 207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"), + 208: ("already_reported",), + 226: ("im_used",), + # Redirection. + 300: ("multiple_choices",), + 301: ("moved_permanently", "moved", "\\o-"), + 302: ("found",), + 303: ("see_other", "other"), + 304: ("not_modified",), + 305: ("use_proxy",), + 306: ("switch_proxy",), + 307: ("temporary_redirect", "temporary_moved", "temporary"), + 308: ( + "permanent_redirect", + "resume_incomplete", + "resume", + ), # "resume" and "resume_incomplete" to be removed in 3.0 + # Client Error. + 400: ("bad_request", "bad"), + 401: ("unauthorized",), + 402: ("payment_required", "payment"), + 403: ("forbidden",), + 404: ("not_found", "-o-"), + 405: ("method_not_allowed", "not_allowed"), + 406: ("not_acceptable",), + 407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"), + 408: ("request_timeout", "timeout"), + 409: ("conflict",), + 410: ("gone",), + 411: ("length_required",), + 412: ("precondition_failed", "precondition"), + 413: ("request_entity_too_large",), + 414: ("request_uri_too_large",), + 415: ("unsupported_media_type", "unsupported_media", "media_type"), + 416: ( + "requested_range_not_satisfiable", + "requested_range", + "range_not_satisfiable", + ), + 417: ("expectation_failed",), + 418: ("im_a_teapot", "teapot", "i_am_a_teapot"), + 421: ("misdirected_request",), + 422: ("unprocessable_entity", "unprocessable"), + 423: ("locked",), + 424: ("failed_dependency", "dependency"), + 425: ("unordered_collection", "unordered"), + 426: ("upgrade_required", "upgrade"), + 428: ("precondition_required", "precondition"), + 429: ("too_many_requests", "too_many"), + 431: ("header_fields_too_large", "fields_too_large"), + 444: ("no_response", "none"), + 449: ("retry_with", "retry"), + 450: ("blocked_by_windows_parental_controls", "parental_controls"), + 451: ("unavailable_for_legal_reasons", "legal_reasons"), + 499: ("client_closed_request",), + # Server Error. + 500: ("internal_server_error", "server_error", "/o\\", "✗"), + 501: ("not_implemented",), + 502: ("bad_gateway",), + 503: ("service_unavailable", "unavailable"), + 504: ("gateway_timeout",), + 505: ("http_version_not_supported", "http_version"), + 506: ("variant_also_negotiates",), + 507: ("insufficient_storage",), + 509: ("bandwidth_limit_exceeded", "bandwidth"), + 510: ("not_extended",), + 511: ("network_authentication_required", "network_auth", "network_authentication"), +} + +codes = LookupDict(name="status_codes") + + +def _init(): + for code, titles in _codes.items(): + for title in titles: + setattr(codes, title, code) + if not title.startswith(("\\", "/")): + setattr(codes, title.upper(), code) + + def doc(code): + names = ", ".join(f"``{n}``" for n in _codes[code]) + return "* %d: %s" % (code, names) + + global __doc__ + __doc__ = ( + __doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes)) + if __doc__ is not None + else None + ) + + +_init() diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/structures.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/structures.py new file mode 100644 index 00000000..188e13e4 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/structures.py @@ -0,0 +1,99 @@ +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. +""" + +from collections import OrderedDict + +from .compat import Mapping, MutableMapping + + +class CaseInsensitiveDict(MutableMapping): + """A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + """ + + def __init__(self, data=None, **kwargs): + self._store = OrderedDict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items()) + + def __eq__(self, other): + if isinstance(other, Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return str(dict(self.items())) + + +class LookupDict(dict): + """Dictionary lookup object.""" + + def __init__(self, name=None): + self.name = name + super().__init__() + + def __repr__(self): + return f"" + + def __getitem__(self, key): + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + def get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/requests/utils.py b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/utils.py new file mode 100644 index 00000000..36607eda --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/requests/utils.py @@ -0,0 +1,1094 @@ +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. +""" + +import codecs +import contextlib +import io +import os +import re +import socket +import struct +import sys +import tempfile +import warnings +import zipfile +from collections import OrderedDict + +from pip._vendor.urllib3.util import make_headers, parse_url + +from . import certs +from .__version__ import __version__ + +# to_native_string is unused here, but imported here for backwards compatibility +from ._internal_utils import ( # noqa: F401 + _HEADER_VALIDATORS_BYTE, + _HEADER_VALIDATORS_STR, + HEADER_VALIDATORS, + to_native_string, +) +from .compat import ( + Mapping, + basestring, + bytes, + getproxies, + getproxies_environment, + integer_types, +) +from .compat import parse_http_list as _parse_list_header +from .compat import ( + proxy_bypass, + proxy_bypass_environment, + quote, + str, + unquote, + urlparse, + urlunparse, +) +from .cookies import cookiejar_from_dict +from .exceptions import ( + FileModeWarning, + InvalidHeader, + InvalidURL, + UnrewindableBodyError, +) +from .structures import CaseInsensitiveDict + +NETRC_FILES = (".netrc", "_netrc") + +DEFAULT_CA_BUNDLE_PATH = certs.where() + +DEFAULT_PORTS = {"http": 80, "https": 443} + +# Ensure that ', ' is used to preserve previous delimiter behavior. +DEFAULT_ACCEPT_ENCODING = ", ".join( + re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"]) +) + + +if sys.platform == "win32": + # provide a proxy_bypass version on Windows without DNS lookups + + def proxy_bypass_registry(host): + try: + import winreg + except ImportError: + return False + + try: + internetSettings = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", + ) + # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it + proxyEnable = int(winreg.QueryValueEx(internetSettings, "ProxyEnable")[0]) + # ProxyOverride is almost always a string + proxyOverride = winreg.QueryValueEx(internetSettings, "ProxyOverride")[0] + except (OSError, ValueError): + return False + if not proxyEnable or not proxyOverride: + return False + + # make a check value list from the registry entry: replace the + # '' string by the localhost entry and the corresponding + # canonical entry. + proxyOverride = proxyOverride.split(";") + # now check if we match one of the registry values. + for test in proxyOverride: + if test == "": + if "." not in host: + return True + test = test.replace(".", r"\.") # mask dots + test = test.replace("*", r".*") # change glob sequence + test = test.replace("?", r".") # change glob char + if re.match(test, host, re.I): + return True + return False + + def proxy_bypass(host): # noqa + """Return True, if the host should be bypassed. + + Checks proxy settings gathered from the environment, if specified, + or the registry. + """ + if getproxies_environment(): + return proxy_bypass_environment(host) + else: + return proxy_bypass_registry(host) + + +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, "items"): + d = d.items() + + return d + + +def super_len(o): + total_length = None + current_position = 0 + + if hasattr(o, "__len__"): + total_length = len(o) + + elif hasattr(o, "len"): + total_length = o.len + + elif hasattr(o, "fileno"): + try: + fileno = o.fileno() + except (io.UnsupportedOperation, AttributeError): + # AttributeError is a surprising exception, seeing as how we've just checked + # that `hasattr(o, 'fileno')`. It happens for objects obtained via + # `Tarfile.extractfile()`, per issue 5229. + pass + else: + total_length = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + if "b" not in o.mode: + warnings.warn( + ( + "Requests has determined the content-length for this " + "request using the binary size of the file: however, the " + "file has been opened in text mode (i.e. without the 'b' " + "flag in the mode). This may lead to an incorrect " + "content-length. In Requests 3.0, support will be removed " + "for files in text mode." + ), + FileModeWarning, + ) + + if hasattr(o, "tell"): + try: + current_position = o.tell() + except OSError: + # This can happen in some weird situations, such as when the file + # is actually a special file descriptor like stdin. In this + # instance, we don't know what the length is, so set it to zero and + # let requests chunk it instead. + if total_length is not None: + current_position = total_length + else: + if hasattr(o, "seek") and total_length is None: + # StringIO and BytesIO have seek but no usable fileno + try: + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + except OSError: + total_length = 0 + + if total_length is None: + total_length = 0 + + return max(0, total_length - current_position) + + +def get_netrc_auth(url, raise_errors=False): + """Returns the Requests tuple auth for a given url from netrc.""" + + netrc_file = os.environ.get("NETRC") + if netrc_file is not None: + netrc_locations = (netrc_file,) + else: + netrc_locations = (f"~/{f}" for f in NETRC_FILES) + + try: + from netrc import NetrcParseError, netrc + + netrc_path = None + + for f in netrc_locations: + try: + loc = os.path.expanduser(f) + except KeyError: + # os.path.expanduser can fail when $HOME is undefined and + # getpwuid fails. See https://bugs.python.org/issue20164 & + # https://github.com/psf/requests/issues/1846 + return + + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + + # Strip port numbers from netloc. This weird `if...encode`` dance is + # used for Python 3.2, which doesn't support unicode literals. + splitstr = b":" + if isinstance(url, str): + splitstr = splitstr.decode("ascii") + host = ri.netloc.split(splitstr)[0] + + try: + _netrc = netrc(netrc_path).authenticators(host) + if _netrc: + # Return with login / password + login_i = 0 if _netrc[0] else 1 + return (_netrc[login_i], _netrc[2]) + except (NetrcParseError, OSError): + # If there was a parsing error or a permissions issue reading the file, + # we'll just skip netrc auth unless explicitly asked to raise errors. + if raise_errors: + raise + + # App Engine hackiness. + except (ImportError, AttributeError): + pass + + +def guess_filename(obj): + """Tries to guess the filename of the given object.""" + name = getattr(obj, "name", None) + if name and isinstance(name, basestring) and name[0] != "<" and name[-1] != ">": + return os.path.basename(name) + + +def extract_zipped_paths(path): + """Replace nonexistent paths that look like they refer to a member of a zip + archive with the location of an extracted copy of the target, or else + just return the provided path unchanged. + """ + if os.path.exists(path): + # this is already a valid path, no need to do anything further + return path + + # find the first valid part of the provided path and treat that as a zip archive + # assume the rest of the path is the name of a member in the archive + archive, member = os.path.split(path) + while archive and not os.path.exists(archive): + archive, prefix = os.path.split(archive) + if not prefix: + # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split), + # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users + break + member = "/".join([prefix, member]) + + if not zipfile.is_zipfile(archive): + return path + + zip_file = zipfile.ZipFile(archive) + if member not in zip_file.namelist(): + return path + + # we have a valid zip archive and a valid member of that archive + tmp = tempfile.gettempdir() + extracted_path = os.path.join(tmp, member.split("/")[-1]) + if not os.path.exists(extracted_path): + # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition + with atomic_open(extracted_path) as file_handler: + file_handler.write(zip_file.read(member)) + return extracted_path + + +@contextlib.contextmanager +def atomic_open(filename): + """Write a file to the disk in an atomic fashion""" + tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) + try: + with os.fdopen(tmp_descriptor, "wb") as tmp_handler: + yield tmp_handler + os.replace(tmp_name, filename) + except BaseException: + os.remove(tmp_name) + raise + + +def from_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + + :rtype: OrderedDict + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError("cannot encode objects that are not 2-tuples") + + return OrderedDict(value) + + +def to_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + + :rtype: list + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError("cannot encode objects that are not 2-tuples") + + if isinstance(value, Mapping): + value = value.items() + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + :rtype: list + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + :rtype: dict + """ + result = {} + for item in _parse_list_header(value): + if "=" not in item: + result[item] = None + continue + name, value = item.split("=", 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + :rtype: str + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != "\\\\": + return value.replace("\\\\", "\\").replace('\\"', '"') + return value + + +def dict_from_cookiejar(cj): + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + :rtype: dict + """ + + cookie_dict = {} + + for cookie in cj: + cookie_dict[cookie.name] = cookie.value + + return cookie_dict + + +def add_dict_to_cookiejar(cj, cookie_dict): + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + :rtype: CookieJar + """ + + return cookiejar_from_dict(cookie_dict, cj) + + +def get_encodings_from_content(content): + """Returns encodings from given content string. + + :param content: bytestring to extract encodings from. + """ + warnings.warn( + ( + "In requests 3.0, get_encodings_from_content will be removed. For " + "more information, please see the discussion on issue #2266. (This" + " warning should only appear once.)" + ), + DeprecationWarning, + ) + + charset_re = re.compile(r']', flags=re.I) + pragma_re = re.compile(r']', flags=re.I) + xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') + + return ( + charset_re.findall(content) + + pragma_re.findall(content) + + xml_re.findall(content) + ) + + +def _parse_content_type_header(header): + """Returns content type and parameters from given header + + :param header: string + :return: tuple containing content type and dictionary of + parameters + """ + + tokens = header.split(";") + content_type, params = tokens[0].strip(), tokens[1:] + params_dict = {} + items_to_strip = "\"' " + + for param in params: + param = param.strip() + if param: + key, value = param, True + index_of_equals = param.find("=") + if index_of_equals != -1: + key = param[:index_of_equals].strip(items_to_strip) + value = param[index_of_equals + 1 :].strip(items_to_strip) + params_dict[key.lower()] = value + return content_type, params_dict + + +def get_encoding_from_headers(headers): + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + :rtype: str + """ + + content_type = headers.get("content-type") + + if not content_type: + return None + + content_type, params = _parse_content_type_header(content_type) + + if "charset" in params: + return params["charset"].strip("'\"") + + if "text" in content_type: + return "ISO-8859-1" + + if "application/json" in content_type: + # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset + return "utf-8" + + +def stream_decode_response_unicode(iterator, r): + """Stream decodes an iterator.""" + + if r.encoding is None: + yield from iterator + return + + decoder = codecs.getincrementaldecoder(r.encoding)(errors="replace") + for chunk in iterator: + rv = decoder.decode(chunk) + if rv: + yield rv + rv = decoder.decode(b"", final=True) + if rv: + yield rv + + +def iter_slices(string, slice_length): + """Iterate over slices of a string.""" + pos = 0 + if slice_length is None or slice_length <= 0: + slice_length = len(string) + while pos < len(string): + yield string[pos : pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r): + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + :rtype: str + """ + warnings.warn( + ( + "In requests 3.0, get_unicode_from_response will be removed. For " + "more information, please see the discussion on issue #2266. (This" + " warning should only appear once.)" + ), + DeprecationWarning, + ) + + tried_encodings = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding, errors="replace") + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~" +) + + +def unquote_unreserved(uri): + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + + :rtype: str + """ + parts = uri.split("%") + for i in range(1, len(parts)): + h = parts[i][0:2] + if len(h) == 2 and h.isalnum(): + try: + c = chr(int(h, 16)) + except ValueError: + raise InvalidURL(f"Invalid percent-escape sequence: '{h}'") + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = f"%{parts[i]}" + else: + parts[i] = f"%{parts[i]}" + return "".join(parts) + + +def requote_uri(uri): + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + + :rtype: str + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip, net): + """This function allows you to check if an IP belongs to a network subnet + + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + + :rtype: bool + """ + ipaddr = struct.unpack("=L", socket.inet_aton(ip))[0] + netaddr, bits = net.split("/") + netmask = struct.unpack("=L", socket.inet_aton(dotted_netmask(int(bits))))[0] + network = struct.unpack("=L", socket.inet_aton(netaddr))[0] & netmask + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask): + """Converts mask from /xx format to xxx.xxx.xxx.xxx + + Example: if mask is 24 function returns 255.255.255.0 + + :rtype: str + """ + bits = 0xFFFFFFFF ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack(">I", bits)) + + +def is_ipv4_address(string_ip): + """ + :rtype: bool + """ + try: + socket.inet_aton(string_ip) + except OSError: + return False + return True + + +def is_valid_cidr(string_network): + """ + Very simple check of the cidr format in no_proxy variable. + + :rtype: bool + """ + if string_network.count("/") == 1: + try: + mask = int(string_network.split("/")[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split("/")[0]) + except OSError: + return False + else: + return False + return True + + +@contextlib.contextmanager +def set_environ(env_name, value): + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + value_changed = value is not None + if value_changed: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value_changed: + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url, no_proxy): + """ + Returns whether we should bypass proxies or not. + + :rtype: bool + """ + # Prioritize lowercase environment variables over uppercase + # to keep a consistent behaviour with other http projects (curl, wget). + def get_proxy(key): + return os.environ.get(key) or os.environ.get(key.upper()) + + # First check whether no_proxy is defined. If it is, check that the URL + # we're getting isn't in the no_proxy list. + no_proxy_arg = no_proxy + if no_proxy is None: + no_proxy = get_proxy("no_proxy") + parsed = urlparse(url) + + if parsed.hostname is None: + # URLs don't always have hostnames, e.g. file:/// urls. + return True + + if no_proxy: + # We need to check whether we match here. We need to see if we match + # the end of the hostname, both with and without the port. + no_proxy = (host for host in no_proxy.replace(" ", "").split(",") if host) + + if is_ipv4_address(parsed.hostname): + for proxy_ip in no_proxy: + if is_valid_cidr(proxy_ip): + if address_in_network(parsed.hostname, proxy_ip): + return True + elif parsed.hostname == proxy_ip: + # If no_proxy ip was defined in plain IP notation instead of cidr notation & + # matches the IP of the index + return True + else: + host_with_port = parsed.hostname + if parsed.port: + host_with_port += f":{parsed.port}" + + for host in no_proxy: + if parsed.hostname.endswith(host) or host_with_port.endswith(host): + # The URL does match something in no_proxy, so we don't want + # to apply the proxies on this URL. + return True + + with set_environ("no_proxy", no_proxy_arg): + # parsed.hostname can be `None` in cases such as a file URI. + try: + bypass = proxy_bypass(parsed.hostname) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + + +def get_environ_proxies(url, no_proxy=None): + """ + Return a dict of environment proxies. + + :rtype: dict + """ + if should_bypass_proxies(url, no_proxy=no_proxy): + return {} + else: + return getproxies() + + +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + if urlparts.hostname is None: + return proxies.get(urlparts.scheme, proxies.get("all")) + + proxy_keys = [ + urlparts.scheme + "://" + urlparts.hostname, + urlparts.scheme, + "all://" + urlparts.hostname, + "all", + ] + proxy = None + for proxy_key in proxy_keys: + if proxy_key in proxies: + proxy = proxies[proxy_key] + break + + return proxy + + +def resolve_proxies(request, proxies, trust_env=True): + """This method takes proxy information from a request and configuration + input to resolve a mapping of target proxies. This will consider settings + such a NO_PROXY to strip proxy configurations. + + :param request: Request or PreparedRequest + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + :param trust_env: Boolean declaring whether to trust environment configs + + :rtype: dict + """ + proxies = proxies if proxies is not None else {} + url = request.url + scheme = urlparse(url).scheme + no_proxy = proxies.get("no_proxy") + new_proxies = proxies.copy() + + if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy): + environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) + + proxy = environ_proxies.get(scheme, environ_proxies.get("all")) + + if proxy: + new_proxies.setdefault(scheme, proxy) + return new_proxies + + +def default_user_agent(name="python-requests"): + """ + Return a string representing the default user agent. + + :rtype: str + """ + return f"{name}/{__version__}" + + +def default_headers(): + """ + :rtype: requests.structures.CaseInsensitiveDict + """ + return CaseInsensitiveDict( + { + "User-Agent": default_user_agent(), + "Accept-Encoding": DEFAULT_ACCEPT_ENCODING, + "Accept": "*/*", + "Connection": "keep-alive", + } + ) + + +def parse_header_links(value): + """Return a list of parsed link headers proxies. + + i.e. Link: ; rel=front; type="image/jpeg",; rel=back;type="image/jpeg" + + :rtype: list + """ + + links = [] + + replace_chars = " '\"" + + value = value.strip(replace_chars) + if not value: + return links + + for val in re.split(", *<", value): + try: + url, params = val.split(";", 1) + except ValueError: + url, params = val, "" + + link = {"url": url.strip("<> '\"")} + + for param in params.split(";"): + try: + key, value = param.split("=") + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = "\x00".encode("ascii") # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data): + """ + :rtype: str + """ + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # determine the encoding. Also detect a BOM, if present. + sample = data[:4] + if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): + return "utf-32" # BOM included + if sample[:3] == codecs.BOM_UTF8: + return "utf-8-sig" # BOM included, MS style (discouraged) + if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): + return "utf-16" # BOM included + nullcount = sample.count(_null) + if nullcount == 0: + return "utf-8" + if nullcount == 2: + if sample[::2] == _null2: # 1st and 3rd are null + return "utf-16-be" + if sample[1::2] == _null2: # 2nd and 4th are null + return "utf-16-le" + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return "utf-32-be" + if sample[1:] == _null3: + return "utf-32-le" + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url, new_scheme): + """Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument. + + :rtype: str + """ + parsed = parse_url(url) + scheme, auth, host, port, path, query, fragment = parsed + + # A defect in urlparse determines that there isn't a netloc present in some + # urls. We previously assumed parsing was overly cautious, and swapped the + # netloc and path. Due to a lack of tests on the original defect, this is + # maintained with parse_url for backwards compatibility. + netloc = parsed.netloc + if not netloc: + netloc, path = path, netloc + + if auth: + # parse_url doesn't provide the netloc with auth + # so we'll add it ourselves. + netloc = "@".join([auth, netloc]) + if scheme is None: + scheme = new_scheme + if path is None: + path = "" + + return urlunparse((scheme, netloc, path, "", query, fragment)) + + +def get_auth_from_url(url): + """Given a url with authentication components, extract them into a tuple of + username,password. + + :rtype: (str,str) + """ + parsed = urlparse(url) + + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): + auth = ("", "") + + return auth + + +def check_header_validity(header): + """Verifies that header parts don't contain leading whitespace + reserved characters, or return characters. + + :param header: tuple, in the format (name, value). + """ + name, value = header + _validate_header_part(header, name, 0) + _validate_header_part(header, value, 1) + + +def _validate_header_part(header, header_part, header_validator_index): + if isinstance(header_part, str): + validator = _HEADER_VALIDATORS_STR[header_validator_index] + elif isinstance(header_part, bytes): + validator = _HEADER_VALIDATORS_BYTE[header_validator_index] + else: + raise InvalidHeader( + f"Header part ({header_part!r}) from {header} " + f"must be of type str or bytes, not {type(header_part)}" + ) + + if not validator.match(header_part): + header_kind = "name" if header_validator_index == 0 else "value" + raise InvalidHeader( + f"Invalid leading whitespace, reserved character(s), or return" + f"character(s) in header {header_kind}: {header_part!r}" + ) + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part. + + :rtype: str + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit("@", 1)[-1] + + return urlunparse((scheme, netloc, path, params, query, "")) + + +def rewind_body(prepared_request): + """Move file pointer back to its recorded starting position + so it can be read again on redirect. + """ + body_seek = getattr(prepared_request.body, "seek", None) + if body_seek is not None and isinstance( + prepared_request._body_position, integer_types + ): + try: + body_seek(prepared_request._body_position) + except OSError: + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect." + ) + else: + raise UnrewindableBodyError("Unable to rewind request body for redirect.") diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py new file mode 100644 index 00000000..d92acc7b --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py @@ -0,0 +1,26 @@ +__all__ = [ + "__version__", + "AbstractProvider", + "AbstractResolver", + "BaseReporter", + "InconsistentCandidate", + "Resolver", + "RequirementsConflicted", + "ResolutionError", + "ResolutionImpossible", + "ResolutionTooDeep", +] + +__version__ = "1.0.1" + + +from .providers import AbstractProvider, AbstractResolver +from .reporters import BaseReporter +from .resolvers import ( + InconsistentCandidate, + RequirementsConflicted, + ResolutionError, + ResolutionImpossible, + ResolutionTooDeep, + Resolver, +) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py new file mode 100644 index 00000000..1becc509 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py @@ -0,0 +1,6 @@ +__all__ = ["Mapping", "Sequence"] + +try: + from collections.abc import Mapping, Sequence +except ImportError: + from collections import Mapping, Sequence diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py new file mode 100644 index 00000000..e99d87ee --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py @@ -0,0 +1,133 @@ +class AbstractProvider(object): + """Delegate class to provide the required interface for the resolver.""" + + def identify(self, requirement_or_candidate): + """Given a requirement, return an identifier for it. + + This is used to identify a requirement, e.g. whether two requirements + should have their specifier parts merged. + """ + raise NotImplementedError + + def get_preference( + self, + identifier, + resolutions, + candidates, + information, + backtrack_causes, + ): + """Produce a sort key for given requirement based on preference. + + The preference is defined as "I think this requirement should be + resolved first". The lower the return value is, the more preferred + this group of arguments is. + + :param identifier: An identifier as returned by ``identify()``. This + identifies the dependency matches which should be returned. + :param resolutions: Mapping of candidates currently pinned by the + resolver. Each key is an identifier, and the value is a candidate. + The candidate may conflict with requirements from ``information``. + :param candidates: Mapping of each dependency's possible candidates. + Each value is an iterator of candidates. + :param information: Mapping of requirement information of each package. + Each value is an iterator of *requirement information*. + :param backtrack_causes: Sequence of requirement information that were + the requirements that caused the resolver to most recently backtrack. + + A *requirement information* instance is a named tuple with two members: + + * ``requirement`` specifies a requirement contributing to the current + list of candidates. + * ``parent`` specifies the candidate that provides (depended on) the + requirement, or ``None`` to indicate a root requirement. + + The preference could depend on various issues, including (not + necessarily in this order): + + * Is this package pinned in the current resolution result? + * How relaxed is the requirement? Stricter ones should probably be + worked on first? (I don't know, actually.) + * How many possibilities are there to satisfy this requirement? Those + with few left should likely be worked on first, I guess? + * Are there any known conflicts for this requirement? We should + probably work on those with the most known conflicts. + + A sortable value should be returned (this will be used as the ``key`` + parameter of the built-in sorting function). The smaller the value is, + the more preferred this requirement is (i.e. the sorting function + is called with ``reverse=False``). + """ + raise NotImplementedError + + def find_matches(self, identifier, requirements, incompatibilities): + """Find all possible candidates that satisfy the given constraints. + + :param identifier: An identifier as returned by ``identify()``. This + identifies the dependency matches of which should be returned. + :param requirements: A mapping of requirements that all returned + candidates must satisfy. Each key is an identifier, and the value + an iterator of requirements for that dependency. + :param incompatibilities: A mapping of known incompatibilities of + each dependency. Each key is an identifier, and the value an + iterator of incompatibilities known to the resolver. All + incompatibilities *must* be excluded from the return value. + + This should try to get candidates based on the requirements' types. + For VCS, local, and archive requirements, the one-and-only match is + returned, and for a "named" requirement, the index(es) should be + consulted to find concrete candidates for this requirement. + + The return value should produce candidates ordered by preference; the + most preferred candidate should come first. The return type may be one + of the following: + + * A callable that returns an iterator that yields candidates. + * An collection of candidates. + * An iterable of candidates. This will be consumed immediately into a + list of candidates. + """ + raise NotImplementedError + + def is_satisfied_by(self, requirement, candidate): + """Whether the given requirement can be satisfied by a candidate. + + The candidate is guaranteed to have been generated from the + requirement. + + A boolean should be returned to indicate whether ``candidate`` is a + viable solution to the requirement. + """ + raise NotImplementedError + + def get_dependencies(self, candidate): + """Get dependencies of a candidate. + + This should return a collection of requirements that `candidate` + specifies as its dependencies. + """ + raise NotImplementedError + + +class AbstractResolver(object): + """The thing that performs the actual resolution work.""" + + base_exception = Exception + + def __init__(self, provider, reporter): + self.provider = provider + self.reporter = reporter + + def resolve(self, requirements, **kwargs): + """Take a collection of constraints, spit out the resolution result. + + This returns a representation of the final resolution state, with one + guarenteed attribute ``mapping`` that contains resolved candidates as + values. The keys are their respective identifiers. + + :param requirements: A collection of constraints. + :param kwargs: Additional keyword arguments that subclasses may accept. + + :raises: ``self.base_exception`` or its subclass. + """ + raise NotImplementedError diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py new file mode 100644 index 00000000..688b5e10 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py @@ -0,0 +1,43 @@ +class BaseReporter(object): + """Delegate class to provider progress reporting for the resolver.""" + + def starting(self): + """Called before the resolution actually starts.""" + + def starting_round(self, index): + """Called before each round of resolution starts. + + The index is zero-based. + """ + + def ending_round(self, index, state): + """Called before each round of resolution ends. + + This is NOT called if the resolution ends at this round. Use `ending` + if you want to report finalization. The index is zero-based. + """ + + def ending(self, state): + """Called before the resolution ends successfully.""" + + def adding_requirement(self, requirement, parent): + """Called when adding a new requirement into the resolve criteria. + + :param requirement: The additional requirement to be applied to filter + the available candidaites. + :param parent: The candidate that requires ``requirement`` as a + dependency, or None if ``requirement`` is one of the root + requirements passed in from ``Resolver.resolve()``. + """ + + def resolving_conflicts(self, causes): + """Called when starting to attempt requirement conflict resolution. + + :param causes: The information on the collision that caused the backtracking. + """ + + def rejecting_candidate(self, criterion, candidate): + """Called when rejecting a candidate during backtracking.""" + + def pinning(self, candidate): + """Called when adding a candidate to the potential solution.""" diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py new file mode 100644 index 00000000..2c3d0e30 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py @@ -0,0 +1,547 @@ +import collections +import itertools +import operator + +from .providers import AbstractResolver +from .structs import DirectedGraph, IteratorMapping, build_iter_view + +RequirementInformation = collections.namedtuple( + "RequirementInformation", ["requirement", "parent"] +) + + +class ResolverException(Exception): + """A base class for all exceptions raised by this module. + + Exceptions derived by this class should all be handled in this module. Any + bubbling pass the resolver should be treated as a bug. + """ + + +class RequirementsConflicted(ResolverException): + def __init__(self, criterion): + super(RequirementsConflicted, self).__init__(criterion) + self.criterion = criterion + + def __str__(self): + return "Requirements conflict: {}".format( + ", ".join(repr(r) for r in self.criterion.iter_requirement()), + ) + + +class InconsistentCandidate(ResolverException): + def __init__(self, candidate, criterion): + super(InconsistentCandidate, self).__init__(candidate, criterion) + self.candidate = candidate + self.criterion = criterion + + def __str__(self): + return "Provided candidate {!r} does not satisfy {}".format( + self.candidate, + ", ".join(repr(r) for r in self.criterion.iter_requirement()), + ) + + +class Criterion(object): + """Representation of possible resolution results of a package. + + This holds three attributes: + + * `information` is a collection of `RequirementInformation` pairs. + Each pair is a requirement contributing to this criterion, and the + candidate that provides the requirement. + * `incompatibilities` is a collection of all known not-to-work candidates + to exclude from consideration. + * `candidates` is a collection containing all possible candidates deducted + from the union of contributing requirements and known incompatibilities. + It should never be empty, except when the criterion is an attribute of a + raised `RequirementsConflicted` (in which case it is always empty). + + .. note:: + This class is intended to be externally immutable. **Do not** mutate + any of its attribute containers. + """ + + def __init__(self, candidates, information, incompatibilities): + self.candidates = candidates + self.information = information + self.incompatibilities = incompatibilities + + def __repr__(self): + requirements = ", ".join( + "({!r}, via={!r})".format(req, parent) + for req, parent in self.information + ) + return "Criterion({})".format(requirements) + + def iter_requirement(self): + return (i.requirement for i in self.information) + + def iter_parent(self): + return (i.parent for i in self.information) + + +class ResolutionError(ResolverException): + pass + + +class ResolutionImpossible(ResolutionError): + def __init__(self, causes): + super(ResolutionImpossible, self).__init__(causes) + # causes is a list of RequirementInformation objects + self.causes = causes + + +class ResolutionTooDeep(ResolutionError): + def __init__(self, round_count): + super(ResolutionTooDeep, self).__init__(round_count) + self.round_count = round_count + + +# Resolution state in a round. +State = collections.namedtuple("State", "mapping criteria backtrack_causes") + + +class Resolution(object): + """Stateful resolution object. + + This is designed as a one-off object that holds information to kick start + the resolution process, and holds the results afterwards. + """ + + def __init__(self, provider, reporter): + self._p = provider + self._r = reporter + self._states = [] + + @property + def state(self): + try: + return self._states[-1] + except IndexError: + raise AttributeError("state") + + def _push_new_state(self): + """Push a new state into history. + + This new state will be used to hold resolution results of the next + coming round. + """ + base = self._states[-1] + state = State( + mapping=base.mapping.copy(), + criteria=base.criteria.copy(), + backtrack_causes=base.backtrack_causes[:], + ) + self._states.append(state) + + def _add_to_criteria(self, criteria, requirement, parent): + self._r.adding_requirement(requirement=requirement, parent=parent) + + identifier = self._p.identify(requirement_or_candidate=requirement) + criterion = criteria.get(identifier) + if criterion: + incompatibilities = list(criterion.incompatibilities) + else: + incompatibilities = [] + + matches = self._p.find_matches( + identifier=identifier, + requirements=IteratorMapping( + criteria, + operator.methodcaller("iter_requirement"), + {identifier: [requirement]}, + ), + incompatibilities=IteratorMapping( + criteria, + operator.attrgetter("incompatibilities"), + {identifier: incompatibilities}, + ), + ) + + if criterion: + information = list(criterion.information) + information.append(RequirementInformation(requirement, parent)) + else: + information = [RequirementInformation(requirement, parent)] + + criterion = Criterion( + candidates=build_iter_view(matches), + information=information, + incompatibilities=incompatibilities, + ) + if not criterion.candidates: + raise RequirementsConflicted(criterion) + criteria[identifier] = criterion + + def _remove_information_from_criteria(self, criteria, parents): + """Remove information from parents of criteria. + + Concretely, removes all values from each criterion's ``information`` + field that have one of ``parents`` as provider of the requirement. + + :param criteria: The criteria to update. + :param parents: Identifiers for which to remove information from all criteria. + """ + if not parents: + return + for key, criterion in criteria.items(): + criteria[key] = Criterion( + criterion.candidates, + [ + information + for information in criterion.information + if ( + information.parent is None + or self._p.identify(information.parent) not in parents + ) + ], + criterion.incompatibilities, + ) + + def _get_preference(self, name): + return self._p.get_preference( + identifier=name, + resolutions=self.state.mapping, + candidates=IteratorMapping( + self.state.criteria, + operator.attrgetter("candidates"), + ), + information=IteratorMapping( + self.state.criteria, + operator.attrgetter("information"), + ), + backtrack_causes=self.state.backtrack_causes, + ) + + def _is_current_pin_satisfying(self, name, criterion): + try: + current_pin = self.state.mapping[name] + except KeyError: + return False + return all( + self._p.is_satisfied_by(requirement=r, candidate=current_pin) + for r in criterion.iter_requirement() + ) + + def _get_updated_criteria(self, candidate): + criteria = self.state.criteria.copy() + for requirement in self._p.get_dependencies(candidate=candidate): + self._add_to_criteria(criteria, requirement, parent=candidate) + return criteria + + def _attempt_to_pin_criterion(self, name): + criterion = self.state.criteria[name] + + causes = [] + for candidate in criterion.candidates: + try: + criteria = self._get_updated_criteria(candidate) + except RequirementsConflicted as e: + self._r.rejecting_candidate(e.criterion, candidate) + causes.append(e.criterion) + continue + + # Check the newly-pinned candidate actually works. This should + # always pass under normal circumstances, but in the case of a + # faulty provider, we will raise an error to notify the implementer + # to fix find_matches() and/or is_satisfied_by(). + satisfied = all( + self._p.is_satisfied_by(requirement=r, candidate=candidate) + for r in criterion.iter_requirement() + ) + if not satisfied: + raise InconsistentCandidate(candidate, criterion) + + self._r.pinning(candidate=candidate) + self.state.criteria.update(criteria) + + # Put newly-pinned candidate at the end. This is essential because + # backtracking looks at this mapping to get the last pin. + self.state.mapping.pop(name, None) + self.state.mapping[name] = candidate + + return [] + + # All candidates tried, nothing works. This criterion is a dead + # end, signal for backtracking. + return causes + + def _backjump(self, causes): + """Perform backjumping. + + When we enter here, the stack is like this:: + + [ state Z ] + [ state Y ] + [ state X ] + .... earlier states are irrelevant. + + 1. No pins worked for Z, so it does not have a pin. + 2. We want to reset state Y to unpinned, and pin another candidate. + 3. State X holds what state Y was before the pin, but does not + have the incompatibility information gathered in state Y. + + Each iteration of the loop will: + + 1. Identify Z. The incompatibility is not always caused by the latest + state. For example, given three requirements A, B and C, with + dependencies A1, B1 and C1, where A1 and B1 are incompatible: the + last state might be related to C, so we want to discard the + previous state. + 2. Discard Z. + 3. Discard Y but remember its incompatibility information gathered + previously, and the failure we're dealing with right now. + 4. Push a new state Y' based on X, and apply the incompatibility + information from Y to Y'. + 5a. If this causes Y' to conflict, we need to backtrack again. Make Y' + the new Z and go back to step 2. + 5b. If the incompatibilities apply cleanly, end backtracking. + """ + incompatible_reqs = itertools.chain( + (c.parent for c in causes if c.parent is not None), + (c.requirement for c in causes), + ) + incompatible_deps = {self._p.identify(r) for r in incompatible_reqs} + while len(self._states) >= 3: + # Remove the state that triggered backtracking. + del self._states[-1] + + # Ensure to backtrack to a state that caused the incompatibility + incompatible_state = False + while not incompatible_state: + # Retrieve the last candidate pin and known incompatibilities. + try: + broken_state = self._states.pop() + name, candidate = broken_state.mapping.popitem() + except (IndexError, KeyError): + raise ResolutionImpossible(causes) + current_dependencies = { + self._p.identify(d) + for d in self._p.get_dependencies(candidate) + } + incompatible_state = not current_dependencies.isdisjoint( + incompatible_deps + ) + + incompatibilities_from_broken = [ + (k, list(v.incompatibilities)) + for k, v in broken_state.criteria.items() + ] + + # Also mark the newly known incompatibility. + incompatibilities_from_broken.append((name, [candidate])) + + # Create a new state from the last known-to-work one, and apply + # the previously gathered incompatibility information. + def _patch_criteria(): + for k, incompatibilities in incompatibilities_from_broken: + if not incompatibilities: + continue + try: + criterion = self.state.criteria[k] + except KeyError: + continue + matches = self._p.find_matches( + identifier=k, + requirements=IteratorMapping( + self.state.criteria, + operator.methodcaller("iter_requirement"), + ), + incompatibilities=IteratorMapping( + self.state.criteria, + operator.attrgetter("incompatibilities"), + {k: incompatibilities}, + ), + ) + candidates = build_iter_view(matches) + if not candidates: + return False + incompatibilities.extend(criterion.incompatibilities) + self.state.criteria[k] = Criterion( + candidates=candidates, + information=list(criterion.information), + incompatibilities=incompatibilities, + ) + return True + + self._push_new_state() + success = _patch_criteria() + + # It works! Let's work on this new state. + if success: + return True + + # State does not work after applying known incompatibilities. + # Try the still previous state. + + # No way to backtrack anymore. + return False + + def resolve(self, requirements, max_rounds): + if self._states: + raise RuntimeError("already resolved") + + self._r.starting() + + # Initialize the root state. + self._states = [ + State( + mapping=collections.OrderedDict(), + criteria={}, + backtrack_causes=[], + ) + ] + for r in requirements: + try: + self._add_to_criteria(self.state.criteria, r, parent=None) + except RequirementsConflicted as e: + raise ResolutionImpossible(e.criterion.information) + + # The root state is saved as a sentinel so the first ever pin can have + # something to backtrack to if it fails. The root state is basically + # pinning the virtual "root" package in the graph. + self._push_new_state() + + for round_index in range(max_rounds): + self._r.starting_round(index=round_index) + + unsatisfied_names = [ + key + for key, criterion in self.state.criteria.items() + if not self._is_current_pin_satisfying(key, criterion) + ] + + # All criteria are accounted for. Nothing more to pin, we are done! + if not unsatisfied_names: + self._r.ending(state=self.state) + return self.state + + # keep track of satisfied names to calculate diff after pinning + satisfied_names = set(self.state.criteria.keys()) - set( + unsatisfied_names + ) + + # Choose the most preferred unpinned criterion to try. + name = min(unsatisfied_names, key=self._get_preference) + failure_causes = self._attempt_to_pin_criterion(name) + + if failure_causes: + causes = [i for c in failure_causes for i in c.information] + # Backjump if pinning fails. The backjump process puts us in + # an unpinned state, so we can work on it in the next round. + self._r.resolving_conflicts(causes=causes) + success = self._backjump(causes) + self.state.backtrack_causes[:] = causes + + # Dead ends everywhere. Give up. + if not success: + raise ResolutionImpossible(self.state.backtrack_causes) + else: + # discard as information sources any invalidated names + # (unsatisfied names that were previously satisfied) + newly_unsatisfied_names = { + key + for key, criterion in self.state.criteria.items() + if key in satisfied_names + and not self._is_current_pin_satisfying(key, criterion) + } + self._remove_information_from_criteria( + self.state.criteria, newly_unsatisfied_names + ) + # Pinning was successful. Push a new state to do another pin. + self._push_new_state() + + self._r.ending_round(index=round_index, state=self.state) + + raise ResolutionTooDeep(max_rounds) + + +def _has_route_to_root(criteria, key, all_keys, connected): + if key in connected: + return True + if key not in criteria: + return False + for p in criteria[key].iter_parent(): + try: + pkey = all_keys[id(p)] + except KeyError: + continue + if pkey in connected: + connected.add(key) + return True + if _has_route_to_root(criteria, pkey, all_keys, connected): + connected.add(key) + return True + return False + + +Result = collections.namedtuple("Result", "mapping graph criteria") + + +def _build_result(state): + mapping = state.mapping + all_keys = {id(v): k for k, v in mapping.items()} + all_keys[id(None)] = None + + graph = DirectedGraph() + graph.add(None) # Sentinel as root dependencies' parent. + + connected = {None} + for key, criterion in state.criteria.items(): + if not _has_route_to_root(state.criteria, key, all_keys, connected): + continue + if key not in graph: + graph.add(key) + for p in criterion.iter_parent(): + try: + pkey = all_keys[id(p)] + except KeyError: + continue + if pkey not in graph: + graph.add(pkey) + graph.connect(pkey, key) + + return Result( + mapping={k: v for k, v in mapping.items() if k in connected}, + graph=graph, + criteria=state.criteria, + ) + + +class Resolver(AbstractResolver): + """The thing that performs the actual resolution work.""" + + base_exception = ResolverException + + def resolve(self, requirements, max_rounds=100): + """Take a collection of constraints, spit out the resolution result. + + The return value is a representation to the final resolution result. It + is a tuple subclass with three public members: + + * `mapping`: A dict of resolved candidates. Each key is an identifier + of a requirement (as returned by the provider's `identify` method), + and the value is the resolved candidate. + * `graph`: A `DirectedGraph` instance representing the dependency tree. + The vertices are keys of `mapping`, and each edge represents *why* + a particular package is included. A special vertex `None` is + included to represent parents of user-supplied requirements. + * `criteria`: A dict of "criteria" that hold detailed information on + how edges in the graph are derived. Each key is an identifier of a + requirement, and the value is a `Criterion` instance. + + The following exceptions may be raised if a resolution cannot be found: + + * `ResolutionImpossible`: A resolution cannot be found for the given + combination of requirements. The `causes` attribute of the + exception is a list of (requirement, parent), giving the + requirements that could not be satisfied. + * `ResolutionTooDeep`: The dependency tree is too deeply nested and + the resolver gave up. This is usually caused by a circular + dependency, but you can try to resolve this by increasing the + `max_rounds` argument. + """ + resolution = Resolution(self.provider, self.reporter) + state = resolution.resolve(requirements, max_rounds=max_rounds) + return _build_result(state) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py new file mode 100644 index 00000000..359a34f6 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py @@ -0,0 +1,170 @@ +import itertools + +from .compat import collections_abc + + +class DirectedGraph(object): + """A graph structure with directed edges.""" + + def __init__(self): + self._vertices = set() + self._forwards = {} # -> Set[] + self._backwards = {} # -> Set[] + + def __iter__(self): + return iter(self._vertices) + + def __len__(self): + return len(self._vertices) + + def __contains__(self, key): + return key in self._vertices + + def copy(self): + """Return a shallow copy of this graph.""" + other = DirectedGraph() + other._vertices = set(self._vertices) + other._forwards = {k: set(v) for k, v in self._forwards.items()} + other._backwards = {k: set(v) for k, v in self._backwards.items()} + return other + + def add(self, key): + """Add a new vertex to the graph.""" + if key in self._vertices: + raise ValueError("vertex exists") + self._vertices.add(key) + self._forwards[key] = set() + self._backwards[key] = set() + + def remove(self, key): + """Remove a vertex from the graph, disconnecting all edges from/to it.""" + self._vertices.remove(key) + for f in self._forwards.pop(key): + self._backwards[f].remove(key) + for t in self._backwards.pop(key): + self._forwards[t].remove(key) + + def connected(self, f, t): + return f in self._backwards[t] and t in self._forwards[f] + + def connect(self, f, t): + """Connect two existing vertices. + + Nothing happens if the vertices are already connected. + """ + if t not in self._vertices: + raise KeyError(t) + self._forwards[f].add(t) + self._backwards[t].add(f) + + def iter_edges(self): + for f, children in self._forwards.items(): + for t in children: + yield f, t + + def iter_children(self, key): + return iter(self._forwards[key]) + + def iter_parents(self, key): + return iter(self._backwards[key]) + + +class IteratorMapping(collections_abc.Mapping): + def __init__(self, mapping, accessor, appends=None): + self._mapping = mapping + self._accessor = accessor + self._appends = appends or {} + + def __repr__(self): + return "IteratorMapping({!r}, {!r}, {!r})".format( + self._mapping, + self._accessor, + self._appends, + ) + + def __bool__(self): + return bool(self._mapping or self._appends) + + __nonzero__ = __bool__ # XXX: Python 2. + + def __contains__(self, key): + return key in self._mapping or key in self._appends + + def __getitem__(self, k): + try: + v = self._mapping[k] + except KeyError: + return iter(self._appends[k]) + return itertools.chain(self._accessor(v), self._appends.get(k, ())) + + def __iter__(self): + more = (k for k in self._appends if k not in self._mapping) + return itertools.chain(self._mapping, more) + + def __len__(self): + more = sum(1 for k in self._appends if k not in self._mapping) + return len(self._mapping) + more + + +class _FactoryIterableView(object): + """Wrap an iterator factory returned by `find_matches()`. + + Calling `iter()` on this class would invoke the underlying iterator + factory, making it a "collection with ordering" that can be iterated + through multiple times, but lacks random access methods presented in + built-in Python sequence types. + """ + + def __init__(self, factory): + self._factory = factory + self._iterable = None + + def __repr__(self): + return "{}({})".format(type(self).__name__, list(self)) + + def __bool__(self): + try: + next(iter(self)) + except StopIteration: + return False + return True + + __nonzero__ = __bool__ # XXX: Python 2. + + def __iter__(self): + iterable = ( + self._factory() if self._iterable is None else self._iterable + ) + self._iterable, current = itertools.tee(iterable) + return current + + +class _SequenceIterableView(object): + """Wrap an iterable returned by find_matches(). + + This is essentially just a proxy to the underlying sequence that provides + the same interface as `_FactoryIterableView`. + """ + + def __init__(self, sequence): + self._sequence = sequence + + def __repr__(self): + return "{}({})".format(type(self).__name__, self._sequence) + + def __bool__(self): + return bool(self._sequence) + + __nonzero__ = __bool__ # XXX: Python 2. + + def __iter__(self): + return iter(self._sequence) + + +def build_iter_view(matches): + """Build an iterable view from the value returned by `find_matches()`.""" + if callable(matches): + return _FactoryIterableView(matches) + if not isinstance(matches, collections_abc.Sequence): + matches = list(matches) + return _SequenceIterableView(matches) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/__init__.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/__init__.py new file mode 100644 index 00000000..73f58d77 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/__init__.py @@ -0,0 +1,177 @@ +"""Rich text and beautiful formatting in the terminal.""" + +import os +from typing import IO, TYPE_CHECKING, Any, Callable, Optional, Union + +from ._extension import load_ipython_extension # noqa: F401 + +__all__ = ["get_console", "reconfigure", "print", "inspect", "print_json"] + +if TYPE_CHECKING: + from .console import Console + +# Global console used by alternative print +_console: Optional["Console"] = None + +try: + _IMPORT_CWD = os.path.abspath(os.getcwd()) +except FileNotFoundError: + # Can happen if the cwd has been deleted + _IMPORT_CWD = "" + + +def get_console() -> "Console": + """Get a global :class:`~rich.console.Console` instance. This function is used when Rich requires a Console, + and hasn't been explicitly given one. + + Returns: + Console: A console instance. + """ + global _console + if _console is None: + from .console import Console + + _console = Console() + + return _console + + +def reconfigure(*args: Any, **kwargs: Any) -> None: + """Reconfigures the global console by replacing it with another. + + Args: + *args (Any): Positional arguments for the replacement :class:`~rich.console.Console`. + **kwargs (Any): Keyword arguments for the replacement :class:`~rich.console.Console`. + """ + from pip._vendor.rich.console import Console + + new_console = Console(*args, **kwargs) + _console = get_console() + _console.__dict__ = new_console.__dict__ + + +def print( + *objects: Any, + sep: str = " ", + end: str = "\n", + file: Optional[IO[str]] = None, + flush: bool = False, +) -> None: + r"""Print object(s) supplied via positional arguments. + This function has an identical signature to the built-in print. + For more advanced features, see the :class:`~rich.console.Console` class. + + Args: + sep (str, optional): Separator between printed objects. Defaults to " ". + end (str, optional): Character to write at end of output. Defaults to "\\n". + file (IO[str], optional): File to write to, or None for stdout. Defaults to None. + flush (bool, optional): Has no effect as Rich always flushes output. Defaults to False. + + """ + from .console import Console + + write_console = get_console() if file is None else Console(file=file) + return write_console.print(*objects, sep=sep, end=end) + + +def print_json( + json: Optional[str] = None, + *, + data: Any = None, + indent: Union[None, int, str] = 2, + highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = False, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, +) -> None: + """Pretty prints JSON. Output will be valid JSON. + + Args: + json (str): A string containing JSON. + data (Any): If json is not supplied, then encode this data. + indent (int, optional): Number of spaces to indent. Defaults to 2. + highlight (bool, optional): Enable highlighting of output: Defaults to True. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. + """ + + get_console().print_json( + json, + data=data, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + + +def inspect( + obj: Any, + *, + console: Optional["Console"] = None, + title: Optional[str] = None, + help: bool = False, + methods: bool = False, + docs: bool = True, + private: bool = False, + dunder: bool = False, + sort: bool = True, + all: bool = False, + value: bool = True, +) -> None: + """Inspect any Python object. + + * inspect() to see summarized info. + * inspect(, methods=True) to see methods. + * inspect(, help=True) to see full (non-abbreviated) help. + * inspect(, private=True) to see private attributes (single underscore). + * inspect(, dunder=True) to see attributes beginning with double underscore. + * inspect(, all=True) to see all attributes. + + Args: + obj (Any): An object to inspect. + title (str, optional): Title to display over inspect result, or None use type. Defaults to None. + help (bool, optional): Show full help text rather than just first paragraph. Defaults to False. + methods (bool, optional): Enable inspection of callables. Defaults to False. + docs (bool, optional): Also render doc strings. Defaults to True. + private (bool, optional): Show private attributes (beginning with underscore). Defaults to False. + dunder (bool, optional): Show attributes starting with double underscore. Defaults to False. + sort (bool, optional): Sort attributes alphabetically. Defaults to True. + all (bool, optional): Show all attributes. Defaults to False. + value (bool, optional): Pretty print value. Defaults to True. + """ + _console = console or get_console() + from pip._vendor.rich._inspect import Inspect + + # Special case for inspect(inspect) + is_inspect = obj is inspect + + _inspect = Inspect( + obj, + title=title, + help=is_inspect or help, + methods=is_inspect or methods, + docs=is_inspect or docs, + private=private, + dunder=dunder, + sort=sort, + all=all, + value=value, + ) + _console.print(_inspect) + + +if __name__ == "__main__": # pragma: no cover + print("Hello, **World**") diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/__main__.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/__main__.py new file mode 100644 index 00000000..270629fd --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/__main__.py @@ -0,0 +1,274 @@ +import colorsys +import io +from time import process_time + +from pip._vendor.rich import box +from pip._vendor.rich.color import Color +from pip._vendor.rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult +from pip._vendor.rich.markdown import Markdown +from pip._vendor.rich.measure import Measurement +from pip._vendor.rich.pretty import Pretty +from pip._vendor.rich.segment import Segment +from pip._vendor.rich.style import Style +from pip._vendor.rich.syntax import Syntax +from pip._vendor.rich.table import Table +from pip._vendor.rich.text import Text + + +class ColorBox: + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + for y in range(0, 5): + for x in range(options.max_width): + h = x / options.max_width + l = 0.1 + ((y / 5) * 0.7) + r1, g1, b1 = colorsys.hls_to_rgb(h, l, 1.0) + r2, g2, b2 = colorsys.hls_to_rgb(h, l + 0.7 / 10, 1.0) + bgcolor = Color.from_rgb(r1 * 255, g1 * 255, b1 * 255) + color = Color.from_rgb(r2 * 255, g2 * 255, b2 * 255) + yield Segment("▄", Style(color=color, bgcolor=bgcolor)) + yield Segment.line() + + def __rich_measure__( + self, console: "Console", options: ConsoleOptions + ) -> Measurement: + return Measurement(1, options.max_width) + + +def make_test_card() -> Table: + """Get a renderable that demonstrates a number of features.""" + table = Table.grid(padding=1, pad_edge=True) + table.title = "Rich features" + table.add_column("Feature", no_wrap=True, justify="center", style="bold red") + table.add_column("Demonstration") + + color_table = Table( + box=None, + expand=False, + show_header=False, + show_edge=False, + pad_edge=False, + ) + color_table.add_row( + ( + "✓ [bold green]4-bit color[/]\n" + "✓ [bold blue]8-bit color[/]\n" + "✓ [bold magenta]Truecolor (16.7 million)[/]\n" + "✓ [bold yellow]Dumb terminals[/]\n" + "✓ [bold cyan]Automatic color conversion" + ), + ColorBox(), + ) + + table.add_row("Colors", color_table) + + table.add_row( + "Styles", + "All ansi styles: [bold]bold[/], [dim]dim[/], [italic]italic[/italic], [underline]underline[/], [strike]strikethrough[/], [reverse]reverse[/], and even [blink]blink[/].", + ) + + lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque in metus sed sapien ultricies pretium a at justo. Maecenas luctus velit et auctor maximus." + lorem_table = Table.grid(padding=1, collapse_padding=True) + lorem_table.pad_edge = False + lorem_table.add_row( + Text(lorem, justify="left", style="green"), + Text(lorem, justify="center", style="yellow"), + Text(lorem, justify="right", style="blue"), + Text(lorem, justify="full", style="red"), + ) + table.add_row( + "Text", + Group( + Text.from_markup( + """Word wrap text. Justify [green]left[/], [yellow]center[/], [blue]right[/] or [red]full[/].\n""" + ), + lorem_table, + ), + ) + + def comparison(renderable1: RenderableType, renderable2: RenderableType) -> Table: + table = Table(show_header=False, pad_edge=False, box=None, expand=True) + table.add_column("1", ratio=1) + table.add_column("2", ratio=1) + table.add_row(renderable1, renderable2) + return table + + table.add_row( + "Asian\nlanguage\nsupport", + ":flag_for_china: 该库支持中文,日文和韩文文本!\n:flag_for_japan: ライブラリは中国語、日本語、韓国語のテキストをサポートしています\n:flag_for_south_korea: 이 라이브러리는 중국어, 일본어 및 한국어 텍스트를 지원합니다", + ) + + markup_example = ( + "[bold magenta]Rich[/] supports a simple [i]bbcode[/i]-like [b]markup[/b] for [yellow]color[/], [underline]style[/], and emoji! " + ":+1: :apple: :ant: :bear: :baguette_bread: :bus: " + ) + table.add_row("Markup", markup_example) + + example_table = Table( + show_edge=False, + show_header=True, + expand=False, + row_styles=["none", "dim"], + box=box.SIMPLE, + ) + example_table.add_column("[green]Date", style="green", no_wrap=True) + example_table.add_column("[blue]Title", style="blue") + example_table.add_column( + "[cyan]Production Budget", + style="cyan", + justify="right", + no_wrap=True, + ) + example_table.add_column( + "[magenta]Box Office", + style="magenta", + justify="right", + no_wrap=True, + ) + example_table.add_row( + "Dec 20, 2019", + "Star Wars: The Rise of Skywalker", + "$275,000,000", + "$375,126,118", + ) + example_table.add_row( + "May 25, 2018", + "[b]Solo[/]: A Star Wars Story", + "$275,000,000", + "$393,151,347", + ) + example_table.add_row( + "Dec 15, 2017", + "Star Wars Ep. VIII: The Last Jedi", + "$262,000,000", + "[bold]$1,332,539,889[/bold]", + ) + example_table.add_row( + "May 19, 1999", + "Star Wars Ep. [b]I[/b]: [i]The phantom Menace", + "$115,000,000", + "$1,027,044,677", + ) + + table.add_row("Tables", example_table) + + code = '''\ +def iter_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value''' + + pretty_data = { + "foo": [ + 3.1427, + ( + "Paul Atreides", + "Vladimir Harkonnen", + "Thufir Hawat", + ), + ], + "atomic": (False, True, None), + } + table.add_row( + "Syntax\nhighlighting\n&\npretty\nprinting", + comparison( + Syntax(code, "python3", line_numbers=True, indent_guides=True), + Pretty(pretty_data, indent_guides=True), + ), + ) + + markdown_example = """\ +# Markdown + +Supports much of the *markdown* __syntax__! + +- Headers +- Basic formatting: **bold**, *italic*, `code` +- Block quotes +- Lists, and more... + """ + table.add_row( + "Markdown", comparison("[cyan]" + markdown_example, Markdown(markdown_example)) + ) + + table.add_row( + "+more!", + """Progress bars, columns, styled logging handler, tracebacks, etc...""", + ) + return table + + +if __name__ == "__main__": # pragma: no cover + + console = Console( + file=io.StringIO(), + force_terminal=True, + ) + test_card = make_test_card() + + # Print once to warm cache + start = process_time() + console.print(test_card) + pre_cache_taken = round((process_time() - start) * 1000.0, 1) + + console.file = io.StringIO() + + start = process_time() + console.print(test_card) + taken = round((process_time() - start) * 1000.0, 1) + + c = Console(record=True) + c.print(test_card) + + print(f"rendered in {pre_cache_taken}ms (cold cache)") + print(f"rendered in {taken}ms (warm cache)") + + from pip._vendor.rich.panel import Panel + + console = Console() + + sponsor_message = Table.grid(padding=1) + sponsor_message.add_column(style="green", justify="right") + sponsor_message.add_column(no_wrap=True) + + sponsor_message.add_row( + "Textualize", + "[u blue link=https://github.com/textualize]https://github.com/textualize", + ) + sponsor_message.add_row( + "Twitter", + "[u blue link=https://twitter.com/willmcgugan]https://twitter.com/willmcgugan", + ) + + intro_message = Text.from_markup( + """\ +We hope you enjoy using Rich! + +Rich is maintained with [red]:heart:[/] by [link=https://www.textualize.io]Textualize.io[/] + +- Will McGugan""" + ) + + message = Table.grid(padding=2) + message.add_column() + message.add_column(no_wrap=True) + message.add_row(intro_message, sponsor_message) + + console.print( + Panel.fit( + message, + box=box.ROUNDED, + padding=(1, 2), + title="[b red]Thanks for trying out Rich!", + border_style="bright_blue", + ), + justify="center", + ) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_cell_widths.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_cell_widths.py new file mode 100644 index 00000000..36286df3 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_cell_widths.py @@ -0,0 +1,451 @@ +# Auto generated by make_terminal_widths.py + +CELL_WIDTHS = [ + (0, 0, 0), + (1, 31, -1), + (127, 159, -1), + (768, 879, 0), + (1155, 1161, 0), + (1425, 1469, 0), + (1471, 1471, 0), + (1473, 1474, 0), + (1476, 1477, 0), + (1479, 1479, 0), + (1552, 1562, 0), + (1611, 1631, 0), + (1648, 1648, 0), + (1750, 1756, 0), + (1759, 1764, 0), + (1767, 1768, 0), + (1770, 1773, 0), + (1809, 1809, 0), + (1840, 1866, 0), + (1958, 1968, 0), + (2027, 2035, 0), + (2045, 2045, 0), + (2070, 2073, 0), + (2075, 2083, 0), + (2085, 2087, 0), + (2089, 2093, 0), + (2137, 2139, 0), + (2259, 2273, 0), + (2275, 2306, 0), + (2362, 2362, 0), + (2364, 2364, 0), + (2369, 2376, 0), + (2381, 2381, 0), + (2385, 2391, 0), + (2402, 2403, 0), + (2433, 2433, 0), + (2492, 2492, 0), + (2497, 2500, 0), + (2509, 2509, 0), + (2530, 2531, 0), + (2558, 2558, 0), + (2561, 2562, 0), + (2620, 2620, 0), + (2625, 2626, 0), + (2631, 2632, 0), + (2635, 2637, 0), + (2641, 2641, 0), + (2672, 2673, 0), + (2677, 2677, 0), + (2689, 2690, 0), + (2748, 2748, 0), + (2753, 2757, 0), + (2759, 2760, 0), + (2765, 2765, 0), + (2786, 2787, 0), + (2810, 2815, 0), + (2817, 2817, 0), + (2876, 2876, 0), + (2879, 2879, 0), + (2881, 2884, 0), + (2893, 2893, 0), + (2901, 2902, 0), + (2914, 2915, 0), + (2946, 2946, 0), + (3008, 3008, 0), + (3021, 3021, 0), + (3072, 3072, 0), + (3076, 3076, 0), + (3134, 3136, 0), + (3142, 3144, 0), + (3146, 3149, 0), + (3157, 3158, 0), + (3170, 3171, 0), + (3201, 3201, 0), + (3260, 3260, 0), + (3263, 3263, 0), + (3270, 3270, 0), + (3276, 3277, 0), + (3298, 3299, 0), + (3328, 3329, 0), + (3387, 3388, 0), + (3393, 3396, 0), + (3405, 3405, 0), + (3426, 3427, 0), + (3457, 3457, 0), + (3530, 3530, 0), + (3538, 3540, 0), + (3542, 3542, 0), + (3633, 3633, 0), + (3636, 3642, 0), + (3655, 3662, 0), + (3761, 3761, 0), + (3764, 3772, 0), + (3784, 3789, 0), + (3864, 3865, 0), + (3893, 3893, 0), + (3895, 3895, 0), + (3897, 3897, 0), + (3953, 3966, 0), + (3968, 3972, 0), + (3974, 3975, 0), + (3981, 3991, 0), + (3993, 4028, 0), + (4038, 4038, 0), + (4141, 4144, 0), + (4146, 4151, 0), + (4153, 4154, 0), + (4157, 4158, 0), + (4184, 4185, 0), + (4190, 4192, 0), + (4209, 4212, 0), + (4226, 4226, 0), + (4229, 4230, 0), + (4237, 4237, 0), + (4253, 4253, 0), + (4352, 4447, 2), + (4957, 4959, 0), + (5906, 5908, 0), + (5938, 5940, 0), + (5970, 5971, 0), + (6002, 6003, 0), + (6068, 6069, 0), + (6071, 6077, 0), + (6086, 6086, 0), + (6089, 6099, 0), + (6109, 6109, 0), + (6155, 6157, 0), + (6277, 6278, 0), + (6313, 6313, 0), + (6432, 6434, 0), + (6439, 6440, 0), + (6450, 6450, 0), + (6457, 6459, 0), + (6679, 6680, 0), + (6683, 6683, 0), + (6742, 6742, 0), + (6744, 6750, 0), + (6752, 6752, 0), + (6754, 6754, 0), + (6757, 6764, 0), + (6771, 6780, 0), + (6783, 6783, 0), + (6832, 6848, 0), + (6912, 6915, 0), + (6964, 6964, 0), + (6966, 6970, 0), + (6972, 6972, 0), + (6978, 6978, 0), + (7019, 7027, 0), + (7040, 7041, 0), + (7074, 7077, 0), + (7080, 7081, 0), + (7083, 7085, 0), + (7142, 7142, 0), + (7144, 7145, 0), + (7149, 7149, 0), + (7151, 7153, 0), + (7212, 7219, 0), + (7222, 7223, 0), + (7376, 7378, 0), + (7380, 7392, 0), + (7394, 7400, 0), + (7405, 7405, 0), + (7412, 7412, 0), + (7416, 7417, 0), + (7616, 7673, 0), + (7675, 7679, 0), + (8203, 8207, 0), + (8232, 8238, 0), + (8288, 8291, 0), + (8400, 8432, 0), + (8986, 8987, 2), + (9001, 9002, 2), + (9193, 9196, 2), + (9200, 9200, 2), + (9203, 9203, 2), + (9725, 9726, 2), + (9748, 9749, 2), + (9800, 9811, 2), + (9855, 9855, 2), + (9875, 9875, 2), + (9889, 9889, 2), + (9898, 9899, 2), + (9917, 9918, 2), + (9924, 9925, 2), + (9934, 9934, 2), + (9940, 9940, 2), + (9962, 9962, 2), + (9970, 9971, 2), + (9973, 9973, 2), + (9978, 9978, 2), + (9981, 9981, 2), + (9989, 9989, 2), + (9994, 9995, 2), + (10024, 10024, 2), + (10060, 10060, 2), + (10062, 10062, 2), + (10067, 10069, 2), + (10071, 10071, 2), + (10133, 10135, 2), + (10160, 10160, 2), + (10175, 10175, 2), + (11035, 11036, 2), + (11088, 11088, 2), + (11093, 11093, 2), + (11503, 11505, 0), + (11647, 11647, 0), + (11744, 11775, 0), + (11904, 11929, 2), + (11931, 12019, 2), + (12032, 12245, 2), + (12272, 12283, 2), + (12288, 12329, 2), + (12330, 12333, 0), + (12334, 12350, 2), + (12353, 12438, 2), + (12441, 12442, 0), + (12443, 12543, 2), + (12549, 12591, 2), + (12593, 12686, 2), + (12688, 12771, 2), + (12784, 12830, 2), + (12832, 12871, 2), + (12880, 19903, 2), + (19968, 42124, 2), + (42128, 42182, 2), + (42607, 42610, 0), + (42612, 42621, 0), + (42654, 42655, 0), + (42736, 42737, 0), + (43010, 43010, 0), + (43014, 43014, 0), + (43019, 43019, 0), + (43045, 43046, 0), + (43052, 43052, 0), + (43204, 43205, 0), + (43232, 43249, 0), + (43263, 43263, 0), + (43302, 43309, 0), + (43335, 43345, 0), + (43360, 43388, 2), + (43392, 43394, 0), + (43443, 43443, 0), + (43446, 43449, 0), + (43452, 43453, 0), + (43493, 43493, 0), + (43561, 43566, 0), + (43569, 43570, 0), + (43573, 43574, 0), + (43587, 43587, 0), + (43596, 43596, 0), + (43644, 43644, 0), + (43696, 43696, 0), + (43698, 43700, 0), + (43703, 43704, 0), + (43710, 43711, 0), + (43713, 43713, 0), + (43756, 43757, 0), + (43766, 43766, 0), + (44005, 44005, 0), + (44008, 44008, 0), + (44013, 44013, 0), + (44032, 55203, 2), + (63744, 64255, 2), + (64286, 64286, 0), + (65024, 65039, 0), + (65040, 65049, 2), + (65056, 65071, 0), + (65072, 65106, 2), + (65108, 65126, 2), + (65128, 65131, 2), + (65281, 65376, 2), + (65504, 65510, 2), + (66045, 66045, 0), + (66272, 66272, 0), + (66422, 66426, 0), + (68097, 68099, 0), + (68101, 68102, 0), + (68108, 68111, 0), + (68152, 68154, 0), + (68159, 68159, 0), + (68325, 68326, 0), + (68900, 68903, 0), + (69291, 69292, 0), + (69446, 69456, 0), + (69633, 69633, 0), + (69688, 69702, 0), + (69759, 69761, 0), + (69811, 69814, 0), + (69817, 69818, 0), + (69888, 69890, 0), + (69927, 69931, 0), + (69933, 69940, 0), + (70003, 70003, 0), + (70016, 70017, 0), + (70070, 70078, 0), + (70089, 70092, 0), + (70095, 70095, 0), + (70191, 70193, 0), + (70196, 70196, 0), + (70198, 70199, 0), + (70206, 70206, 0), + (70367, 70367, 0), + (70371, 70378, 0), + (70400, 70401, 0), + (70459, 70460, 0), + (70464, 70464, 0), + (70502, 70508, 0), + (70512, 70516, 0), + (70712, 70719, 0), + (70722, 70724, 0), + (70726, 70726, 0), + (70750, 70750, 0), + (70835, 70840, 0), + (70842, 70842, 0), + (70847, 70848, 0), + (70850, 70851, 0), + (71090, 71093, 0), + (71100, 71101, 0), + (71103, 71104, 0), + (71132, 71133, 0), + (71219, 71226, 0), + (71229, 71229, 0), + (71231, 71232, 0), + (71339, 71339, 0), + (71341, 71341, 0), + (71344, 71349, 0), + (71351, 71351, 0), + (71453, 71455, 0), + (71458, 71461, 0), + (71463, 71467, 0), + (71727, 71735, 0), + (71737, 71738, 0), + (71995, 71996, 0), + (71998, 71998, 0), + (72003, 72003, 0), + (72148, 72151, 0), + (72154, 72155, 0), + (72160, 72160, 0), + (72193, 72202, 0), + (72243, 72248, 0), + (72251, 72254, 0), + (72263, 72263, 0), + (72273, 72278, 0), + (72281, 72283, 0), + (72330, 72342, 0), + (72344, 72345, 0), + (72752, 72758, 0), + (72760, 72765, 0), + (72767, 72767, 0), + (72850, 72871, 0), + (72874, 72880, 0), + (72882, 72883, 0), + (72885, 72886, 0), + (73009, 73014, 0), + (73018, 73018, 0), + (73020, 73021, 0), + (73023, 73029, 0), + (73031, 73031, 0), + (73104, 73105, 0), + (73109, 73109, 0), + (73111, 73111, 0), + (73459, 73460, 0), + (92912, 92916, 0), + (92976, 92982, 0), + (94031, 94031, 0), + (94095, 94098, 0), + (94176, 94179, 2), + (94180, 94180, 0), + (94192, 94193, 2), + (94208, 100343, 2), + (100352, 101589, 2), + (101632, 101640, 2), + (110592, 110878, 2), + (110928, 110930, 2), + (110948, 110951, 2), + (110960, 111355, 2), + (113821, 113822, 0), + (119143, 119145, 0), + (119163, 119170, 0), + (119173, 119179, 0), + (119210, 119213, 0), + (119362, 119364, 0), + (121344, 121398, 0), + (121403, 121452, 0), + (121461, 121461, 0), + (121476, 121476, 0), + (121499, 121503, 0), + (121505, 121519, 0), + (122880, 122886, 0), + (122888, 122904, 0), + (122907, 122913, 0), + (122915, 122916, 0), + (122918, 122922, 0), + (123184, 123190, 0), + (123628, 123631, 0), + (125136, 125142, 0), + (125252, 125258, 0), + (126980, 126980, 2), + (127183, 127183, 2), + (127374, 127374, 2), + (127377, 127386, 2), + (127488, 127490, 2), + (127504, 127547, 2), + (127552, 127560, 2), + (127568, 127569, 2), + (127584, 127589, 2), + (127744, 127776, 2), + (127789, 127797, 2), + (127799, 127868, 2), + (127870, 127891, 2), + (127904, 127946, 2), + (127951, 127955, 2), + (127968, 127984, 2), + (127988, 127988, 2), + (127992, 128062, 2), + (128064, 128064, 2), + (128066, 128252, 2), + (128255, 128317, 2), + (128331, 128334, 2), + (128336, 128359, 2), + (128378, 128378, 2), + (128405, 128406, 2), + (128420, 128420, 2), + (128507, 128591, 2), + (128640, 128709, 2), + (128716, 128716, 2), + (128720, 128722, 2), + (128725, 128727, 2), + (128747, 128748, 2), + (128756, 128764, 2), + (128992, 129003, 2), + (129292, 129338, 2), + (129340, 129349, 2), + (129351, 129400, 2), + (129402, 129483, 2), + (129485, 129535, 2), + (129648, 129652, 2), + (129656, 129658, 2), + (129664, 129670, 2), + (129680, 129704, 2), + (129712, 129718, 2), + (129728, 129730, 2), + (129744, 129750, 2), + (131072, 196605, 2), + (196608, 262141, 2), + (917760, 917999, 0), +] diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_codes.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_codes.py new file mode 100644 index 00000000..1f2877bb --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_codes.py @@ -0,0 +1,3610 @@ +EMOJI = { + "1st_place_medal": "🥇", + "2nd_place_medal": "🥈", + "3rd_place_medal": "🥉", + "ab_button_(blood_type)": "🆎", + "atm_sign": "🏧", + "a_button_(blood_type)": "🅰", + "afghanistan": "🇦🇫", + "albania": "🇦🇱", + "algeria": "🇩🇿", + "american_samoa": "🇦🇸", + "andorra": "🇦🇩", + "angola": "🇦🇴", + "anguilla": "🇦🇮", + "antarctica": "🇦🇶", + "antigua_&_barbuda": "🇦🇬", + "aquarius": "♒", + "argentina": "🇦🇷", + "aries": "♈", + "armenia": "🇦🇲", + "aruba": "🇦🇼", + "ascension_island": "🇦🇨", + "australia": "🇦🇺", + "austria": "🇦🇹", + "azerbaijan": "🇦🇿", + "back_arrow": "🔙", + "b_button_(blood_type)": "🅱", + "bahamas": "🇧🇸", + "bahrain": "🇧🇭", + "bangladesh": "🇧🇩", + "barbados": "🇧🇧", + "belarus": "🇧🇾", + "belgium": "🇧🇪", + "belize": "🇧🇿", + "benin": "🇧🇯", + "bermuda": "🇧🇲", + "bhutan": "🇧🇹", + "bolivia": "🇧🇴", + "bosnia_&_herzegovina": "🇧🇦", + "botswana": "🇧🇼", + "bouvet_island": "🇧🇻", + "brazil": "🇧🇷", + "british_indian_ocean_territory": "🇮🇴", + "british_virgin_islands": "🇻🇬", + "brunei": "🇧🇳", + "bulgaria": "🇧🇬", + "burkina_faso": "🇧🇫", + "burundi": "🇧🇮", + "cl_button": "🆑", + "cool_button": "🆒", + "cambodia": "🇰🇭", + "cameroon": "🇨🇲", + "canada": "🇨🇦", + "canary_islands": "🇮🇨", + "cancer": "♋", + "cape_verde": "🇨🇻", + "capricorn": "♑", + "caribbean_netherlands": "🇧🇶", + "cayman_islands": "🇰🇾", + "central_african_republic": "🇨🇫", + "ceuta_&_melilla": "🇪🇦", + "chad": "🇹🇩", + "chile": "🇨🇱", + "china": "🇨🇳", + "christmas_island": "🇨🇽", + "christmas_tree": "🎄", + "clipperton_island": "🇨🇵", + "cocos_(keeling)_islands": "🇨🇨", + "colombia": "🇨🇴", + "comoros": "🇰🇲", + "congo_-_brazzaville": "🇨🇬", + "congo_-_kinshasa": "🇨🇩", + "cook_islands": "🇨🇰", + "costa_rica": "🇨🇷", + "croatia": "🇭🇷", + "cuba": "🇨🇺", + "curaçao": "🇨🇼", + "cyprus": "🇨🇾", + "czechia": "🇨🇿", + "côte_d’ivoire": "🇨🇮", + "denmark": "🇩🇰", + "diego_garcia": "🇩🇬", + "djibouti": "🇩🇯", + "dominica": "🇩🇲", + "dominican_republic": "🇩🇴", + "end_arrow": "🔚", + "ecuador": "🇪🇨", + "egypt": "🇪🇬", + "el_salvador": "🇸🇻", + "england": "🏴\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f", + "equatorial_guinea": "🇬🇶", + "eritrea": "🇪🇷", + "estonia": "🇪🇪", + "ethiopia": "🇪🇹", + "european_union": "🇪🇺", + "free_button": "🆓", + "falkland_islands": "🇫🇰", + "faroe_islands": "🇫🇴", + "fiji": "🇫🇯", + "finland": "🇫🇮", + "france": "🇫🇷", + "french_guiana": "🇬🇫", + "french_polynesia": "🇵🇫", + "french_southern_territories": "🇹🇫", + "gabon": "🇬🇦", + "gambia": "🇬🇲", + "gemini": "♊", + "georgia": "🇬🇪", + "germany": "🇩🇪", + "ghana": "🇬🇭", + "gibraltar": "🇬🇮", + "greece": "🇬🇷", + "greenland": "🇬🇱", + "grenada": "🇬🇩", + "guadeloupe": "🇬🇵", + "guam": "🇬🇺", + "guatemala": "🇬🇹", + "guernsey": "🇬🇬", + "guinea": "🇬🇳", + "guinea-bissau": "🇬🇼", + "guyana": "🇬🇾", + "haiti": "🇭🇹", + "heard_&_mcdonald_islands": "🇭🇲", + "honduras": "🇭🇳", + "hong_kong_sar_china": "🇭🇰", + "hungary": "🇭🇺", + "id_button": "🆔", + "iceland": "🇮🇸", + "india": "🇮🇳", + "indonesia": "🇮🇩", + "iran": "🇮🇷", + "iraq": "🇮🇶", + "ireland": "🇮🇪", + "isle_of_man": "🇮🇲", + "israel": "🇮🇱", + "italy": "🇮🇹", + "jamaica": "🇯🇲", + "japan": "🗾", + "japanese_acceptable_button": "🉑", + "japanese_application_button": "🈸", + "japanese_bargain_button": "🉐", + "japanese_castle": "🏯", + "japanese_congratulations_button": "㊗", + "japanese_discount_button": "🈹", + "japanese_dolls": "🎎", + "japanese_free_of_charge_button": "🈚", + "japanese_here_button": "🈁", + "japanese_monthly_amount_button": "🈷", + "japanese_no_vacancy_button": "🈵", + "japanese_not_free_of_charge_button": "🈶", + "japanese_open_for_business_button": "🈺", + "japanese_passing_grade_button": "🈴", + "japanese_post_office": "🏣", + "japanese_prohibited_button": "🈲", + "japanese_reserved_button": "🈯", + "japanese_secret_button": "㊙", + "japanese_service_charge_button": "🈂", + "japanese_symbol_for_beginner": "🔰", + "japanese_vacancy_button": "🈳", + "jersey": "🇯🇪", + "jordan": "🇯🇴", + "kazakhstan": "🇰🇿", + "kenya": "🇰🇪", + "kiribati": "🇰🇮", + "kosovo": "🇽🇰", + "kuwait": "🇰🇼", + "kyrgyzstan": "🇰🇬", + "laos": "🇱🇦", + "latvia": "🇱🇻", + "lebanon": "🇱🇧", + "leo": "♌", + "lesotho": "🇱🇸", + "liberia": "🇱🇷", + "libra": "♎", + "libya": "🇱🇾", + "liechtenstein": "🇱🇮", + "lithuania": "🇱🇹", + "luxembourg": "🇱🇺", + "macau_sar_china": "🇲🇴", + "macedonia": "🇲🇰", + "madagascar": "🇲🇬", + "malawi": "🇲🇼", + "malaysia": "🇲🇾", + "maldives": "🇲🇻", + "mali": "🇲🇱", + "malta": "🇲🇹", + "marshall_islands": "🇲🇭", + "martinique": "🇲🇶", + "mauritania": "🇲🇷", + "mauritius": "🇲🇺", + "mayotte": "🇾🇹", + "mexico": "🇲🇽", + "micronesia": "🇫🇲", + "moldova": "🇲🇩", + "monaco": "🇲🇨", + "mongolia": "🇲🇳", + "montenegro": "🇲🇪", + "montserrat": "🇲🇸", + "morocco": "🇲🇦", + "mozambique": "🇲🇿", + "mrs._claus": "🤶", + "mrs._claus_dark_skin_tone": "🤶🏿", + "mrs._claus_light_skin_tone": "🤶🏻", + "mrs._claus_medium-dark_skin_tone": "🤶🏾", + "mrs._claus_medium-light_skin_tone": "🤶🏼", + "mrs._claus_medium_skin_tone": "🤶🏽", + "myanmar_(burma)": "🇲🇲", + "new_button": "🆕", + "ng_button": "🆖", + "namibia": "🇳🇦", + "nauru": "🇳🇷", + "nepal": "🇳🇵", + "netherlands": "🇳🇱", + "new_caledonia": "🇳🇨", + "new_zealand": "🇳🇿", + "nicaragua": "🇳🇮", + "niger": "🇳🇪", + "nigeria": "🇳🇬", + "niue": "🇳🇺", + "norfolk_island": "🇳🇫", + "north_korea": "🇰🇵", + "northern_mariana_islands": "🇲🇵", + "norway": "🇳🇴", + "ok_button": "🆗", + "ok_hand": "👌", + "ok_hand_dark_skin_tone": "👌🏿", + "ok_hand_light_skin_tone": "👌🏻", + "ok_hand_medium-dark_skin_tone": "👌🏾", + "ok_hand_medium-light_skin_tone": "👌🏼", + "ok_hand_medium_skin_tone": "👌🏽", + "on!_arrow": "🔛", + "o_button_(blood_type)": "🅾", + "oman": "🇴🇲", + "ophiuchus": "⛎", + "p_button": "🅿", + "pakistan": "🇵🇰", + "palau": "🇵🇼", + "palestinian_territories": "🇵🇸", + "panama": "🇵🇦", + "papua_new_guinea": "🇵🇬", + "paraguay": "🇵🇾", + "peru": "🇵🇪", + "philippines": "🇵🇭", + "pisces": "♓", + "pitcairn_islands": "🇵🇳", + "poland": "🇵🇱", + "portugal": "🇵🇹", + "puerto_rico": "🇵🇷", + "qatar": "🇶🇦", + "romania": "🇷🇴", + "russia": "🇷🇺", + "rwanda": "🇷🇼", + "réunion": "🇷🇪", + "soon_arrow": "🔜", + "sos_button": "🆘", + "sagittarius": "♐", + "samoa": "🇼🇸", + "san_marino": "🇸🇲", + "santa_claus": "🎅", + "santa_claus_dark_skin_tone": "🎅🏿", + "santa_claus_light_skin_tone": "🎅🏻", + "santa_claus_medium-dark_skin_tone": "🎅🏾", + "santa_claus_medium-light_skin_tone": "🎅🏼", + "santa_claus_medium_skin_tone": "🎅🏽", + "saudi_arabia": "🇸🇦", + "scorpio": "♏", + "scotland": "🏴\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f", + "senegal": "🇸🇳", + "serbia": "🇷🇸", + "seychelles": "🇸🇨", + "sierra_leone": "🇸🇱", + "singapore": "🇸🇬", + "sint_maarten": "🇸🇽", + "slovakia": "🇸🇰", + "slovenia": "🇸🇮", + "solomon_islands": "🇸🇧", + "somalia": "🇸🇴", + "south_africa": "🇿🇦", + "south_georgia_&_south_sandwich_islands": "🇬🇸", + "south_korea": "🇰🇷", + "south_sudan": "🇸🇸", + "spain": "🇪🇸", + "sri_lanka": "🇱🇰", + "st._barthélemy": "🇧🇱", + "st._helena": "🇸🇭", + "st._kitts_&_nevis": "🇰🇳", + "st._lucia": "🇱🇨", + "st._martin": "🇲🇫", + "st._pierre_&_miquelon": "🇵🇲", + "st._vincent_&_grenadines": "🇻🇨", + "statue_of_liberty": "🗽", + "sudan": "🇸🇩", + "suriname": "🇸🇷", + "svalbard_&_jan_mayen": "🇸🇯", + "swaziland": "🇸🇿", + "sweden": "🇸🇪", + "switzerland": "🇨🇭", + "syria": "🇸🇾", + "são_tomé_&_príncipe": "🇸🇹", + "t-rex": "🦖", + "top_arrow": "🔝", + "taiwan": "🇹🇼", + "tajikistan": "🇹🇯", + "tanzania": "🇹🇿", + "taurus": "♉", + "thailand": "🇹🇭", + "timor-leste": "🇹🇱", + "togo": "🇹🇬", + "tokelau": "🇹🇰", + "tokyo_tower": "🗼", + "tonga": "🇹🇴", + "trinidad_&_tobago": "🇹🇹", + "tristan_da_cunha": "🇹🇦", + "tunisia": "🇹🇳", + "turkey": "🦃", + "turkmenistan": "🇹🇲", + "turks_&_caicos_islands": "🇹🇨", + "tuvalu": "🇹🇻", + "u.s._outlying_islands": "🇺🇲", + "u.s._virgin_islands": "🇻🇮", + "up!_button": "🆙", + "uganda": "🇺🇬", + "ukraine": "🇺🇦", + "united_arab_emirates": "🇦🇪", + "united_kingdom": "🇬🇧", + "united_nations": "🇺🇳", + "united_states": "🇺🇸", + "uruguay": "🇺🇾", + "uzbekistan": "🇺🇿", + "vs_button": "🆚", + "vanuatu": "🇻🇺", + "vatican_city": "🇻🇦", + "venezuela": "🇻🇪", + "vietnam": "🇻🇳", + "virgo": "♍", + "wales": "🏴\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f", + "wallis_&_futuna": "🇼🇫", + "western_sahara": "🇪🇭", + "yemen": "🇾🇪", + "zambia": "🇿🇲", + "zimbabwe": "🇿🇼", + "abacus": "🧮", + "adhesive_bandage": "🩹", + "admission_tickets": "🎟", + "adult": "🧑", + "adult_dark_skin_tone": "🧑🏿", + "adult_light_skin_tone": "🧑🏻", + "adult_medium-dark_skin_tone": "🧑🏾", + "adult_medium-light_skin_tone": "🧑🏼", + "adult_medium_skin_tone": "🧑🏽", + "aerial_tramway": "🚡", + "airplane": "✈", + "airplane_arrival": "🛬", + "airplane_departure": "🛫", + "alarm_clock": "⏰", + "alembic": "⚗", + "alien": "👽", + "alien_monster": "👾", + "ambulance": "🚑", + "american_football": "🏈", + "amphora": "🏺", + "anchor": "⚓", + "anger_symbol": "💢", + "angry_face": "😠", + "angry_face_with_horns": "👿", + "anguished_face": "😧", + "ant": "🐜", + "antenna_bars": "📶", + "anxious_face_with_sweat": "😰", + "articulated_lorry": "🚛", + "artist_palette": "🎨", + "astonished_face": "😲", + "atom_symbol": "⚛", + "auto_rickshaw": "🛺", + "automobile": "🚗", + "avocado": "🥑", + "axe": "🪓", + "baby": "👶", + "baby_angel": "👼", + "baby_angel_dark_skin_tone": "👼🏿", + "baby_angel_light_skin_tone": "👼🏻", + "baby_angel_medium-dark_skin_tone": "👼🏾", + "baby_angel_medium-light_skin_tone": "👼🏼", + "baby_angel_medium_skin_tone": "👼🏽", + "baby_bottle": "🍼", + "baby_chick": "🐤", + "baby_dark_skin_tone": "👶🏿", + "baby_light_skin_tone": "👶🏻", + "baby_medium-dark_skin_tone": "👶🏾", + "baby_medium-light_skin_tone": "👶🏼", + "baby_medium_skin_tone": "👶🏽", + "baby_symbol": "🚼", + "backhand_index_pointing_down": "👇", + "backhand_index_pointing_down_dark_skin_tone": "👇🏿", + "backhand_index_pointing_down_light_skin_tone": "👇🏻", + "backhand_index_pointing_down_medium-dark_skin_tone": "👇🏾", + "backhand_index_pointing_down_medium-light_skin_tone": "👇🏼", + "backhand_index_pointing_down_medium_skin_tone": "👇🏽", + "backhand_index_pointing_left": "👈", + "backhand_index_pointing_left_dark_skin_tone": "👈🏿", + "backhand_index_pointing_left_light_skin_tone": "👈🏻", + "backhand_index_pointing_left_medium-dark_skin_tone": "👈🏾", + "backhand_index_pointing_left_medium-light_skin_tone": "👈🏼", + "backhand_index_pointing_left_medium_skin_tone": "👈🏽", + "backhand_index_pointing_right": "👉", + "backhand_index_pointing_right_dark_skin_tone": "👉🏿", + "backhand_index_pointing_right_light_skin_tone": "👉🏻", + "backhand_index_pointing_right_medium-dark_skin_tone": "👉🏾", + "backhand_index_pointing_right_medium-light_skin_tone": "👉🏼", + "backhand_index_pointing_right_medium_skin_tone": "👉🏽", + "backhand_index_pointing_up": "👆", + "backhand_index_pointing_up_dark_skin_tone": "👆🏿", + "backhand_index_pointing_up_light_skin_tone": "👆🏻", + "backhand_index_pointing_up_medium-dark_skin_tone": "👆🏾", + "backhand_index_pointing_up_medium-light_skin_tone": "👆🏼", + "backhand_index_pointing_up_medium_skin_tone": "👆🏽", + "bacon": "🥓", + "badger": "🦡", + "badminton": "🏸", + "bagel": "🥯", + "baggage_claim": "🛄", + "baguette_bread": "🥖", + "balance_scale": "⚖", + "bald": "🦲", + "bald_man": "👨\u200d🦲", + "bald_woman": "👩\u200d🦲", + "ballet_shoes": "🩰", + "balloon": "🎈", + "ballot_box_with_ballot": "🗳", + "ballot_box_with_check": "☑", + "banana": "🍌", + "banjo": "🪕", + "bank": "🏦", + "bar_chart": "📊", + "barber_pole": "💈", + "baseball": "⚾", + "basket": "🧺", + "basketball": "🏀", + "bat": "🦇", + "bathtub": "🛁", + "battery": "🔋", + "beach_with_umbrella": "🏖", + "beaming_face_with_smiling_eyes": "😁", + "bear_face": "🐻", + "bearded_person": "🧔", + "bearded_person_dark_skin_tone": "🧔🏿", + "bearded_person_light_skin_tone": "🧔🏻", + "bearded_person_medium-dark_skin_tone": "🧔🏾", + "bearded_person_medium-light_skin_tone": "🧔🏼", + "bearded_person_medium_skin_tone": "🧔🏽", + "beating_heart": "💓", + "bed": "🛏", + "beer_mug": "🍺", + "bell": "🔔", + "bell_with_slash": "🔕", + "bellhop_bell": "🛎", + "bento_box": "🍱", + "beverage_box": "🧃", + "bicycle": "🚲", + "bikini": "👙", + "billed_cap": "🧢", + "biohazard": "☣", + "bird": "🐦", + "birthday_cake": "🎂", + "black_circle": "⚫", + "black_flag": "🏴", + "black_heart": "🖤", + "black_large_square": "⬛", + "black_medium-small_square": "◾", + "black_medium_square": "◼", + "black_nib": "✒", + "black_small_square": "▪", + "black_square_button": "🔲", + "blond-haired_man": "👱\u200d♂️", + "blond-haired_man_dark_skin_tone": "👱🏿\u200d♂️", + "blond-haired_man_light_skin_tone": "👱🏻\u200d♂️", + "blond-haired_man_medium-dark_skin_tone": "👱🏾\u200d♂️", + "blond-haired_man_medium-light_skin_tone": "👱🏼\u200d♂️", + "blond-haired_man_medium_skin_tone": "👱🏽\u200d♂️", + "blond-haired_person": "👱", + "blond-haired_person_dark_skin_tone": "👱🏿", + "blond-haired_person_light_skin_tone": "👱🏻", + "blond-haired_person_medium-dark_skin_tone": "👱🏾", + "blond-haired_person_medium-light_skin_tone": "👱🏼", + "blond-haired_person_medium_skin_tone": "👱🏽", + "blond-haired_woman": "👱\u200d♀️", + "blond-haired_woman_dark_skin_tone": "👱🏿\u200d♀️", + "blond-haired_woman_light_skin_tone": "👱🏻\u200d♀️", + "blond-haired_woman_medium-dark_skin_tone": "👱🏾\u200d♀️", + "blond-haired_woman_medium-light_skin_tone": "👱🏼\u200d♀️", + "blond-haired_woman_medium_skin_tone": "👱🏽\u200d♀️", + "blossom": "🌼", + "blowfish": "🐡", + "blue_book": "📘", + "blue_circle": "🔵", + "blue_heart": "💙", + "blue_square": "🟦", + "boar": "🐗", + "bomb": "💣", + "bone": "🦴", + "bookmark": "🔖", + "bookmark_tabs": "📑", + "books": "📚", + "bottle_with_popping_cork": "🍾", + "bouquet": "💐", + "bow_and_arrow": "🏹", + "bowl_with_spoon": "🥣", + "bowling": "🎳", + "boxing_glove": "🥊", + "boy": "👦", + "boy_dark_skin_tone": "👦🏿", + "boy_light_skin_tone": "👦🏻", + "boy_medium-dark_skin_tone": "👦🏾", + "boy_medium-light_skin_tone": "👦🏼", + "boy_medium_skin_tone": "👦🏽", + "brain": "🧠", + "bread": "🍞", + "breast-feeding": "🤱", + "breast-feeding_dark_skin_tone": "🤱🏿", + "breast-feeding_light_skin_tone": "🤱🏻", + "breast-feeding_medium-dark_skin_tone": "🤱🏾", + "breast-feeding_medium-light_skin_tone": "🤱🏼", + "breast-feeding_medium_skin_tone": "🤱🏽", + "brick": "🧱", + "bride_with_veil": "👰", + "bride_with_veil_dark_skin_tone": "👰🏿", + "bride_with_veil_light_skin_tone": "👰🏻", + "bride_with_veil_medium-dark_skin_tone": "👰🏾", + "bride_with_veil_medium-light_skin_tone": "👰🏼", + "bride_with_veil_medium_skin_tone": "👰🏽", + "bridge_at_night": "🌉", + "briefcase": "💼", + "briefs": "🩲", + "bright_button": "🔆", + "broccoli": "🥦", + "broken_heart": "💔", + "broom": "🧹", + "brown_circle": "🟤", + "brown_heart": "🤎", + "brown_square": "🟫", + "bug": "🐛", + "building_construction": "🏗", + "bullet_train": "🚅", + "burrito": "🌯", + "bus": "🚌", + "bus_stop": "🚏", + "bust_in_silhouette": "👤", + "busts_in_silhouette": "👥", + "butter": "🧈", + "butterfly": "🦋", + "cactus": "🌵", + "calendar": "📆", + "call_me_hand": "🤙", + "call_me_hand_dark_skin_tone": "🤙🏿", + "call_me_hand_light_skin_tone": "🤙🏻", + "call_me_hand_medium-dark_skin_tone": "🤙🏾", + "call_me_hand_medium-light_skin_tone": "🤙🏼", + "call_me_hand_medium_skin_tone": "🤙🏽", + "camel": "🐫", + "camera": "📷", + "camera_with_flash": "📸", + "camping": "🏕", + "candle": "🕯", + "candy": "🍬", + "canned_food": "🥫", + "canoe": "🛶", + "card_file_box": "🗃", + "card_index": "📇", + "card_index_dividers": "🗂", + "carousel_horse": "🎠", + "carp_streamer": "🎏", + "carrot": "🥕", + "castle": "🏰", + "cat": "🐱", + "cat_face": "🐱", + "cat_face_with_tears_of_joy": "😹", + "cat_face_with_wry_smile": "😼", + "chains": "⛓", + "chair": "🪑", + "chart_decreasing": "📉", + "chart_increasing": "📈", + "chart_increasing_with_yen": "💹", + "cheese_wedge": "🧀", + "chequered_flag": "🏁", + "cherries": "🍒", + "cherry_blossom": "🌸", + "chess_pawn": "♟", + "chestnut": "🌰", + "chicken": "🐔", + "child": "🧒", + "child_dark_skin_tone": "🧒🏿", + "child_light_skin_tone": "🧒🏻", + "child_medium-dark_skin_tone": "🧒🏾", + "child_medium-light_skin_tone": "🧒🏼", + "child_medium_skin_tone": "🧒🏽", + "children_crossing": "🚸", + "chipmunk": "🐿", + "chocolate_bar": "🍫", + "chopsticks": "🥢", + "church": "⛪", + "cigarette": "🚬", + "cinema": "🎦", + "circled_m": "Ⓜ", + "circus_tent": "🎪", + "cityscape": "🏙", + "cityscape_at_dusk": "🌆", + "clamp": "🗜", + "clapper_board": "🎬", + "clapping_hands": "👏", + "clapping_hands_dark_skin_tone": "👏🏿", + "clapping_hands_light_skin_tone": "👏🏻", + "clapping_hands_medium-dark_skin_tone": "👏🏾", + "clapping_hands_medium-light_skin_tone": "👏🏼", + "clapping_hands_medium_skin_tone": "👏🏽", + "classical_building": "🏛", + "clinking_beer_mugs": "🍻", + "clinking_glasses": "🥂", + "clipboard": "📋", + "clockwise_vertical_arrows": "🔃", + "closed_book": "📕", + "closed_mailbox_with_lowered_flag": "📪", + "closed_mailbox_with_raised_flag": "📫", + "closed_umbrella": "🌂", + "cloud": "☁", + "cloud_with_lightning": "🌩", + "cloud_with_lightning_and_rain": "⛈", + "cloud_with_rain": "🌧", + "cloud_with_snow": "🌨", + "clown_face": "🤡", + "club_suit": "♣", + "clutch_bag": "👝", + "coat": "🧥", + "cocktail_glass": "🍸", + "coconut": "🥥", + "coffin": "⚰", + "cold_face": "🥶", + "collision": "💥", + "comet": "☄", + "compass": "🧭", + "computer_disk": "💽", + "computer_mouse": "🖱", + "confetti_ball": "🎊", + "confounded_face": "😖", + "confused_face": "😕", + "construction": "🚧", + "construction_worker": "👷", + "construction_worker_dark_skin_tone": "👷🏿", + "construction_worker_light_skin_tone": "👷🏻", + "construction_worker_medium-dark_skin_tone": "👷🏾", + "construction_worker_medium-light_skin_tone": "👷🏼", + "construction_worker_medium_skin_tone": "👷🏽", + "control_knobs": "🎛", + "convenience_store": "🏪", + "cooked_rice": "🍚", + "cookie": "🍪", + "cooking": "🍳", + "copyright": "©", + "couch_and_lamp": "🛋", + "counterclockwise_arrows_button": "🔄", + "couple_with_heart": "💑", + "couple_with_heart_man_man": "👨\u200d❤️\u200d👨", + "couple_with_heart_woman_man": "👩\u200d❤️\u200d👨", + "couple_with_heart_woman_woman": "👩\u200d❤️\u200d👩", + "cow": "🐮", + "cow_face": "🐮", + "cowboy_hat_face": "🤠", + "crab": "🦀", + "crayon": "🖍", + "credit_card": "💳", + "crescent_moon": "🌙", + "cricket": "🦗", + "cricket_game": "🏏", + "crocodile": "🐊", + "croissant": "🥐", + "cross_mark": "❌", + "cross_mark_button": "❎", + "crossed_fingers": "🤞", + "crossed_fingers_dark_skin_tone": "🤞🏿", + "crossed_fingers_light_skin_tone": "🤞🏻", + "crossed_fingers_medium-dark_skin_tone": "🤞🏾", + "crossed_fingers_medium-light_skin_tone": "🤞🏼", + "crossed_fingers_medium_skin_tone": "🤞🏽", + "crossed_flags": "🎌", + "crossed_swords": "⚔", + "crown": "👑", + "crying_cat_face": "😿", + "crying_face": "😢", + "crystal_ball": "🔮", + "cucumber": "🥒", + "cupcake": "🧁", + "cup_with_straw": "🥤", + "curling_stone": "🥌", + "curly_hair": "🦱", + "curly-haired_man": "👨\u200d🦱", + "curly-haired_woman": "👩\u200d🦱", + "curly_loop": "➰", + "currency_exchange": "💱", + "curry_rice": "🍛", + "custard": "🍮", + "customs": "🛃", + "cut_of_meat": "🥩", + "cyclone": "🌀", + "dagger": "🗡", + "dango": "🍡", + "dashing_away": "💨", + "deaf_person": "🧏", + "deciduous_tree": "🌳", + "deer": "🦌", + "delivery_truck": "🚚", + "department_store": "🏬", + "derelict_house": "🏚", + "desert": "🏜", + "desert_island": "🏝", + "desktop_computer": "🖥", + "detective": "🕵", + "detective_dark_skin_tone": "🕵🏿", + "detective_light_skin_tone": "🕵🏻", + "detective_medium-dark_skin_tone": "🕵🏾", + "detective_medium-light_skin_tone": "🕵🏼", + "detective_medium_skin_tone": "🕵🏽", + "diamond_suit": "♦", + "diamond_with_a_dot": "💠", + "dim_button": "🔅", + "direct_hit": "🎯", + "disappointed_face": "😞", + "diving_mask": "🤿", + "diya_lamp": "🪔", + "dizzy": "💫", + "dizzy_face": "😵", + "dna": "🧬", + "dog": "🐶", + "dog_face": "🐶", + "dollar_banknote": "💵", + "dolphin": "🐬", + "door": "🚪", + "dotted_six-pointed_star": "🔯", + "double_curly_loop": "➿", + "double_exclamation_mark": "‼", + "doughnut": "🍩", + "dove": "🕊", + "down-left_arrow": "↙", + "down-right_arrow": "↘", + "down_arrow": "⬇", + "downcast_face_with_sweat": "😓", + "downwards_button": "🔽", + "dragon": "🐉", + "dragon_face": "🐲", + "dress": "👗", + "drooling_face": "🤤", + "drop_of_blood": "🩸", + "droplet": "💧", + "drum": "🥁", + "duck": "🦆", + "dumpling": "🥟", + "dvd": "📀", + "e-mail": "📧", + "eagle": "🦅", + "ear": "👂", + "ear_dark_skin_tone": "👂🏿", + "ear_light_skin_tone": "👂🏻", + "ear_medium-dark_skin_tone": "👂🏾", + "ear_medium-light_skin_tone": "👂🏼", + "ear_medium_skin_tone": "👂🏽", + "ear_of_corn": "🌽", + "ear_with_hearing_aid": "🦻", + "egg": "🍳", + "eggplant": "🍆", + "eight-pointed_star": "✴", + "eight-spoked_asterisk": "✳", + "eight-thirty": "🕣", + "eight_o’clock": "🕗", + "eject_button": "⏏", + "electric_plug": "🔌", + "elephant": "🐘", + "eleven-thirty": "🕦", + "eleven_o’clock": "🕚", + "elf": "🧝", + "elf_dark_skin_tone": "🧝🏿", + "elf_light_skin_tone": "🧝🏻", + "elf_medium-dark_skin_tone": "🧝🏾", + "elf_medium-light_skin_tone": "🧝🏼", + "elf_medium_skin_tone": "🧝🏽", + "envelope": "✉", + "envelope_with_arrow": "📩", + "euro_banknote": "💶", + "evergreen_tree": "🌲", + "ewe": "🐑", + "exclamation_mark": "❗", + "exclamation_question_mark": "⁉", + "exploding_head": "🤯", + "expressionless_face": "😑", + "eye": "👁", + "eye_in_speech_bubble": "👁️\u200d🗨️", + "eyes": "👀", + "face_blowing_a_kiss": "😘", + "face_savoring_food": "😋", + "face_screaming_in_fear": "😱", + "face_vomiting": "🤮", + "face_with_hand_over_mouth": "🤭", + "face_with_head-bandage": "🤕", + "face_with_medical_mask": "😷", + "face_with_monocle": "🧐", + "face_with_open_mouth": "😮", + "face_with_raised_eyebrow": "🤨", + "face_with_rolling_eyes": "🙄", + "face_with_steam_from_nose": "😤", + "face_with_symbols_on_mouth": "🤬", + "face_with_tears_of_joy": "😂", + "face_with_thermometer": "🤒", + "face_with_tongue": "😛", + "face_without_mouth": "😶", + "factory": "🏭", + "fairy": "🧚", + "fairy_dark_skin_tone": "🧚🏿", + "fairy_light_skin_tone": "🧚🏻", + "fairy_medium-dark_skin_tone": "🧚🏾", + "fairy_medium-light_skin_tone": "🧚🏼", + "fairy_medium_skin_tone": "🧚🏽", + "falafel": "🧆", + "fallen_leaf": "🍂", + "family": "👪", + "family_man_boy": "👨\u200d👦", + "family_man_boy_boy": "👨\u200d👦\u200d👦", + "family_man_girl": "👨\u200d👧", + "family_man_girl_boy": "👨\u200d👧\u200d👦", + "family_man_girl_girl": "👨\u200d👧\u200d👧", + "family_man_man_boy": "👨\u200d👨\u200d👦", + "family_man_man_boy_boy": "👨\u200d👨\u200d👦\u200d👦", + "family_man_man_girl": "👨\u200d👨\u200d👧", + "family_man_man_girl_boy": "👨\u200d👨\u200d👧\u200d👦", + "family_man_man_girl_girl": "👨\u200d👨\u200d👧\u200d👧", + "family_man_woman_boy": "👨\u200d👩\u200d👦", + "family_man_woman_boy_boy": "👨\u200d👩\u200d👦\u200d👦", + "family_man_woman_girl": "👨\u200d👩\u200d👧", + "family_man_woman_girl_boy": "👨\u200d👩\u200d👧\u200d👦", + "family_man_woman_girl_girl": "👨\u200d👩\u200d👧\u200d👧", + "family_woman_boy": "👩\u200d👦", + "family_woman_boy_boy": "👩\u200d👦\u200d👦", + "family_woman_girl": "👩\u200d👧", + "family_woman_girl_boy": "👩\u200d👧\u200d👦", + "family_woman_girl_girl": "👩\u200d👧\u200d👧", + "family_woman_woman_boy": "👩\u200d👩\u200d👦", + "family_woman_woman_boy_boy": "👩\u200d👩\u200d👦\u200d👦", + "family_woman_woman_girl": "👩\u200d👩\u200d👧", + "family_woman_woman_girl_boy": "👩\u200d👩\u200d👧\u200d👦", + "family_woman_woman_girl_girl": "👩\u200d👩\u200d👧\u200d👧", + "fast-forward_button": "⏩", + "fast_down_button": "⏬", + "fast_reverse_button": "⏪", + "fast_up_button": "⏫", + "fax_machine": "📠", + "fearful_face": "😨", + "female_sign": "♀", + "ferris_wheel": "🎡", + "ferry": "⛴", + "field_hockey": "🏑", + "file_cabinet": "🗄", + "file_folder": "📁", + "film_frames": "🎞", + "film_projector": "📽", + "fire": "🔥", + "fire_extinguisher": "🧯", + "firecracker": "🧨", + "fire_engine": "🚒", + "fireworks": "🎆", + "first_quarter_moon": "🌓", + "first_quarter_moon_face": "🌛", + "fish": "🐟", + "fish_cake_with_swirl": "🍥", + "fishing_pole": "🎣", + "five-thirty": "🕠", + "five_o’clock": "🕔", + "flag_in_hole": "⛳", + "flamingo": "🦩", + "flashlight": "🔦", + "flat_shoe": "🥿", + "fleur-de-lis": "⚜", + "flexed_biceps": "💪", + "flexed_biceps_dark_skin_tone": "💪🏿", + "flexed_biceps_light_skin_tone": "💪🏻", + "flexed_biceps_medium-dark_skin_tone": "💪🏾", + "flexed_biceps_medium-light_skin_tone": "💪🏼", + "flexed_biceps_medium_skin_tone": "💪🏽", + "floppy_disk": "💾", + "flower_playing_cards": "🎴", + "flushed_face": "😳", + "flying_disc": "🥏", + "flying_saucer": "🛸", + "fog": "🌫", + "foggy": "🌁", + "folded_hands": "🙏", + "folded_hands_dark_skin_tone": "🙏🏿", + "folded_hands_light_skin_tone": "🙏🏻", + "folded_hands_medium-dark_skin_tone": "🙏🏾", + "folded_hands_medium-light_skin_tone": "🙏🏼", + "folded_hands_medium_skin_tone": "🙏🏽", + "foot": "🦶", + "footprints": "👣", + "fork_and_knife": "🍴", + "fork_and_knife_with_plate": "🍽", + "fortune_cookie": "🥠", + "fountain": "⛲", + "fountain_pen": "🖋", + "four-thirty": "🕟", + "four_leaf_clover": "🍀", + "four_o’clock": "🕓", + "fox_face": "🦊", + "framed_picture": "🖼", + "french_fries": "🍟", + "fried_shrimp": "🍤", + "frog_face": "🐸", + "front-facing_baby_chick": "🐥", + "frowning_face": "☹", + "frowning_face_with_open_mouth": "😦", + "fuel_pump": "⛽", + "full_moon": "🌕", + "full_moon_face": "🌝", + "funeral_urn": "⚱", + "game_die": "🎲", + "garlic": "🧄", + "gear": "⚙", + "gem_stone": "💎", + "genie": "🧞", + "ghost": "👻", + "giraffe": "🦒", + "girl": "👧", + "girl_dark_skin_tone": "👧🏿", + "girl_light_skin_tone": "👧🏻", + "girl_medium-dark_skin_tone": "👧🏾", + "girl_medium-light_skin_tone": "👧🏼", + "girl_medium_skin_tone": "👧🏽", + "glass_of_milk": "🥛", + "glasses": "👓", + "globe_showing_americas": "🌎", + "globe_showing_asia-australia": "🌏", + "globe_showing_europe-africa": "🌍", + "globe_with_meridians": "🌐", + "gloves": "🧤", + "glowing_star": "🌟", + "goal_net": "🥅", + "goat": "🐐", + "goblin": "👺", + "goggles": "🥽", + "gorilla": "🦍", + "graduation_cap": "🎓", + "grapes": "🍇", + "green_apple": "🍏", + "green_book": "📗", + "green_circle": "🟢", + "green_heart": "💚", + "green_salad": "🥗", + "green_square": "🟩", + "grimacing_face": "😬", + "grinning_cat_face": "😺", + "grinning_cat_face_with_smiling_eyes": "😸", + "grinning_face": "😀", + "grinning_face_with_big_eyes": "😃", + "grinning_face_with_smiling_eyes": "😄", + "grinning_face_with_sweat": "😅", + "grinning_squinting_face": "😆", + "growing_heart": "💗", + "guard": "💂", + "guard_dark_skin_tone": "💂🏿", + "guard_light_skin_tone": "💂🏻", + "guard_medium-dark_skin_tone": "💂🏾", + "guard_medium-light_skin_tone": "💂🏼", + "guard_medium_skin_tone": "💂🏽", + "guide_dog": "🦮", + "guitar": "🎸", + "hamburger": "🍔", + "hammer": "🔨", + "hammer_and_pick": "⚒", + "hammer_and_wrench": "🛠", + "hamster_face": "🐹", + "hand_with_fingers_splayed": "🖐", + "hand_with_fingers_splayed_dark_skin_tone": "🖐🏿", + "hand_with_fingers_splayed_light_skin_tone": "🖐🏻", + "hand_with_fingers_splayed_medium-dark_skin_tone": "🖐🏾", + "hand_with_fingers_splayed_medium-light_skin_tone": "🖐🏼", + "hand_with_fingers_splayed_medium_skin_tone": "🖐🏽", + "handbag": "👜", + "handshake": "🤝", + "hatching_chick": "🐣", + "headphone": "🎧", + "hear-no-evil_monkey": "🙉", + "heart_decoration": "💟", + "heart_suit": "♥", + "heart_with_arrow": "💘", + "heart_with_ribbon": "💝", + "heavy_check_mark": "✔", + "heavy_division_sign": "➗", + "heavy_dollar_sign": "💲", + "heavy_heart_exclamation": "❣", + "heavy_large_circle": "⭕", + "heavy_minus_sign": "➖", + "heavy_multiplication_x": "✖", + "heavy_plus_sign": "➕", + "hedgehog": "🦔", + "helicopter": "🚁", + "herb": "🌿", + "hibiscus": "🌺", + "high-heeled_shoe": "👠", + "high-speed_train": "🚄", + "high_voltage": "⚡", + "hiking_boot": "🥾", + "hindu_temple": "🛕", + "hippopotamus": "🦛", + "hole": "🕳", + "honey_pot": "🍯", + "honeybee": "🐝", + "horizontal_traffic_light": "🚥", + "horse": "🐴", + "horse_face": "🐴", + "horse_racing": "🏇", + "horse_racing_dark_skin_tone": "🏇🏿", + "horse_racing_light_skin_tone": "🏇🏻", + "horse_racing_medium-dark_skin_tone": "🏇🏾", + "horse_racing_medium-light_skin_tone": "🏇🏼", + "horse_racing_medium_skin_tone": "🏇🏽", + "hospital": "🏥", + "hot_beverage": "☕", + "hot_dog": "🌭", + "hot_face": "🥵", + "hot_pepper": "🌶", + "hot_springs": "♨", + "hotel": "🏨", + "hourglass_done": "⌛", + "hourglass_not_done": "⏳", + "house": "🏠", + "house_with_garden": "🏡", + "houses": "🏘", + "hugging_face": "🤗", + "hundred_points": "💯", + "hushed_face": "😯", + "ice": "🧊", + "ice_cream": "🍨", + "ice_hockey": "🏒", + "ice_skate": "⛸", + "inbox_tray": "📥", + "incoming_envelope": "📨", + "index_pointing_up": "☝", + "index_pointing_up_dark_skin_tone": "☝🏿", + "index_pointing_up_light_skin_tone": "☝🏻", + "index_pointing_up_medium-dark_skin_tone": "☝🏾", + "index_pointing_up_medium-light_skin_tone": "☝🏼", + "index_pointing_up_medium_skin_tone": "☝🏽", + "infinity": "♾", + "information": "ℹ", + "input_latin_letters": "🔤", + "input_latin_lowercase": "🔡", + "input_latin_uppercase": "🔠", + "input_numbers": "🔢", + "input_symbols": "🔣", + "jack-o-lantern": "🎃", + "jeans": "👖", + "jigsaw": "🧩", + "joker": "🃏", + "joystick": "🕹", + "kaaba": "🕋", + "kangaroo": "🦘", + "key": "🔑", + "keyboard": "⌨", + "keycap_#": "#️⃣", + "keycap_*": "*️⃣", + "keycap_0": "0️⃣", + "keycap_1": "1️⃣", + "keycap_10": "🔟", + "keycap_2": "2️⃣", + "keycap_3": "3️⃣", + "keycap_4": "4️⃣", + "keycap_5": "5️⃣", + "keycap_6": "6️⃣", + "keycap_7": "7️⃣", + "keycap_8": "8️⃣", + "keycap_9": "9️⃣", + "kick_scooter": "🛴", + "kimono": "👘", + "kiss": "💋", + "kiss_man_man": "👨\u200d❤️\u200d💋\u200d👨", + "kiss_mark": "💋", + "kiss_woman_man": "👩\u200d❤️\u200d💋\u200d👨", + "kiss_woman_woman": "👩\u200d❤️\u200d💋\u200d👩", + "kissing_cat_face": "😽", + "kissing_face": "😗", + "kissing_face_with_closed_eyes": "😚", + "kissing_face_with_smiling_eyes": "😙", + "kitchen_knife": "🔪", + "kite": "🪁", + "kiwi_fruit": "🥝", + "koala": "🐨", + "lab_coat": "🥼", + "label": "🏷", + "lacrosse": "🥍", + "lady_beetle": "🐞", + "laptop_computer": "💻", + "large_blue_diamond": "🔷", + "large_orange_diamond": "🔶", + "last_quarter_moon": "🌗", + "last_quarter_moon_face": "🌜", + "last_track_button": "⏮", + "latin_cross": "✝", + "leaf_fluttering_in_wind": "🍃", + "leafy_green": "🥬", + "ledger": "📒", + "left-facing_fist": "🤛", + "left-facing_fist_dark_skin_tone": "🤛🏿", + "left-facing_fist_light_skin_tone": "🤛🏻", + "left-facing_fist_medium-dark_skin_tone": "🤛🏾", + "left-facing_fist_medium-light_skin_tone": "🤛🏼", + "left-facing_fist_medium_skin_tone": "🤛🏽", + "left-right_arrow": "↔", + "left_arrow": "⬅", + "left_arrow_curving_right": "↪", + "left_luggage": "🛅", + "left_speech_bubble": "🗨", + "leg": "🦵", + "lemon": "🍋", + "leopard": "🐆", + "level_slider": "🎚", + "light_bulb": "💡", + "light_rail": "🚈", + "link": "🔗", + "linked_paperclips": "🖇", + "lion_face": "🦁", + "lipstick": "💄", + "litter_in_bin_sign": "🚮", + "lizard": "🦎", + "llama": "🦙", + "lobster": "🦞", + "locked": "🔒", + "locked_with_key": "🔐", + "locked_with_pen": "🔏", + "locomotive": "🚂", + "lollipop": "🍭", + "lotion_bottle": "🧴", + "loudly_crying_face": "😭", + "loudspeaker": "📢", + "love-you_gesture": "🤟", + "love-you_gesture_dark_skin_tone": "🤟🏿", + "love-you_gesture_light_skin_tone": "🤟🏻", + "love-you_gesture_medium-dark_skin_tone": "🤟🏾", + "love-you_gesture_medium-light_skin_tone": "🤟🏼", + "love-you_gesture_medium_skin_tone": "🤟🏽", + "love_hotel": "🏩", + "love_letter": "💌", + "luggage": "🧳", + "lying_face": "🤥", + "mage": "🧙", + "mage_dark_skin_tone": "🧙🏿", + "mage_light_skin_tone": "🧙🏻", + "mage_medium-dark_skin_tone": "🧙🏾", + "mage_medium-light_skin_tone": "🧙🏼", + "mage_medium_skin_tone": "🧙🏽", + "magnet": "🧲", + "magnifying_glass_tilted_left": "🔍", + "magnifying_glass_tilted_right": "🔎", + "mahjong_red_dragon": "🀄", + "male_sign": "♂", + "man": "👨", + "man_and_woman_holding_hands": "👫", + "man_artist": "👨\u200d🎨", + "man_artist_dark_skin_tone": "👨🏿\u200d🎨", + "man_artist_light_skin_tone": "👨🏻\u200d🎨", + "man_artist_medium-dark_skin_tone": "👨🏾\u200d🎨", + "man_artist_medium-light_skin_tone": "👨🏼\u200d🎨", + "man_artist_medium_skin_tone": "👨🏽\u200d🎨", + "man_astronaut": "👨\u200d🚀", + "man_astronaut_dark_skin_tone": "👨🏿\u200d🚀", + "man_astronaut_light_skin_tone": "👨🏻\u200d🚀", + "man_astronaut_medium-dark_skin_tone": "👨🏾\u200d🚀", + "man_astronaut_medium-light_skin_tone": "👨🏼\u200d🚀", + "man_astronaut_medium_skin_tone": "👨🏽\u200d🚀", + "man_biking": "🚴\u200d♂️", + "man_biking_dark_skin_tone": "🚴🏿\u200d♂️", + "man_biking_light_skin_tone": "🚴🏻\u200d♂️", + "man_biking_medium-dark_skin_tone": "🚴🏾\u200d♂️", + "man_biking_medium-light_skin_tone": "🚴🏼\u200d♂️", + "man_biking_medium_skin_tone": "🚴🏽\u200d♂️", + "man_bouncing_ball": "⛹️\u200d♂️", + "man_bouncing_ball_dark_skin_tone": "⛹🏿\u200d♂️", + "man_bouncing_ball_light_skin_tone": "⛹🏻\u200d♂️", + "man_bouncing_ball_medium-dark_skin_tone": "⛹🏾\u200d♂️", + "man_bouncing_ball_medium-light_skin_tone": "⛹🏼\u200d♂️", + "man_bouncing_ball_medium_skin_tone": "⛹🏽\u200d♂️", + "man_bowing": "🙇\u200d♂️", + "man_bowing_dark_skin_tone": "🙇🏿\u200d♂️", + "man_bowing_light_skin_tone": "🙇🏻\u200d♂️", + "man_bowing_medium-dark_skin_tone": "🙇🏾\u200d♂️", + "man_bowing_medium-light_skin_tone": "🙇🏼\u200d♂️", + "man_bowing_medium_skin_tone": "🙇🏽\u200d♂️", + "man_cartwheeling": "🤸\u200d♂️", + "man_cartwheeling_dark_skin_tone": "🤸🏿\u200d♂️", + "man_cartwheeling_light_skin_tone": "🤸🏻\u200d♂️", + "man_cartwheeling_medium-dark_skin_tone": "🤸🏾\u200d♂️", + "man_cartwheeling_medium-light_skin_tone": "🤸🏼\u200d♂️", + "man_cartwheeling_medium_skin_tone": "🤸🏽\u200d♂️", + "man_climbing": "🧗\u200d♂️", + "man_climbing_dark_skin_tone": "🧗🏿\u200d♂️", + "man_climbing_light_skin_tone": "🧗🏻\u200d♂️", + "man_climbing_medium-dark_skin_tone": "🧗🏾\u200d♂️", + "man_climbing_medium-light_skin_tone": "🧗🏼\u200d♂️", + "man_climbing_medium_skin_tone": "🧗🏽\u200d♂️", + "man_construction_worker": "👷\u200d♂️", + "man_construction_worker_dark_skin_tone": "👷🏿\u200d♂️", + "man_construction_worker_light_skin_tone": "👷🏻\u200d♂️", + "man_construction_worker_medium-dark_skin_tone": "👷🏾\u200d♂️", + "man_construction_worker_medium-light_skin_tone": "👷🏼\u200d♂️", + "man_construction_worker_medium_skin_tone": "👷🏽\u200d♂️", + "man_cook": "👨\u200d🍳", + "man_cook_dark_skin_tone": "👨🏿\u200d🍳", + "man_cook_light_skin_tone": "👨🏻\u200d🍳", + "man_cook_medium-dark_skin_tone": "👨🏾\u200d🍳", + "man_cook_medium-light_skin_tone": "👨🏼\u200d🍳", + "man_cook_medium_skin_tone": "👨🏽\u200d🍳", + "man_dancing": "🕺", + "man_dancing_dark_skin_tone": "🕺🏿", + "man_dancing_light_skin_tone": "🕺🏻", + "man_dancing_medium-dark_skin_tone": "🕺🏾", + "man_dancing_medium-light_skin_tone": "🕺🏼", + "man_dancing_medium_skin_tone": "🕺🏽", + "man_dark_skin_tone": "👨🏿", + "man_detective": "🕵️\u200d♂️", + "man_detective_dark_skin_tone": "🕵🏿\u200d♂️", + "man_detective_light_skin_tone": "🕵🏻\u200d♂️", + "man_detective_medium-dark_skin_tone": "🕵🏾\u200d♂️", + "man_detective_medium-light_skin_tone": "🕵🏼\u200d♂️", + "man_detective_medium_skin_tone": "🕵🏽\u200d♂️", + "man_elf": "🧝\u200d♂️", + "man_elf_dark_skin_tone": "🧝🏿\u200d♂️", + "man_elf_light_skin_tone": "🧝🏻\u200d♂️", + "man_elf_medium-dark_skin_tone": "🧝🏾\u200d♂️", + "man_elf_medium-light_skin_tone": "🧝🏼\u200d♂️", + "man_elf_medium_skin_tone": "🧝🏽\u200d♂️", + "man_facepalming": "🤦\u200d♂️", + "man_facepalming_dark_skin_tone": "🤦🏿\u200d♂️", + "man_facepalming_light_skin_tone": "🤦🏻\u200d♂️", + "man_facepalming_medium-dark_skin_tone": "🤦🏾\u200d♂️", + "man_facepalming_medium-light_skin_tone": "🤦🏼\u200d♂️", + "man_facepalming_medium_skin_tone": "🤦🏽\u200d♂️", + "man_factory_worker": "👨\u200d🏭", + "man_factory_worker_dark_skin_tone": "👨🏿\u200d🏭", + "man_factory_worker_light_skin_tone": "👨🏻\u200d🏭", + "man_factory_worker_medium-dark_skin_tone": "👨🏾\u200d🏭", + "man_factory_worker_medium-light_skin_tone": "👨🏼\u200d🏭", + "man_factory_worker_medium_skin_tone": "👨🏽\u200d🏭", + "man_fairy": "🧚\u200d♂️", + "man_fairy_dark_skin_tone": "🧚🏿\u200d♂️", + "man_fairy_light_skin_tone": "🧚🏻\u200d♂️", + "man_fairy_medium-dark_skin_tone": "🧚🏾\u200d♂️", + "man_fairy_medium-light_skin_tone": "🧚🏼\u200d♂️", + "man_fairy_medium_skin_tone": "🧚🏽\u200d♂️", + "man_farmer": "👨\u200d🌾", + "man_farmer_dark_skin_tone": "👨🏿\u200d🌾", + "man_farmer_light_skin_tone": "👨🏻\u200d🌾", + "man_farmer_medium-dark_skin_tone": "👨🏾\u200d🌾", + "man_farmer_medium-light_skin_tone": "👨🏼\u200d🌾", + "man_farmer_medium_skin_tone": "👨🏽\u200d🌾", + "man_firefighter": "👨\u200d🚒", + "man_firefighter_dark_skin_tone": "👨🏿\u200d🚒", + "man_firefighter_light_skin_tone": "👨🏻\u200d🚒", + "man_firefighter_medium-dark_skin_tone": "👨🏾\u200d🚒", + "man_firefighter_medium-light_skin_tone": "👨🏼\u200d🚒", + "man_firefighter_medium_skin_tone": "👨🏽\u200d🚒", + "man_frowning": "🙍\u200d♂️", + "man_frowning_dark_skin_tone": "🙍🏿\u200d♂️", + "man_frowning_light_skin_tone": "🙍🏻\u200d♂️", + "man_frowning_medium-dark_skin_tone": "🙍🏾\u200d♂️", + "man_frowning_medium-light_skin_tone": "🙍🏼\u200d♂️", + "man_frowning_medium_skin_tone": "🙍🏽\u200d♂️", + "man_genie": "🧞\u200d♂️", + "man_gesturing_no": "🙅\u200d♂️", + "man_gesturing_no_dark_skin_tone": "🙅🏿\u200d♂️", + "man_gesturing_no_light_skin_tone": "🙅🏻\u200d♂️", + "man_gesturing_no_medium-dark_skin_tone": "🙅🏾\u200d♂️", + "man_gesturing_no_medium-light_skin_tone": "🙅🏼\u200d♂️", + "man_gesturing_no_medium_skin_tone": "🙅🏽\u200d♂️", + "man_gesturing_ok": "🙆\u200d♂️", + "man_gesturing_ok_dark_skin_tone": "🙆🏿\u200d♂️", + "man_gesturing_ok_light_skin_tone": "🙆🏻\u200d♂️", + "man_gesturing_ok_medium-dark_skin_tone": "🙆🏾\u200d♂️", + "man_gesturing_ok_medium-light_skin_tone": "🙆🏼\u200d♂️", + "man_gesturing_ok_medium_skin_tone": "🙆🏽\u200d♂️", + "man_getting_haircut": "💇\u200d♂️", + "man_getting_haircut_dark_skin_tone": "💇🏿\u200d♂️", + "man_getting_haircut_light_skin_tone": "💇🏻\u200d♂️", + "man_getting_haircut_medium-dark_skin_tone": "💇🏾\u200d♂️", + "man_getting_haircut_medium-light_skin_tone": "💇🏼\u200d♂️", + "man_getting_haircut_medium_skin_tone": "💇🏽\u200d♂️", + "man_getting_massage": "💆\u200d♂️", + "man_getting_massage_dark_skin_tone": "💆🏿\u200d♂️", + "man_getting_massage_light_skin_tone": "💆🏻\u200d♂️", + "man_getting_massage_medium-dark_skin_tone": "💆🏾\u200d♂️", + "man_getting_massage_medium-light_skin_tone": "💆🏼\u200d♂️", + "man_getting_massage_medium_skin_tone": "💆🏽\u200d♂️", + "man_golfing": "🏌️\u200d♂️", + "man_golfing_dark_skin_tone": "🏌🏿\u200d♂️", + "man_golfing_light_skin_tone": "🏌🏻\u200d♂️", + "man_golfing_medium-dark_skin_tone": "🏌🏾\u200d♂️", + "man_golfing_medium-light_skin_tone": "🏌🏼\u200d♂️", + "man_golfing_medium_skin_tone": "🏌🏽\u200d♂️", + "man_guard": "💂\u200d♂️", + "man_guard_dark_skin_tone": "💂🏿\u200d♂️", + "man_guard_light_skin_tone": "💂🏻\u200d♂️", + "man_guard_medium-dark_skin_tone": "💂🏾\u200d♂️", + "man_guard_medium-light_skin_tone": "💂🏼\u200d♂️", + "man_guard_medium_skin_tone": "💂🏽\u200d♂️", + "man_health_worker": "👨\u200d⚕️", + "man_health_worker_dark_skin_tone": "👨🏿\u200d⚕️", + "man_health_worker_light_skin_tone": "👨🏻\u200d⚕️", + "man_health_worker_medium-dark_skin_tone": "👨🏾\u200d⚕️", + "man_health_worker_medium-light_skin_tone": "👨🏼\u200d⚕️", + "man_health_worker_medium_skin_tone": "👨🏽\u200d⚕️", + "man_in_lotus_position": "🧘\u200d♂️", + "man_in_lotus_position_dark_skin_tone": "🧘🏿\u200d♂️", + "man_in_lotus_position_light_skin_tone": "🧘🏻\u200d♂️", + "man_in_lotus_position_medium-dark_skin_tone": "🧘🏾\u200d♂️", + "man_in_lotus_position_medium-light_skin_tone": "🧘🏼\u200d♂️", + "man_in_lotus_position_medium_skin_tone": "🧘🏽\u200d♂️", + "man_in_manual_wheelchair": "👨\u200d🦽", + "man_in_motorized_wheelchair": "👨\u200d🦼", + "man_in_steamy_room": "🧖\u200d♂️", + "man_in_steamy_room_dark_skin_tone": "🧖🏿\u200d♂️", + "man_in_steamy_room_light_skin_tone": "🧖🏻\u200d♂️", + "man_in_steamy_room_medium-dark_skin_tone": "🧖🏾\u200d♂️", + "man_in_steamy_room_medium-light_skin_tone": "🧖🏼\u200d♂️", + "man_in_steamy_room_medium_skin_tone": "🧖🏽\u200d♂️", + "man_in_suit_levitating": "🕴", + "man_in_suit_levitating_dark_skin_tone": "🕴🏿", + "man_in_suit_levitating_light_skin_tone": "🕴🏻", + "man_in_suit_levitating_medium-dark_skin_tone": "🕴🏾", + "man_in_suit_levitating_medium-light_skin_tone": "🕴🏼", + "man_in_suit_levitating_medium_skin_tone": "🕴🏽", + "man_in_tuxedo": "🤵", + "man_in_tuxedo_dark_skin_tone": "🤵🏿", + "man_in_tuxedo_light_skin_tone": "🤵🏻", + "man_in_tuxedo_medium-dark_skin_tone": "🤵🏾", + "man_in_tuxedo_medium-light_skin_tone": "🤵🏼", + "man_in_tuxedo_medium_skin_tone": "🤵🏽", + "man_judge": "👨\u200d⚖️", + "man_judge_dark_skin_tone": "👨🏿\u200d⚖️", + "man_judge_light_skin_tone": "👨🏻\u200d⚖️", + "man_judge_medium-dark_skin_tone": "👨🏾\u200d⚖️", + "man_judge_medium-light_skin_tone": "👨🏼\u200d⚖️", + "man_judge_medium_skin_tone": "👨🏽\u200d⚖️", + "man_juggling": "🤹\u200d♂️", + "man_juggling_dark_skin_tone": "🤹🏿\u200d♂️", + "man_juggling_light_skin_tone": "🤹🏻\u200d♂️", + "man_juggling_medium-dark_skin_tone": "🤹🏾\u200d♂️", + "man_juggling_medium-light_skin_tone": "🤹🏼\u200d♂️", + "man_juggling_medium_skin_tone": "🤹🏽\u200d♂️", + "man_lifting_weights": "🏋️\u200d♂️", + "man_lifting_weights_dark_skin_tone": "🏋🏿\u200d♂️", + "man_lifting_weights_light_skin_tone": "🏋🏻\u200d♂️", + "man_lifting_weights_medium-dark_skin_tone": "🏋🏾\u200d♂️", + "man_lifting_weights_medium-light_skin_tone": "🏋🏼\u200d♂️", + "man_lifting_weights_medium_skin_tone": "🏋🏽\u200d♂️", + "man_light_skin_tone": "👨🏻", + "man_mage": "🧙\u200d♂️", + "man_mage_dark_skin_tone": "🧙🏿\u200d♂️", + "man_mage_light_skin_tone": "🧙🏻\u200d♂️", + "man_mage_medium-dark_skin_tone": "🧙🏾\u200d♂️", + "man_mage_medium-light_skin_tone": "🧙🏼\u200d♂️", + "man_mage_medium_skin_tone": "🧙🏽\u200d♂️", + "man_mechanic": "👨\u200d🔧", + "man_mechanic_dark_skin_tone": "👨🏿\u200d🔧", + "man_mechanic_light_skin_tone": "👨🏻\u200d🔧", + "man_mechanic_medium-dark_skin_tone": "👨🏾\u200d🔧", + "man_mechanic_medium-light_skin_tone": "👨🏼\u200d🔧", + "man_mechanic_medium_skin_tone": "👨🏽\u200d🔧", + "man_medium-dark_skin_tone": "👨🏾", + "man_medium-light_skin_tone": "👨🏼", + "man_medium_skin_tone": "👨🏽", + "man_mountain_biking": "🚵\u200d♂️", + "man_mountain_biking_dark_skin_tone": "🚵🏿\u200d♂️", + "man_mountain_biking_light_skin_tone": "🚵🏻\u200d♂️", + "man_mountain_biking_medium-dark_skin_tone": "🚵🏾\u200d♂️", + "man_mountain_biking_medium-light_skin_tone": "🚵🏼\u200d♂️", + "man_mountain_biking_medium_skin_tone": "🚵🏽\u200d♂️", + "man_office_worker": "👨\u200d💼", + "man_office_worker_dark_skin_tone": "👨🏿\u200d💼", + "man_office_worker_light_skin_tone": "👨🏻\u200d💼", + "man_office_worker_medium-dark_skin_tone": "👨🏾\u200d💼", + "man_office_worker_medium-light_skin_tone": "👨🏼\u200d💼", + "man_office_worker_medium_skin_tone": "👨🏽\u200d💼", + "man_pilot": "👨\u200d✈️", + "man_pilot_dark_skin_tone": "👨🏿\u200d✈️", + "man_pilot_light_skin_tone": "👨🏻\u200d✈️", + "man_pilot_medium-dark_skin_tone": "👨🏾\u200d✈️", + "man_pilot_medium-light_skin_tone": "👨🏼\u200d✈️", + "man_pilot_medium_skin_tone": "👨🏽\u200d✈️", + "man_playing_handball": "🤾\u200d♂️", + "man_playing_handball_dark_skin_tone": "🤾🏿\u200d♂️", + "man_playing_handball_light_skin_tone": "🤾🏻\u200d♂️", + "man_playing_handball_medium-dark_skin_tone": "🤾🏾\u200d♂️", + "man_playing_handball_medium-light_skin_tone": "🤾🏼\u200d♂️", + "man_playing_handball_medium_skin_tone": "🤾🏽\u200d♂️", + "man_playing_water_polo": "🤽\u200d♂️", + "man_playing_water_polo_dark_skin_tone": "🤽🏿\u200d♂️", + "man_playing_water_polo_light_skin_tone": "🤽🏻\u200d♂️", + "man_playing_water_polo_medium-dark_skin_tone": "🤽🏾\u200d♂️", + "man_playing_water_polo_medium-light_skin_tone": "🤽🏼\u200d♂️", + "man_playing_water_polo_medium_skin_tone": "🤽🏽\u200d♂️", + "man_police_officer": "👮\u200d♂️", + "man_police_officer_dark_skin_tone": "👮🏿\u200d♂️", + "man_police_officer_light_skin_tone": "👮🏻\u200d♂️", + "man_police_officer_medium-dark_skin_tone": "👮🏾\u200d♂️", + "man_police_officer_medium-light_skin_tone": "👮🏼\u200d♂️", + "man_police_officer_medium_skin_tone": "👮🏽\u200d♂️", + "man_pouting": "🙎\u200d♂️", + "man_pouting_dark_skin_tone": "🙎🏿\u200d♂️", + "man_pouting_light_skin_tone": "🙎🏻\u200d♂️", + "man_pouting_medium-dark_skin_tone": "🙎🏾\u200d♂️", + "man_pouting_medium-light_skin_tone": "🙎🏼\u200d♂️", + "man_pouting_medium_skin_tone": "🙎🏽\u200d♂️", + "man_raising_hand": "🙋\u200d♂️", + "man_raising_hand_dark_skin_tone": "🙋🏿\u200d♂️", + "man_raising_hand_light_skin_tone": "🙋🏻\u200d♂️", + "man_raising_hand_medium-dark_skin_tone": "🙋🏾\u200d♂️", + "man_raising_hand_medium-light_skin_tone": "🙋🏼\u200d♂️", + "man_raising_hand_medium_skin_tone": "🙋🏽\u200d♂️", + "man_rowing_boat": "🚣\u200d♂️", + "man_rowing_boat_dark_skin_tone": "🚣🏿\u200d♂️", + "man_rowing_boat_light_skin_tone": "🚣🏻\u200d♂️", + "man_rowing_boat_medium-dark_skin_tone": "🚣🏾\u200d♂️", + "man_rowing_boat_medium-light_skin_tone": "🚣🏼\u200d♂️", + "man_rowing_boat_medium_skin_tone": "🚣🏽\u200d♂️", + "man_running": "🏃\u200d♂️", + "man_running_dark_skin_tone": "🏃🏿\u200d♂️", + "man_running_light_skin_tone": "🏃🏻\u200d♂️", + "man_running_medium-dark_skin_tone": "🏃🏾\u200d♂️", + "man_running_medium-light_skin_tone": "🏃🏼\u200d♂️", + "man_running_medium_skin_tone": "🏃🏽\u200d♂️", + "man_scientist": "👨\u200d🔬", + "man_scientist_dark_skin_tone": "👨🏿\u200d🔬", + "man_scientist_light_skin_tone": "👨🏻\u200d🔬", + "man_scientist_medium-dark_skin_tone": "👨🏾\u200d🔬", + "man_scientist_medium-light_skin_tone": "👨🏼\u200d🔬", + "man_scientist_medium_skin_tone": "👨🏽\u200d🔬", + "man_shrugging": "🤷\u200d♂️", + "man_shrugging_dark_skin_tone": "🤷🏿\u200d♂️", + "man_shrugging_light_skin_tone": "🤷🏻\u200d♂️", + "man_shrugging_medium-dark_skin_tone": "🤷🏾\u200d♂️", + "man_shrugging_medium-light_skin_tone": "🤷🏼\u200d♂️", + "man_shrugging_medium_skin_tone": "🤷🏽\u200d♂️", + "man_singer": "👨\u200d🎤", + "man_singer_dark_skin_tone": "👨🏿\u200d🎤", + "man_singer_light_skin_tone": "👨🏻\u200d🎤", + "man_singer_medium-dark_skin_tone": "👨🏾\u200d🎤", + "man_singer_medium-light_skin_tone": "👨🏼\u200d🎤", + "man_singer_medium_skin_tone": "👨🏽\u200d🎤", + "man_student": "👨\u200d🎓", + "man_student_dark_skin_tone": "👨🏿\u200d🎓", + "man_student_light_skin_tone": "👨🏻\u200d🎓", + "man_student_medium-dark_skin_tone": "👨🏾\u200d🎓", + "man_student_medium-light_skin_tone": "👨🏼\u200d🎓", + "man_student_medium_skin_tone": "👨🏽\u200d🎓", + "man_surfing": "🏄\u200d♂️", + "man_surfing_dark_skin_tone": "🏄🏿\u200d♂️", + "man_surfing_light_skin_tone": "🏄🏻\u200d♂️", + "man_surfing_medium-dark_skin_tone": "🏄🏾\u200d♂️", + "man_surfing_medium-light_skin_tone": "🏄🏼\u200d♂️", + "man_surfing_medium_skin_tone": "🏄🏽\u200d♂️", + "man_swimming": "🏊\u200d♂️", + "man_swimming_dark_skin_tone": "🏊🏿\u200d♂️", + "man_swimming_light_skin_tone": "🏊🏻\u200d♂️", + "man_swimming_medium-dark_skin_tone": "🏊🏾\u200d♂️", + "man_swimming_medium-light_skin_tone": "🏊🏼\u200d♂️", + "man_swimming_medium_skin_tone": "🏊🏽\u200d♂️", + "man_teacher": "👨\u200d🏫", + "man_teacher_dark_skin_tone": "👨🏿\u200d🏫", + "man_teacher_light_skin_tone": "👨🏻\u200d🏫", + "man_teacher_medium-dark_skin_tone": "👨🏾\u200d🏫", + "man_teacher_medium-light_skin_tone": "👨🏼\u200d🏫", + "man_teacher_medium_skin_tone": "👨🏽\u200d🏫", + "man_technologist": "👨\u200d💻", + "man_technologist_dark_skin_tone": "👨🏿\u200d💻", + "man_technologist_light_skin_tone": "👨🏻\u200d💻", + "man_technologist_medium-dark_skin_tone": "👨🏾\u200d💻", + "man_technologist_medium-light_skin_tone": "👨🏼\u200d💻", + "man_technologist_medium_skin_tone": "👨🏽\u200d💻", + "man_tipping_hand": "💁\u200d♂️", + "man_tipping_hand_dark_skin_tone": "💁🏿\u200d♂️", + "man_tipping_hand_light_skin_tone": "💁🏻\u200d♂️", + "man_tipping_hand_medium-dark_skin_tone": "💁🏾\u200d♂️", + "man_tipping_hand_medium-light_skin_tone": "💁🏼\u200d♂️", + "man_tipping_hand_medium_skin_tone": "💁🏽\u200d♂️", + "man_vampire": "🧛\u200d♂️", + "man_vampire_dark_skin_tone": "🧛🏿\u200d♂️", + "man_vampire_light_skin_tone": "🧛🏻\u200d♂️", + "man_vampire_medium-dark_skin_tone": "🧛🏾\u200d♂️", + "man_vampire_medium-light_skin_tone": "🧛🏼\u200d♂️", + "man_vampire_medium_skin_tone": "🧛🏽\u200d♂️", + "man_walking": "🚶\u200d♂️", + "man_walking_dark_skin_tone": "🚶🏿\u200d♂️", + "man_walking_light_skin_tone": "🚶🏻\u200d♂️", + "man_walking_medium-dark_skin_tone": "🚶🏾\u200d♂️", + "man_walking_medium-light_skin_tone": "🚶🏼\u200d♂️", + "man_walking_medium_skin_tone": "🚶🏽\u200d♂️", + "man_wearing_turban": "👳\u200d♂️", + "man_wearing_turban_dark_skin_tone": "👳🏿\u200d♂️", + "man_wearing_turban_light_skin_tone": "👳🏻\u200d♂️", + "man_wearing_turban_medium-dark_skin_tone": "👳🏾\u200d♂️", + "man_wearing_turban_medium-light_skin_tone": "👳🏼\u200d♂️", + "man_wearing_turban_medium_skin_tone": "👳🏽\u200d♂️", + "man_with_probing_cane": "👨\u200d🦯", + "man_with_chinese_cap": "👲", + "man_with_chinese_cap_dark_skin_tone": "👲🏿", + "man_with_chinese_cap_light_skin_tone": "👲🏻", + "man_with_chinese_cap_medium-dark_skin_tone": "👲🏾", + "man_with_chinese_cap_medium-light_skin_tone": "👲🏼", + "man_with_chinese_cap_medium_skin_tone": "👲🏽", + "man_zombie": "🧟\u200d♂️", + "mango": "🥭", + "mantelpiece_clock": "🕰", + "manual_wheelchair": "🦽", + "man’s_shoe": "👞", + "map_of_japan": "🗾", + "maple_leaf": "🍁", + "martial_arts_uniform": "🥋", + "mate": "🧉", + "meat_on_bone": "🍖", + "mechanical_arm": "🦾", + "mechanical_leg": "🦿", + "medical_symbol": "⚕", + "megaphone": "📣", + "melon": "🍈", + "memo": "📝", + "men_with_bunny_ears": "👯\u200d♂️", + "men_wrestling": "🤼\u200d♂️", + "menorah": "🕎", + "men’s_room": "🚹", + "mermaid": "🧜\u200d♀️", + "mermaid_dark_skin_tone": "🧜🏿\u200d♀️", + "mermaid_light_skin_tone": "🧜🏻\u200d♀️", + "mermaid_medium-dark_skin_tone": "🧜🏾\u200d♀️", + "mermaid_medium-light_skin_tone": "🧜🏼\u200d♀️", + "mermaid_medium_skin_tone": "🧜🏽\u200d♀️", + "merman": "🧜\u200d♂️", + "merman_dark_skin_tone": "🧜🏿\u200d♂️", + "merman_light_skin_tone": "🧜🏻\u200d♂️", + "merman_medium-dark_skin_tone": "🧜🏾\u200d♂️", + "merman_medium-light_skin_tone": "🧜🏼\u200d♂️", + "merman_medium_skin_tone": "🧜🏽\u200d♂️", + "merperson": "🧜", + "merperson_dark_skin_tone": "🧜🏿", + "merperson_light_skin_tone": "🧜🏻", + "merperson_medium-dark_skin_tone": "🧜🏾", + "merperson_medium-light_skin_tone": "🧜🏼", + "merperson_medium_skin_tone": "🧜🏽", + "metro": "🚇", + "microbe": "🦠", + "microphone": "🎤", + "microscope": "🔬", + "middle_finger": "🖕", + "middle_finger_dark_skin_tone": "🖕🏿", + "middle_finger_light_skin_tone": "🖕🏻", + "middle_finger_medium-dark_skin_tone": "🖕🏾", + "middle_finger_medium-light_skin_tone": "🖕🏼", + "middle_finger_medium_skin_tone": "🖕🏽", + "military_medal": "🎖", + "milky_way": "🌌", + "minibus": "🚐", + "moai": "🗿", + "mobile_phone": "📱", + "mobile_phone_off": "📴", + "mobile_phone_with_arrow": "📲", + "money-mouth_face": "🤑", + "money_bag": "💰", + "money_with_wings": "💸", + "monkey": "🐒", + "monkey_face": "🐵", + "monorail": "🚝", + "moon_cake": "🥮", + "moon_viewing_ceremony": "🎑", + "mosque": "🕌", + "mosquito": "🦟", + "motor_boat": "🛥", + "motor_scooter": "🛵", + "motorcycle": "🏍", + "motorized_wheelchair": "🦼", + "motorway": "🛣", + "mount_fuji": "🗻", + "mountain": "⛰", + "mountain_cableway": "🚠", + "mountain_railway": "🚞", + "mouse": "🐭", + "mouse_face": "🐭", + "mouth": "👄", + "movie_camera": "🎥", + "mushroom": "🍄", + "musical_keyboard": "🎹", + "musical_note": "🎵", + "musical_notes": "🎶", + "musical_score": "🎼", + "muted_speaker": "🔇", + "nail_polish": "💅", + "nail_polish_dark_skin_tone": "💅🏿", + "nail_polish_light_skin_tone": "💅🏻", + "nail_polish_medium-dark_skin_tone": "💅🏾", + "nail_polish_medium-light_skin_tone": "💅🏼", + "nail_polish_medium_skin_tone": "💅🏽", + "name_badge": "📛", + "national_park": "🏞", + "nauseated_face": "🤢", + "nazar_amulet": "🧿", + "necktie": "👔", + "nerd_face": "🤓", + "neutral_face": "😐", + "new_moon": "🌑", + "new_moon_face": "🌚", + "newspaper": "📰", + "next_track_button": "⏭", + "night_with_stars": "🌃", + "nine-thirty": "🕤", + "nine_o’clock": "🕘", + "no_bicycles": "🚳", + "no_entry": "⛔", + "no_littering": "🚯", + "no_mobile_phones": "📵", + "no_one_under_eighteen": "🔞", + "no_pedestrians": "🚷", + "no_smoking": "🚭", + "non-potable_water": "🚱", + "nose": "👃", + "nose_dark_skin_tone": "👃🏿", + "nose_light_skin_tone": "👃🏻", + "nose_medium-dark_skin_tone": "👃🏾", + "nose_medium-light_skin_tone": "👃🏼", + "nose_medium_skin_tone": "👃🏽", + "notebook": "📓", + "notebook_with_decorative_cover": "📔", + "nut_and_bolt": "🔩", + "octopus": "🐙", + "oden": "🍢", + "office_building": "🏢", + "ogre": "👹", + "oil_drum": "🛢", + "old_key": "🗝", + "old_man": "👴", + "old_man_dark_skin_tone": "👴🏿", + "old_man_light_skin_tone": "👴🏻", + "old_man_medium-dark_skin_tone": "👴🏾", + "old_man_medium-light_skin_tone": "👴🏼", + "old_man_medium_skin_tone": "👴🏽", + "old_woman": "👵", + "old_woman_dark_skin_tone": "👵🏿", + "old_woman_light_skin_tone": "👵🏻", + "old_woman_medium-dark_skin_tone": "👵🏾", + "old_woman_medium-light_skin_tone": "👵🏼", + "old_woman_medium_skin_tone": "👵🏽", + "older_adult": "🧓", + "older_adult_dark_skin_tone": "🧓🏿", + "older_adult_light_skin_tone": "🧓🏻", + "older_adult_medium-dark_skin_tone": "🧓🏾", + "older_adult_medium-light_skin_tone": "🧓🏼", + "older_adult_medium_skin_tone": "🧓🏽", + "om": "🕉", + "oncoming_automobile": "🚘", + "oncoming_bus": "🚍", + "oncoming_fist": "👊", + "oncoming_fist_dark_skin_tone": "👊🏿", + "oncoming_fist_light_skin_tone": "👊🏻", + "oncoming_fist_medium-dark_skin_tone": "👊🏾", + "oncoming_fist_medium-light_skin_tone": "👊🏼", + "oncoming_fist_medium_skin_tone": "👊🏽", + "oncoming_police_car": "🚔", + "oncoming_taxi": "🚖", + "one-piece_swimsuit": "🩱", + "one-thirty": "🕜", + "one_o’clock": "🕐", + "onion": "🧅", + "open_book": "📖", + "open_file_folder": "📂", + "open_hands": "👐", + "open_hands_dark_skin_tone": "👐🏿", + "open_hands_light_skin_tone": "👐🏻", + "open_hands_medium-dark_skin_tone": "👐🏾", + "open_hands_medium-light_skin_tone": "👐🏼", + "open_hands_medium_skin_tone": "👐🏽", + "open_mailbox_with_lowered_flag": "📭", + "open_mailbox_with_raised_flag": "📬", + "optical_disk": "💿", + "orange_book": "📙", + "orange_circle": "🟠", + "orange_heart": "🧡", + "orange_square": "🟧", + "orangutan": "🦧", + "orthodox_cross": "☦", + "otter": "🦦", + "outbox_tray": "📤", + "owl": "🦉", + "ox": "🐂", + "oyster": "🦪", + "package": "📦", + "page_facing_up": "📄", + "page_with_curl": "📃", + "pager": "📟", + "paintbrush": "🖌", + "palm_tree": "🌴", + "palms_up_together": "🤲", + "palms_up_together_dark_skin_tone": "🤲🏿", + "palms_up_together_light_skin_tone": "🤲🏻", + "palms_up_together_medium-dark_skin_tone": "🤲🏾", + "palms_up_together_medium-light_skin_tone": "🤲🏼", + "palms_up_together_medium_skin_tone": "🤲🏽", + "pancakes": "🥞", + "panda_face": "🐼", + "paperclip": "📎", + "parrot": "🦜", + "part_alternation_mark": "〽", + "party_popper": "🎉", + "partying_face": "🥳", + "passenger_ship": "🛳", + "passport_control": "🛂", + "pause_button": "⏸", + "paw_prints": "🐾", + "peace_symbol": "☮", + "peach": "🍑", + "peacock": "🦚", + "peanuts": "🥜", + "pear": "🍐", + "pen": "🖊", + "pencil": "📝", + "penguin": "🐧", + "pensive_face": "😔", + "people_holding_hands": "🧑\u200d🤝\u200d🧑", + "people_with_bunny_ears": "👯", + "people_wrestling": "🤼", + "performing_arts": "🎭", + "persevering_face": "😣", + "person_biking": "🚴", + "person_biking_dark_skin_tone": "🚴🏿", + "person_biking_light_skin_tone": "🚴🏻", + "person_biking_medium-dark_skin_tone": "🚴🏾", + "person_biking_medium-light_skin_tone": "🚴🏼", + "person_biking_medium_skin_tone": "🚴🏽", + "person_bouncing_ball": "⛹", + "person_bouncing_ball_dark_skin_tone": "⛹🏿", + "person_bouncing_ball_light_skin_tone": "⛹🏻", + "person_bouncing_ball_medium-dark_skin_tone": "⛹🏾", + "person_bouncing_ball_medium-light_skin_tone": "⛹🏼", + "person_bouncing_ball_medium_skin_tone": "⛹🏽", + "person_bowing": "🙇", + "person_bowing_dark_skin_tone": "🙇🏿", + "person_bowing_light_skin_tone": "🙇🏻", + "person_bowing_medium-dark_skin_tone": "🙇🏾", + "person_bowing_medium-light_skin_tone": "🙇🏼", + "person_bowing_medium_skin_tone": "🙇🏽", + "person_cartwheeling": "🤸", + "person_cartwheeling_dark_skin_tone": "🤸🏿", + "person_cartwheeling_light_skin_tone": "🤸🏻", + "person_cartwheeling_medium-dark_skin_tone": "🤸🏾", + "person_cartwheeling_medium-light_skin_tone": "🤸🏼", + "person_cartwheeling_medium_skin_tone": "🤸🏽", + "person_climbing": "🧗", + "person_climbing_dark_skin_tone": "🧗🏿", + "person_climbing_light_skin_tone": "🧗🏻", + "person_climbing_medium-dark_skin_tone": "🧗🏾", + "person_climbing_medium-light_skin_tone": "🧗🏼", + "person_climbing_medium_skin_tone": "🧗🏽", + "person_facepalming": "🤦", + "person_facepalming_dark_skin_tone": "🤦🏿", + "person_facepalming_light_skin_tone": "🤦🏻", + "person_facepalming_medium-dark_skin_tone": "🤦🏾", + "person_facepalming_medium-light_skin_tone": "🤦🏼", + "person_facepalming_medium_skin_tone": "🤦🏽", + "person_fencing": "🤺", + "person_frowning": "🙍", + "person_frowning_dark_skin_tone": "🙍🏿", + "person_frowning_light_skin_tone": "🙍🏻", + "person_frowning_medium-dark_skin_tone": "🙍🏾", + "person_frowning_medium-light_skin_tone": "🙍🏼", + "person_frowning_medium_skin_tone": "🙍🏽", + "person_gesturing_no": "🙅", + "person_gesturing_no_dark_skin_tone": "🙅🏿", + "person_gesturing_no_light_skin_tone": "🙅🏻", + "person_gesturing_no_medium-dark_skin_tone": "🙅🏾", + "person_gesturing_no_medium-light_skin_tone": "🙅🏼", + "person_gesturing_no_medium_skin_tone": "🙅🏽", + "person_gesturing_ok": "🙆", + "person_gesturing_ok_dark_skin_tone": "🙆🏿", + "person_gesturing_ok_light_skin_tone": "🙆🏻", + "person_gesturing_ok_medium-dark_skin_tone": "🙆🏾", + "person_gesturing_ok_medium-light_skin_tone": "🙆🏼", + "person_gesturing_ok_medium_skin_tone": "🙆🏽", + "person_getting_haircut": "💇", + "person_getting_haircut_dark_skin_tone": "💇🏿", + "person_getting_haircut_light_skin_tone": "💇🏻", + "person_getting_haircut_medium-dark_skin_tone": "💇🏾", + "person_getting_haircut_medium-light_skin_tone": "💇🏼", + "person_getting_haircut_medium_skin_tone": "💇🏽", + "person_getting_massage": "💆", + "person_getting_massage_dark_skin_tone": "💆🏿", + "person_getting_massage_light_skin_tone": "💆🏻", + "person_getting_massage_medium-dark_skin_tone": "💆🏾", + "person_getting_massage_medium-light_skin_tone": "💆🏼", + "person_getting_massage_medium_skin_tone": "💆🏽", + "person_golfing": "🏌", + "person_golfing_dark_skin_tone": "🏌🏿", + "person_golfing_light_skin_tone": "🏌🏻", + "person_golfing_medium-dark_skin_tone": "🏌🏾", + "person_golfing_medium-light_skin_tone": "🏌🏼", + "person_golfing_medium_skin_tone": "🏌🏽", + "person_in_bed": "🛌", + "person_in_bed_dark_skin_tone": "🛌🏿", + "person_in_bed_light_skin_tone": "🛌🏻", + "person_in_bed_medium-dark_skin_tone": "🛌🏾", + "person_in_bed_medium-light_skin_tone": "🛌🏼", + "person_in_bed_medium_skin_tone": "🛌🏽", + "person_in_lotus_position": "🧘", + "person_in_lotus_position_dark_skin_tone": "🧘🏿", + "person_in_lotus_position_light_skin_tone": "🧘🏻", + "person_in_lotus_position_medium-dark_skin_tone": "🧘🏾", + "person_in_lotus_position_medium-light_skin_tone": "🧘🏼", + "person_in_lotus_position_medium_skin_tone": "🧘🏽", + "person_in_steamy_room": "🧖", + "person_in_steamy_room_dark_skin_tone": "🧖🏿", + "person_in_steamy_room_light_skin_tone": "🧖🏻", + "person_in_steamy_room_medium-dark_skin_tone": "🧖🏾", + "person_in_steamy_room_medium-light_skin_tone": "🧖🏼", + "person_in_steamy_room_medium_skin_tone": "🧖🏽", + "person_juggling": "🤹", + "person_juggling_dark_skin_tone": "🤹🏿", + "person_juggling_light_skin_tone": "🤹🏻", + "person_juggling_medium-dark_skin_tone": "🤹🏾", + "person_juggling_medium-light_skin_tone": "🤹🏼", + "person_juggling_medium_skin_tone": "🤹🏽", + "person_kneeling": "🧎", + "person_lifting_weights": "🏋", + "person_lifting_weights_dark_skin_tone": "🏋🏿", + "person_lifting_weights_light_skin_tone": "🏋🏻", + "person_lifting_weights_medium-dark_skin_tone": "🏋🏾", + "person_lifting_weights_medium-light_skin_tone": "🏋🏼", + "person_lifting_weights_medium_skin_tone": "🏋🏽", + "person_mountain_biking": "🚵", + "person_mountain_biking_dark_skin_tone": "🚵🏿", + "person_mountain_biking_light_skin_tone": "🚵🏻", + "person_mountain_biking_medium-dark_skin_tone": "🚵🏾", + "person_mountain_biking_medium-light_skin_tone": "🚵🏼", + "person_mountain_biking_medium_skin_tone": "🚵🏽", + "person_playing_handball": "🤾", + "person_playing_handball_dark_skin_tone": "🤾🏿", + "person_playing_handball_light_skin_tone": "🤾🏻", + "person_playing_handball_medium-dark_skin_tone": "🤾🏾", + "person_playing_handball_medium-light_skin_tone": "🤾🏼", + "person_playing_handball_medium_skin_tone": "🤾🏽", + "person_playing_water_polo": "🤽", + "person_playing_water_polo_dark_skin_tone": "🤽🏿", + "person_playing_water_polo_light_skin_tone": "🤽🏻", + "person_playing_water_polo_medium-dark_skin_tone": "🤽🏾", + "person_playing_water_polo_medium-light_skin_tone": "🤽🏼", + "person_playing_water_polo_medium_skin_tone": "🤽🏽", + "person_pouting": "🙎", + "person_pouting_dark_skin_tone": "🙎🏿", + "person_pouting_light_skin_tone": "🙎🏻", + "person_pouting_medium-dark_skin_tone": "🙎🏾", + "person_pouting_medium-light_skin_tone": "🙎🏼", + "person_pouting_medium_skin_tone": "🙎🏽", + "person_raising_hand": "🙋", + "person_raising_hand_dark_skin_tone": "🙋🏿", + "person_raising_hand_light_skin_tone": "🙋🏻", + "person_raising_hand_medium-dark_skin_tone": "🙋🏾", + "person_raising_hand_medium-light_skin_tone": "🙋🏼", + "person_raising_hand_medium_skin_tone": "🙋🏽", + "person_rowing_boat": "🚣", + "person_rowing_boat_dark_skin_tone": "🚣🏿", + "person_rowing_boat_light_skin_tone": "🚣🏻", + "person_rowing_boat_medium-dark_skin_tone": "🚣🏾", + "person_rowing_boat_medium-light_skin_tone": "🚣🏼", + "person_rowing_boat_medium_skin_tone": "🚣🏽", + "person_running": "🏃", + "person_running_dark_skin_tone": "🏃🏿", + "person_running_light_skin_tone": "🏃🏻", + "person_running_medium-dark_skin_tone": "🏃🏾", + "person_running_medium-light_skin_tone": "🏃🏼", + "person_running_medium_skin_tone": "🏃🏽", + "person_shrugging": "🤷", + "person_shrugging_dark_skin_tone": "🤷🏿", + "person_shrugging_light_skin_tone": "🤷🏻", + "person_shrugging_medium-dark_skin_tone": "🤷🏾", + "person_shrugging_medium-light_skin_tone": "🤷🏼", + "person_shrugging_medium_skin_tone": "🤷🏽", + "person_standing": "🧍", + "person_surfing": "🏄", + "person_surfing_dark_skin_tone": "🏄🏿", + "person_surfing_light_skin_tone": "🏄🏻", + "person_surfing_medium-dark_skin_tone": "🏄🏾", + "person_surfing_medium-light_skin_tone": "🏄🏼", + "person_surfing_medium_skin_tone": "🏄🏽", + "person_swimming": "🏊", + "person_swimming_dark_skin_tone": "🏊🏿", + "person_swimming_light_skin_tone": "🏊🏻", + "person_swimming_medium-dark_skin_tone": "🏊🏾", + "person_swimming_medium-light_skin_tone": "🏊🏼", + "person_swimming_medium_skin_tone": "🏊🏽", + "person_taking_bath": "🛀", + "person_taking_bath_dark_skin_tone": "🛀🏿", + "person_taking_bath_light_skin_tone": "🛀🏻", + "person_taking_bath_medium-dark_skin_tone": "🛀🏾", + "person_taking_bath_medium-light_skin_tone": "🛀🏼", + "person_taking_bath_medium_skin_tone": "🛀🏽", + "person_tipping_hand": "💁", + "person_tipping_hand_dark_skin_tone": "💁🏿", + "person_tipping_hand_light_skin_tone": "💁🏻", + "person_tipping_hand_medium-dark_skin_tone": "💁🏾", + "person_tipping_hand_medium-light_skin_tone": "💁🏼", + "person_tipping_hand_medium_skin_tone": "💁🏽", + "person_walking": "🚶", + "person_walking_dark_skin_tone": "🚶🏿", + "person_walking_light_skin_tone": "🚶🏻", + "person_walking_medium-dark_skin_tone": "🚶🏾", + "person_walking_medium-light_skin_tone": "🚶🏼", + "person_walking_medium_skin_tone": "🚶🏽", + "person_wearing_turban": "👳", + "person_wearing_turban_dark_skin_tone": "👳🏿", + "person_wearing_turban_light_skin_tone": "👳🏻", + "person_wearing_turban_medium-dark_skin_tone": "👳🏾", + "person_wearing_turban_medium-light_skin_tone": "👳🏼", + "person_wearing_turban_medium_skin_tone": "👳🏽", + "petri_dish": "🧫", + "pick": "⛏", + "pie": "🥧", + "pig": "🐷", + "pig_face": "🐷", + "pig_nose": "🐽", + "pile_of_poo": "💩", + "pill": "💊", + "pinching_hand": "🤏", + "pine_decoration": "🎍", + "pineapple": "🍍", + "ping_pong": "🏓", + "pirate_flag": "🏴\u200d☠️", + "pistol": "🔫", + "pizza": "🍕", + "place_of_worship": "🛐", + "play_button": "▶", + "play_or_pause_button": "⏯", + "pleading_face": "🥺", + "police_car": "🚓", + "police_car_light": "🚨", + "police_officer": "👮", + "police_officer_dark_skin_tone": "👮🏿", + "police_officer_light_skin_tone": "👮🏻", + "police_officer_medium-dark_skin_tone": "👮🏾", + "police_officer_medium-light_skin_tone": "👮🏼", + "police_officer_medium_skin_tone": "👮🏽", + "poodle": "🐩", + "pool_8_ball": "🎱", + "popcorn": "🍿", + "post_office": "🏣", + "postal_horn": "📯", + "postbox": "📮", + "pot_of_food": "🍲", + "potable_water": "🚰", + "potato": "🥔", + "poultry_leg": "🍗", + "pound_banknote": "💷", + "pouting_cat_face": "😾", + "pouting_face": "😡", + "prayer_beads": "📿", + "pregnant_woman": "🤰", + "pregnant_woman_dark_skin_tone": "🤰🏿", + "pregnant_woman_light_skin_tone": "🤰🏻", + "pregnant_woman_medium-dark_skin_tone": "🤰🏾", + "pregnant_woman_medium-light_skin_tone": "🤰🏼", + "pregnant_woman_medium_skin_tone": "🤰🏽", + "pretzel": "🥨", + "probing_cane": "🦯", + "prince": "🤴", + "prince_dark_skin_tone": "🤴🏿", + "prince_light_skin_tone": "🤴🏻", + "prince_medium-dark_skin_tone": "🤴🏾", + "prince_medium-light_skin_tone": "🤴🏼", + "prince_medium_skin_tone": "🤴🏽", + "princess": "👸", + "princess_dark_skin_tone": "👸🏿", + "princess_light_skin_tone": "👸🏻", + "princess_medium-dark_skin_tone": "👸🏾", + "princess_medium-light_skin_tone": "👸🏼", + "princess_medium_skin_tone": "👸🏽", + "printer": "🖨", + "prohibited": "🚫", + "purple_circle": "🟣", + "purple_heart": "💜", + "purple_square": "🟪", + "purse": "👛", + "pushpin": "📌", + "question_mark": "❓", + "rabbit": "🐰", + "rabbit_face": "🐰", + "raccoon": "🦝", + "racing_car": "🏎", + "radio": "📻", + "radio_button": "🔘", + "radioactive": "☢", + "railway_car": "🚃", + "railway_track": "🛤", + "rainbow": "🌈", + "rainbow_flag": "🏳️\u200d🌈", + "raised_back_of_hand": "🤚", + "raised_back_of_hand_dark_skin_tone": "🤚🏿", + "raised_back_of_hand_light_skin_tone": "🤚🏻", + "raised_back_of_hand_medium-dark_skin_tone": "🤚🏾", + "raised_back_of_hand_medium-light_skin_tone": "🤚🏼", + "raised_back_of_hand_medium_skin_tone": "🤚🏽", + "raised_fist": "✊", + "raised_fist_dark_skin_tone": "✊🏿", + "raised_fist_light_skin_tone": "✊🏻", + "raised_fist_medium-dark_skin_tone": "✊🏾", + "raised_fist_medium-light_skin_tone": "✊🏼", + "raised_fist_medium_skin_tone": "✊🏽", + "raised_hand": "✋", + "raised_hand_dark_skin_tone": "✋🏿", + "raised_hand_light_skin_tone": "✋🏻", + "raised_hand_medium-dark_skin_tone": "✋🏾", + "raised_hand_medium-light_skin_tone": "✋🏼", + "raised_hand_medium_skin_tone": "✋🏽", + "raising_hands": "🙌", + "raising_hands_dark_skin_tone": "🙌🏿", + "raising_hands_light_skin_tone": "🙌🏻", + "raising_hands_medium-dark_skin_tone": "🙌🏾", + "raising_hands_medium-light_skin_tone": "🙌🏼", + "raising_hands_medium_skin_tone": "🙌🏽", + "ram": "🐏", + "rat": "🐀", + "razor": "🪒", + "ringed_planet": "🪐", + "receipt": "🧾", + "record_button": "⏺", + "recycling_symbol": "♻", + "red_apple": "🍎", + "red_circle": "🔴", + "red_envelope": "🧧", + "red_hair": "🦰", + "red-haired_man": "👨\u200d🦰", + "red-haired_woman": "👩\u200d🦰", + "red_heart": "❤", + "red_paper_lantern": "🏮", + "red_square": "🟥", + "red_triangle_pointed_down": "🔻", + "red_triangle_pointed_up": "🔺", + "registered": "®", + "relieved_face": "😌", + "reminder_ribbon": "🎗", + "repeat_button": "🔁", + "repeat_single_button": "🔂", + "rescue_worker’s_helmet": "⛑", + "restroom": "🚻", + "reverse_button": "◀", + "revolving_hearts": "💞", + "rhinoceros": "🦏", + "ribbon": "🎀", + "rice_ball": "🍙", + "rice_cracker": "🍘", + "right-facing_fist": "🤜", + "right-facing_fist_dark_skin_tone": "🤜🏿", + "right-facing_fist_light_skin_tone": "🤜🏻", + "right-facing_fist_medium-dark_skin_tone": "🤜🏾", + "right-facing_fist_medium-light_skin_tone": "🤜🏼", + "right-facing_fist_medium_skin_tone": "🤜🏽", + "right_anger_bubble": "🗯", + "right_arrow": "➡", + "right_arrow_curving_down": "⤵", + "right_arrow_curving_left": "↩", + "right_arrow_curving_up": "⤴", + "ring": "💍", + "roasted_sweet_potato": "🍠", + "robot_face": "🤖", + "rocket": "🚀", + "roll_of_paper": "🧻", + "rolled-up_newspaper": "🗞", + "roller_coaster": "🎢", + "rolling_on_the_floor_laughing": "🤣", + "rooster": "🐓", + "rose": "🌹", + "rosette": "🏵", + "round_pushpin": "📍", + "rugby_football": "🏉", + "running_shirt": "🎽", + "running_shoe": "👟", + "sad_but_relieved_face": "😥", + "safety_pin": "🧷", + "safety_vest": "🦺", + "salt": "🧂", + "sailboat": "⛵", + "sake": "🍶", + "sandwich": "🥪", + "sari": "🥻", + "satellite": "📡", + "satellite_antenna": "📡", + "sauropod": "🦕", + "saxophone": "🎷", + "scarf": "🧣", + "school": "🏫", + "school_backpack": "🎒", + "scissors": "✂", + "scorpion": "🦂", + "scroll": "📜", + "seat": "💺", + "see-no-evil_monkey": "🙈", + "seedling": "🌱", + "selfie": "🤳", + "selfie_dark_skin_tone": "🤳🏿", + "selfie_light_skin_tone": "🤳🏻", + "selfie_medium-dark_skin_tone": "🤳🏾", + "selfie_medium-light_skin_tone": "🤳🏼", + "selfie_medium_skin_tone": "🤳🏽", + "service_dog": "🐕\u200d🦺", + "seven-thirty": "🕢", + "seven_o’clock": "🕖", + "shallow_pan_of_food": "🥘", + "shamrock": "☘", + "shark": "🦈", + "shaved_ice": "🍧", + "sheaf_of_rice": "🌾", + "shield": "🛡", + "shinto_shrine": "⛩", + "ship": "🚢", + "shooting_star": "🌠", + "shopping_bags": "🛍", + "shopping_cart": "🛒", + "shortcake": "🍰", + "shorts": "🩳", + "shower": "🚿", + "shrimp": "🦐", + "shuffle_tracks_button": "🔀", + "shushing_face": "🤫", + "sign_of_the_horns": "🤘", + "sign_of_the_horns_dark_skin_tone": "🤘🏿", + "sign_of_the_horns_light_skin_tone": "🤘🏻", + "sign_of_the_horns_medium-dark_skin_tone": "🤘🏾", + "sign_of_the_horns_medium-light_skin_tone": "🤘🏼", + "sign_of_the_horns_medium_skin_tone": "🤘🏽", + "six-thirty": "🕡", + "six_o’clock": "🕕", + "skateboard": "🛹", + "skier": "⛷", + "skis": "🎿", + "skull": "💀", + "skull_and_crossbones": "☠", + "skunk": "🦨", + "sled": "🛷", + "sleeping_face": "😴", + "sleepy_face": "😪", + "slightly_frowning_face": "🙁", + "slightly_smiling_face": "🙂", + "slot_machine": "🎰", + "sloth": "🦥", + "small_airplane": "🛩", + "small_blue_diamond": "🔹", + "small_orange_diamond": "🔸", + "smiling_cat_face_with_heart-eyes": "😻", + "smiling_face": "☺", + "smiling_face_with_halo": "😇", + "smiling_face_with_3_hearts": "🥰", + "smiling_face_with_heart-eyes": "😍", + "smiling_face_with_horns": "😈", + "smiling_face_with_smiling_eyes": "😊", + "smiling_face_with_sunglasses": "😎", + "smirking_face": "😏", + "snail": "🐌", + "snake": "🐍", + "sneezing_face": "🤧", + "snow-capped_mountain": "🏔", + "snowboarder": "🏂", + "snowboarder_dark_skin_tone": "🏂🏿", + "snowboarder_light_skin_tone": "🏂🏻", + "snowboarder_medium-dark_skin_tone": "🏂🏾", + "snowboarder_medium-light_skin_tone": "🏂🏼", + "snowboarder_medium_skin_tone": "🏂🏽", + "snowflake": "❄", + "snowman": "☃", + "snowman_without_snow": "⛄", + "soap": "🧼", + "soccer_ball": "⚽", + "socks": "🧦", + "softball": "🥎", + "soft_ice_cream": "🍦", + "spade_suit": "♠", + "spaghetti": "🍝", + "sparkle": "❇", + "sparkler": "🎇", + "sparkles": "✨", + "sparkling_heart": "💖", + "speak-no-evil_monkey": "🙊", + "speaker_high_volume": "🔊", + "speaker_low_volume": "🔈", + "speaker_medium_volume": "🔉", + "speaking_head": "🗣", + "speech_balloon": "💬", + "speedboat": "🚤", + "spider": "🕷", + "spider_web": "🕸", + "spiral_calendar": "🗓", + "spiral_notepad": "🗒", + "spiral_shell": "🐚", + "spoon": "🥄", + "sponge": "🧽", + "sport_utility_vehicle": "🚙", + "sports_medal": "🏅", + "spouting_whale": "🐳", + "squid": "🦑", + "squinting_face_with_tongue": "😝", + "stadium": "🏟", + "star-struck": "🤩", + "star_and_crescent": "☪", + "star_of_david": "✡", + "station": "🚉", + "steaming_bowl": "🍜", + "stethoscope": "🩺", + "stop_button": "⏹", + "stop_sign": "🛑", + "stopwatch": "⏱", + "straight_ruler": "📏", + "strawberry": "🍓", + "studio_microphone": "🎙", + "stuffed_flatbread": "🥙", + "sun": "☀", + "sun_behind_cloud": "⛅", + "sun_behind_large_cloud": "🌥", + "sun_behind_rain_cloud": "🌦", + "sun_behind_small_cloud": "🌤", + "sun_with_face": "🌞", + "sunflower": "🌻", + "sunglasses": "😎", + "sunrise": "🌅", + "sunrise_over_mountains": "🌄", + "sunset": "🌇", + "superhero": "🦸", + "supervillain": "🦹", + "sushi": "🍣", + "suspension_railway": "🚟", + "swan": "🦢", + "sweat_droplets": "💦", + "synagogue": "🕍", + "syringe": "💉", + "t-shirt": "👕", + "taco": "🌮", + "takeout_box": "🥡", + "tanabata_tree": "🎋", + "tangerine": "🍊", + "taxi": "🚕", + "teacup_without_handle": "🍵", + "tear-off_calendar": "📆", + "teddy_bear": "🧸", + "telephone": "☎", + "telephone_receiver": "📞", + "telescope": "🔭", + "television": "📺", + "ten-thirty": "🕥", + "ten_o’clock": "🕙", + "tennis": "🎾", + "tent": "⛺", + "test_tube": "🧪", + "thermometer": "🌡", + "thinking_face": "🤔", + "thought_balloon": "💭", + "thread": "🧵", + "three-thirty": "🕞", + "three_o’clock": "🕒", + "thumbs_down": "👎", + "thumbs_down_dark_skin_tone": "👎🏿", + "thumbs_down_light_skin_tone": "👎🏻", + "thumbs_down_medium-dark_skin_tone": "👎🏾", + "thumbs_down_medium-light_skin_tone": "👎🏼", + "thumbs_down_medium_skin_tone": "👎🏽", + "thumbs_up": "👍", + "thumbs_up_dark_skin_tone": "👍🏿", + "thumbs_up_light_skin_tone": "👍🏻", + "thumbs_up_medium-dark_skin_tone": "👍🏾", + "thumbs_up_medium-light_skin_tone": "👍🏼", + "thumbs_up_medium_skin_tone": "👍🏽", + "ticket": "🎫", + "tiger": "🐯", + "tiger_face": "🐯", + "timer_clock": "⏲", + "tired_face": "😫", + "toolbox": "🧰", + "toilet": "🚽", + "tomato": "🍅", + "tongue": "👅", + "tooth": "🦷", + "top_hat": "🎩", + "tornado": "🌪", + "trackball": "🖲", + "tractor": "🚜", + "trade_mark": "™", + "train": "🚋", + "tram": "🚊", + "tram_car": "🚋", + "triangular_flag": "🚩", + "triangular_ruler": "📐", + "trident_emblem": "🔱", + "trolleybus": "🚎", + "trophy": "🏆", + "tropical_drink": "🍹", + "tropical_fish": "🐠", + "trumpet": "🎺", + "tulip": "🌷", + "tumbler_glass": "🥃", + "turtle": "🐢", + "twelve-thirty": "🕧", + "twelve_o’clock": "🕛", + "two-hump_camel": "🐫", + "two-thirty": "🕝", + "two_hearts": "💕", + "two_men_holding_hands": "👬", + "two_o’clock": "🕑", + "two_women_holding_hands": "👭", + "umbrella": "☂", + "umbrella_on_ground": "⛱", + "umbrella_with_rain_drops": "☔", + "unamused_face": "😒", + "unicorn_face": "🦄", + "unlocked": "🔓", + "up-down_arrow": "↕", + "up-left_arrow": "↖", + "up-right_arrow": "↗", + "up_arrow": "⬆", + "upside-down_face": "🙃", + "upwards_button": "🔼", + "vampire": "🧛", + "vampire_dark_skin_tone": "🧛🏿", + "vampire_light_skin_tone": "🧛🏻", + "vampire_medium-dark_skin_tone": "🧛🏾", + "vampire_medium-light_skin_tone": "🧛🏼", + "vampire_medium_skin_tone": "🧛🏽", + "vertical_traffic_light": "🚦", + "vibration_mode": "📳", + "victory_hand": "✌", + "victory_hand_dark_skin_tone": "✌🏿", + "victory_hand_light_skin_tone": "✌🏻", + "victory_hand_medium-dark_skin_tone": "✌🏾", + "victory_hand_medium-light_skin_tone": "✌🏼", + "victory_hand_medium_skin_tone": "✌🏽", + "video_camera": "📹", + "video_game": "🎮", + "videocassette": "📼", + "violin": "🎻", + "volcano": "🌋", + "volleyball": "🏐", + "vulcan_salute": "🖖", + "vulcan_salute_dark_skin_tone": "🖖🏿", + "vulcan_salute_light_skin_tone": "🖖🏻", + "vulcan_salute_medium-dark_skin_tone": "🖖🏾", + "vulcan_salute_medium-light_skin_tone": "🖖🏼", + "vulcan_salute_medium_skin_tone": "🖖🏽", + "waffle": "🧇", + "waning_crescent_moon": "🌘", + "waning_gibbous_moon": "🌖", + "warning": "⚠", + "wastebasket": "🗑", + "watch": "⌚", + "water_buffalo": "🐃", + "water_closet": "🚾", + "water_wave": "🌊", + "watermelon": "🍉", + "waving_hand": "👋", + "waving_hand_dark_skin_tone": "👋🏿", + "waving_hand_light_skin_tone": "👋🏻", + "waving_hand_medium-dark_skin_tone": "👋🏾", + "waving_hand_medium-light_skin_tone": "👋🏼", + "waving_hand_medium_skin_tone": "👋🏽", + "wavy_dash": "〰", + "waxing_crescent_moon": "🌒", + "waxing_gibbous_moon": "🌔", + "weary_cat_face": "🙀", + "weary_face": "😩", + "wedding": "💒", + "whale": "🐳", + "wheel_of_dharma": "☸", + "wheelchair_symbol": "♿", + "white_circle": "⚪", + "white_exclamation_mark": "❕", + "white_flag": "🏳", + "white_flower": "💮", + "white_hair": "🦳", + "white-haired_man": "👨\u200d🦳", + "white-haired_woman": "👩\u200d🦳", + "white_heart": "🤍", + "white_heavy_check_mark": "✅", + "white_large_square": "⬜", + "white_medium-small_square": "◽", + "white_medium_square": "◻", + "white_medium_star": "⭐", + "white_question_mark": "❔", + "white_small_square": "▫", + "white_square_button": "🔳", + "wilted_flower": "🥀", + "wind_chime": "🎐", + "wind_face": "🌬", + "wine_glass": "🍷", + "winking_face": "😉", + "winking_face_with_tongue": "😜", + "wolf_face": "🐺", + "woman": "👩", + "woman_artist": "👩\u200d🎨", + "woman_artist_dark_skin_tone": "👩🏿\u200d🎨", + "woman_artist_light_skin_tone": "👩🏻\u200d🎨", + "woman_artist_medium-dark_skin_tone": "👩🏾\u200d🎨", + "woman_artist_medium-light_skin_tone": "👩🏼\u200d🎨", + "woman_artist_medium_skin_tone": "👩🏽\u200d🎨", + "woman_astronaut": "👩\u200d🚀", + "woman_astronaut_dark_skin_tone": "👩🏿\u200d🚀", + "woman_astronaut_light_skin_tone": "👩🏻\u200d🚀", + "woman_astronaut_medium-dark_skin_tone": "👩🏾\u200d🚀", + "woman_astronaut_medium-light_skin_tone": "👩🏼\u200d🚀", + "woman_astronaut_medium_skin_tone": "👩🏽\u200d🚀", + "woman_biking": "🚴\u200d♀️", + "woman_biking_dark_skin_tone": "🚴🏿\u200d♀️", + "woman_biking_light_skin_tone": "🚴🏻\u200d♀️", + "woman_biking_medium-dark_skin_tone": "🚴🏾\u200d♀️", + "woman_biking_medium-light_skin_tone": "🚴🏼\u200d♀️", + "woman_biking_medium_skin_tone": "🚴🏽\u200d♀️", + "woman_bouncing_ball": "⛹️\u200d♀️", + "woman_bouncing_ball_dark_skin_tone": "⛹🏿\u200d♀️", + "woman_bouncing_ball_light_skin_tone": "⛹🏻\u200d♀️", + "woman_bouncing_ball_medium-dark_skin_tone": "⛹🏾\u200d♀️", + "woman_bouncing_ball_medium-light_skin_tone": "⛹🏼\u200d♀️", + "woman_bouncing_ball_medium_skin_tone": "⛹🏽\u200d♀️", + "woman_bowing": "🙇\u200d♀️", + "woman_bowing_dark_skin_tone": "🙇🏿\u200d♀️", + "woman_bowing_light_skin_tone": "🙇🏻\u200d♀️", + "woman_bowing_medium-dark_skin_tone": "🙇🏾\u200d♀️", + "woman_bowing_medium-light_skin_tone": "🙇🏼\u200d♀️", + "woman_bowing_medium_skin_tone": "🙇🏽\u200d♀️", + "woman_cartwheeling": "🤸\u200d♀️", + "woman_cartwheeling_dark_skin_tone": "🤸🏿\u200d♀️", + "woman_cartwheeling_light_skin_tone": "🤸🏻\u200d♀️", + "woman_cartwheeling_medium-dark_skin_tone": "🤸🏾\u200d♀️", + "woman_cartwheeling_medium-light_skin_tone": "🤸🏼\u200d♀️", + "woman_cartwheeling_medium_skin_tone": "🤸🏽\u200d♀️", + "woman_climbing": "🧗\u200d♀️", + "woman_climbing_dark_skin_tone": "🧗🏿\u200d♀️", + "woman_climbing_light_skin_tone": "🧗🏻\u200d♀️", + "woman_climbing_medium-dark_skin_tone": "🧗🏾\u200d♀️", + "woman_climbing_medium-light_skin_tone": "🧗🏼\u200d♀️", + "woman_climbing_medium_skin_tone": "🧗🏽\u200d♀️", + "woman_construction_worker": "👷\u200d♀️", + "woman_construction_worker_dark_skin_tone": "👷🏿\u200d♀️", + "woman_construction_worker_light_skin_tone": "👷🏻\u200d♀️", + "woman_construction_worker_medium-dark_skin_tone": "👷🏾\u200d♀️", + "woman_construction_worker_medium-light_skin_tone": "👷🏼\u200d♀️", + "woman_construction_worker_medium_skin_tone": "👷🏽\u200d♀️", + "woman_cook": "👩\u200d🍳", + "woman_cook_dark_skin_tone": "👩🏿\u200d🍳", + "woman_cook_light_skin_tone": "👩🏻\u200d🍳", + "woman_cook_medium-dark_skin_tone": "👩🏾\u200d🍳", + "woman_cook_medium-light_skin_tone": "👩🏼\u200d🍳", + "woman_cook_medium_skin_tone": "👩🏽\u200d🍳", + "woman_dancing": "💃", + "woman_dancing_dark_skin_tone": "💃🏿", + "woman_dancing_light_skin_tone": "💃🏻", + "woman_dancing_medium-dark_skin_tone": "💃🏾", + "woman_dancing_medium-light_skin_tone": "💃🏼", + "woman_dancing_medium_skin_tone": "💃🏽", + "woman_dark_skin_tone": "👩🏿", + "woman_detective": "🕵️\u200d♀️", + "woman_detective_dark_skin_tone": "🕵🏿\u200d♀️", + "woman_detective_light_skin_tone": "🕵🏻\u200d♀️", + "woman_detective_medium-dark_skin_tone": "🕵🏾\u200d♀️", + "woman_detective_medium-light_skin_tone": "🕵🏼\u200d♀️", + "woman_detective_medium_skin_tone": "🕵🏽\u200d♀️", + "woman_elf": "🧝\u200d♀️", + "woman_elf_dark_skin_tone": "🧝🏿\u200d♀️", + "woman_elf_light_skin_tone": "🧝🏻\u200d♀️", + "woman_elf_medium-dark_skin_tone": "🧝🏾\u200d♀️", + "woman_elf_medium-light_skin_tone": "🧝🏼\u200d♀️", + "woman_elf_medium_skin_tone": "🧝🏽\u200d♀️", + "woman_facepalming": "🤦\u200d♀️", + "woman_facepalming_dark_skin_tone": "🤦🏿\u200d♀️", + "woman_facepalming_light_skin_tone": "🤦🏻\u200d♀️", + "woman_facepalming_medium-dark_skin_tone": "🤦🏾\u200d♀️", + "woman_facepalming_medium-light_skin_tone": "🤦🏼\u200d♀️", + "woman_facepalming_medium_skin_tone": "🤦🏽\u200d♀️", + "woman_factory_worker": "👩\u200d🏭", + "woman_factory_worker_dark_skin_tone": "👩🏿\u200d🏭", + "woman_factory_worker_light_skin_tone": "👩🏻\u200d🏭", + "woman_factory_worker_medium-dark_skin_tone": "👩🏾\u200d🏭", + "woman_factory_worker_medium-light_skin_tone": "👩🏼\u200d🏭", + "woman_factory_worker_medium_skin_tone": "👩🏽\u200d🏭", + "woman_fairy": "🧚\u200d♀️", + "woman_fairy_dark_skin_tone": "🧚🏿\u200d♀️", + "woman_fairy_light_skin_tone": "🧚🏻\u200d♀️", + "woman_fairy_medium-dark_skin_tone": "🧚🏾\u200d♀️", + "woman_fairy_medium-light_skin_tone": "🧚🏼\u200d♀️", + "woman_fairy_medium_skin_tone": "🧚🏽\u200d♀️", + "woman_farmer": "👩\u200d🌾", + "woman_farmer_dark_skin_tone": "👩🏿\u200d🌾", + "woman_farmer_light_skin_tone": "👩🏻\u200d🌾", + "woman_farmer_medium-dark_skin_tone": "👩🏾\u200d🌾", + "woman_farmer_medium-light_skin_tone": "👩🏼\u200d🌾", + "woman_farmer_medium_skin_tone": "👩🏽\u200d🌾", + "woman_firefighter": "👩\u200d🚒", + "woman_firefighter_dark_skin_tone": "👩🏿\u200d🚒", + "woman_firefighter_light_skin_tone": "👩🏻\u200d🚒", + "woman_firefighter_medium-dark_skin_tone": "👩🏾\u200d🚒", + "woman_firefighter_medium-light_skin_tone": "👩🏼\u200d🚒", + "woman_firefighter_medium_skin_tone": "👩🏽\u200d🚒", + "woman_frowning": "🙍\u200d♀️", + "woman_frowning_dark_skin_tone": "🙍🏿\u200d♀️", + "woman_frowning_light_skin_tone": "🙍🏻\u200d♀️", + "woman_frowning_medium-dark_skin_tone": "🙍🏾\u200d♀️", + "woman_frowning_medium-light_skin_tone": "🙍🏼\u200d♀️", + "woman_frowning_medium_skin_tone": "🙍🏽\u200d♀️", + "woman_genie": "🧞\u200d♀️", + "woman_gesturing_no": "🙅\u200d♀️", + "woman_gesturing_no_dark_skin_tone": "🙅🏿\u200d♀️", + "woman_gesturing_no_light_skin_tone": "🙅🏻\u200d♀️", + "woman_gesturing_no_medium-dark_skin_tone": "🙅🏾\u200d♀️", + "woman_gesturing_no_medium-light_skin_tone": "🙅🏼\u200d♀️", + "woman_gesturing_no_medium_skin_tone": "🙅🏽\u200d♀️", + "woman_gesturing_ok": "🙆\u200d♀️", + "woman_gesturing_ok_dark_skin_tone": "🙆🏿\u200d♀️", + "woman_gesturing_ok_light_skin_tone": "🙆🏻\u200d♀️", + "woman_gesturing_ok_medium-dark_skin_tone": "🙆🏾\u200d♀️", + "woman_gesturing_ok_medium-light_skin_tone": "🙆🏼\u200d♀️", + "woman_gesturing_ok_medium_skin_tone": "🙆🏽\u200d♀️", + "woman_getting_haircut": "💇\u200d♀️", + "woman_getting_haircut_dark_skin_tone": "💇🏿\u200d♀️", + "woman_getting_haircut_light_skin_tone": "💇🏻\u200d♀️", + "woman_getting_haircut_medium-dark_skin_tone": "💇🏾\u200d♀️", + "woman_getting_haircut_medium-light_skin_tone": "💇🏼\u200d♀️", + "woman_getting_haircut_medium_skin_tone": "💇🏽\u200d♀️", + "woman_getting_massage": "💆\u200d♀️", + "woman_getting_massage_dark_skin_tone": "💆🏿\u200d♀️", + "woman_getting_massage_light_skin_tone": "💆🏻\u200d♀️", + "woman_getting_massage_medium-dark_skin_tone": "💆🏾\u200d♀️", + "woman_getting_massage_medium-light_skin_tone": "💆🏼\u200d♀️", + "woman_getting_massage_medium_skin_tone": "💆🏽\u200d♀️", + "woman_golfing": "🏌️\u200d♀️", + "woman_golfing_dark_skin_tone": "🏌🏿\u200d♀️", + "woman_golfing_light_skin_tone": "🏌🏻\u200d♀️", + "woman_golfing_medium-dark_skin_tone": "🏌🏾\u200d♀️", + "woman_golfing_medium-light_skin_tone": "🏌🏼\u200d♀️", + "woman_golfing_medium_skin_tone": "🏌🏽\u200d♀️", + "woman_guard": "💂\u200d♀️", + "woman_guard_dark_skin_tone": "💂🏿\u200d♀️", + "woman_guard_light_skin_tone": "💂🏻\u200d♀️", + "woman_guard_medium-dark_skin_tone": "💂🏾\u200d♀️", + "woman_guard_medium-light_skin_tone": "💂🏼\u200d♀️", + "woman_guard_medium_skin_tone": "💂🏽\u200d♀️", + "woman_health_worker": "👩\u200d⚕️", + "woman_health_worker_dark_skin_tone": "👩🏿\u200d⚕️", + "woman_health_worker_light_skin_tone": "👩🏻\u200d⚕️", + "woman_health_worker_medium-dark_skin_tone": "👩🏾\u200d⚕️", + "woman_health_worker_medium-light_skin_tone": "👩🏼\u200d⚕️", + "woman_health_worker_medium_skin_tone": "👩🏽\u200d⚕️", + "woman_in_lotus_position": "🧘\u200d♀️", + "woman_in_lotus_position_dark_skin_tone": "🧘🏿\u200d♀️", + "woman_in_lotus_position_light_skin_tone": "🧘🏻\u200d♀️", + "woman_in_lotus_position_medium-dark_skin_tone": "🧘🏾\u200d♀️", + "woman_in_lotus_position_medium-light_skin_tone": "🧘🏼\u200d♀️", + "woman_in_lotus_position_medium_skin_tone": "🧘🏽\u200d♀️", + "woman_in_manual_wheelchair": "👩\u200d🦽", + "woman_in_motorized_wheelchair": "👩\u200d🦼", + "woman_in_steamy_room": "🧖\u200d♀️", + "woman_in_steamy_room_dark_skin_tone": "🧖🏿\u200d♀️", + "woman_in_steamy_room_light_skin_tone": "🧖🏻\u200d♀️", + "woman_in_steamy_room_medium-dark_skin_tone": "🧖🏾\u200d♀️", + "woman_in_steamy_room_medium-light_skin_tone": "🧖🏼\u200d♀️", + "woman_in_steamy_room_medium_skin_tone": "🧖🏽\u200d♀️", + "woman_judge": "👩\u200d⚖️", + "woman_judge_dark_skin_tone": "👩🏿\u200d⚖️", + "woman_judge_light_skin_tone": "👩🏻\u200d⚖️", + "woman_judge_medium-dark_skin_tone": "👩🏾\u200d⚖️", + "woman_judge_medium-light_skin_tone": "👩🏼\u200d⚖️", + "woman_judge_medium_skin_tone": "👩🏽\u200d⚖️", + "woman_juggling": "🤹\u200d♀️", + "woman_juggling_dark_skin_tone": "🤹🏿\u200d♀️", + "woman_juggling_light_skin_tone": "🤹🏻\u200d♀️", + "woman_juggling_medium-dark_skin_tone": "🤹🏾\u200d♀️", + "woman_juggling_medium-light_skin_tone": "🤹🏼\u200d♀️", + "woman_juggling_medium_skin_tone": "🤹🏽\u200d♀️", + "woman_lifting_weights": "🏋️\u200d♀️", + "woman_lifting_weights_dark_skin_tone": "🏋🏿\u200d♀️", + "woman_lifting_weights_light_skin_tone": "🏋🏻\u200d♀️", + "woman_lifting_weights_medium-dark_skin_tone": "🏋🏾\u200d♀️", + "woman_lifting_weights_medium-light_skin_tone": "🏋🏼\u200d♀️", + "woman_lifting_weights_medium_skin_tone": "🏋🏽\u200d♀️", + "woman_light_skin_tone": "👩🏻", + "woman_mage": "🧙\u200d♀️", + "woman_mage_dark_skin_tone": "🧙🏿\u200d♀️", + "woman_mage_light_skin_tone": "🧙🏻\u200d♀️", + "woman_mage_medium-dark_skin_tone": "🧙🏾\u200d♀️", + "woman_mage_medium-light_skin_tone": "🧙🏼\u200d♀️", + "woman_mage_medium_skin_tone": "🧙🏽\u200d♀️", + "woman_mechanic": "👩\u200d🔧", + "woman_mechanic_dark_skin_tone": "👩🏿\u200d🔧", + "woman_mechanic_light_skin_tone": "👩🏻\u200d🔧", + "woman_mechanic_medium-dark_skin_tone": "👩🏾\u200d🔧", + "woman_mechanic_medium-light_skin_tone": "👩🏼\u200d🔧", + "woman_mechanic_medium_skin_tone": "👩🏽\u200d🔧", + "woman_medium-dark_skin_tone": "👩🏾", + "woman_medium-light_skin_tone": "👩🏼", + "woman_medium_skin_tone": "👩🏽", + "woman_mountain_biking": "🚵\u200d♀️", + "woman_mountain_biking_dark_skin_tone": "🚵🏿\u200d♀️", + "woman_mountain_biking_light_skin_tone": "🚵🏻\u200d♀️", + "woman_mountain_biking_medium-dark_skin_tone": "🚵🏾\u200d♀️", + "woman_mountain_biking_medium-light_skin_tone": "🚵🏼\u200d♀️", + "woman_mountain_biking_medium_skin_tone": "🚵🏽\u200d♀️", + "woman_office_worker": "👩\u200d💼", + "woman_office_worker_dark_skin_tone": "👩🏿\u200d💼", + "woman_office_worker_light_skin_tone": "👩🏻\u200d💼", + "woman_office_worker_medium-dark_skin_tone": "👩🏾\u200d💼", + "woman_office_worker_medium-light_skin_tone": "👩🏼\u200d💼", + "woman_office_worker_medium_skin_tone": "👩🏽\u200d💼", + "woman_pilot": "👩\u200d✈️", + "woman_pilot_dark_skin_tone": "👩🏿\u200d✈️", + "woman_pilot_light_skin_tone": "👩🏻\u200d✈️", + "woman_pilot_medium-dark_skin_tone": "👩🏾\u200d✈️", + "woman_pilot_medium-light_skin_tone": "👩🏼\u200d✈️", + "woman_pilot_medium_skin_tone": "👩🏽\u200d✈️", + "woman_playing_handball": "🤾\u200d♀️", + "woman_playing_handball_dark_skin_tone": "🤾🏿\u200d♀️", + "woman_playing_handball_light_skin_tone": "🤾🏻\u200d♀️", + "woman_playing_handball_medium-dark_skin_tone": "🤾🏾\u200d♀️", + "woman_playing_handball_medium-light_skin_tone": "🤾🏼\u200d♀️", + "woman_playing_handball_medium_skin_tone": "🤾🏽\u200d♀️", + "woman_playing_water_polo": "🤽\u200d♀️", + "woman_playing_water_polo_dark_skin_tone": "🤽🏿\u200d♀️", + "woman_playing_water_polo_light_skin_tone": "🤽🏻\u200d♀️", + "woman_playing_water_polo_medium-dark_skin_tone": "🤽🏾\u200d♀️", + "woman_playing_water_polo_medium-light_skin_tone": "🤽🏼\u200d♀️", + "woman_playing_water_polo_medium_skin_tone": "🤽🏽\u200d♀️", + "woman_police_officer": "👮\u200d♀️", + "woman_police_officer_dark_skin_tone": "👮🏿\u200d♀️", + "woman_police_officer_light_skin_tone": "👮🏻\u200d♀️", + "woman_police_officer_medium-dark_skin_tone": "👮🏾\u200d♀️", + "woman_police_officer_medium-light_skin_tone": "👮🏼\u200d♀️", + "woman_police_officer_medium_skin_tone": "👮🏽\u200d♀️", + "woman_pouting": "🙎\u200d♀️", + "woman_pouting_dark_skin_tone": "🙎🏿\u200d♀️", + "woman_pouting_light_skin_tone": "🙎🏻\u200d♀️", + "woman_pouting_medium-dark_skin_tone": "🙎🏾\u200d♀️", + "woman_pouting_medium-light_skin_tone": "🙎🏼\u200d♀️", + "woman_pouting_medium_skin_tone": "🙎🏽\u200d♀️", + "woman_raising_hand": "🙋\u200d♀️", + "woman_raising_hand_dark_skin_tone": "🙋🏿\u200d♀️", + "woman_raising_hand_light_skin_tone": "🙋🏻\u200d♀️", + "woman_raising_hand_medium-dark_skin_tone": "🙋🏾\u200d♀️", + "woman_raising_hand_medium-light_skin_tone": "🙋🏼\u200d♀️", + "woman_raising_hand_medium_skin_tone": "🙋🏽\u200d♀️", + "woman_rowing_boat": "🚣\u200d♀️", + "woman_rowing_boat_dark_skin_tone": "🚣🏿\u200d♀️", + "woman_rowing_boat_light_skin_tone": "🚣🏻\u200d♀️", + "woman_rowing_boat_medium-dark_skin_tone": "🚣🏾\u200d♀️", + "woman_rowing_boat_medium-light_skin_tone": "🚣🏼\u200d♀️", + "woman_rowing_boat_medium_skin_tone": "🚣🏽\u200d♀️", + "woman_running": "🏃\u200d♀️", + "woman_running_dark_skin_tone": "🏃🏿\u200d♀️", + "woman_running_light_skin_tone": "🏃🏻\u200d♀️", + "woman_running_medium-dark_skin_tone": "🏃🏾\u200d♀️", + "woman_running_medium-light_skin_tone": "🏃🏼\u200d♀️", + "woman_running_medium_skin_tone": "🏃🏽\u200d♀️", + "woman_scientist": "👩\u200d🔬", + "woman_scientist_dark_skin_tone": "👩🏿\u200d🔬", + "woman_scientist_light_skin_tone": "👩🏻\u200d🔬", + "woman_scientist_medium-dark_skin_tone": "👩🏾\u200d🔬", + "woman_scientist_medium-light_skin_tone": "👩🏼\u200d🔬", + "woman_scientist_medium_skin_tone": "👩🏽\u200d🔬", + "woman_shrugging": "🤷\u200d♀️", + "woman_shrugging_dark_skin_tone": "🤷🏿\u200d♀️", + "woman_shrugging_light_skin_tone": "🤷🏻\u200d♀️", + "woman_shrugging_medium-dark_skin_tone": "🤷🏾\u200d♀️", + "woman_shrugging_medium-light_skin_tone": "🤷🏼\u200d♀️", + "woman_shrugging_medium_skin_tone": "🤷🏽\u200d♀️", + "woman_singer": "👩\u200d🎤", + "woman_singer_dark_skin_tone": "👩🏿\u200d🎤", + "woman_singer_light_skin_tone": "👩🏻\u200d🎤", + "woman_singer_medium-dark_skin_tone": "👩🏾\u200d🎤", + "woman_singer_medium-light_skin_tone": "👩🏼\u200d🎤", + "woman_singer_medium_skin_tone": "👩🏽\u200d🎤", + "woman_student": "👩\u200d🎓", + "woman_student_dark_skin_tone": "👩🏿\u200d🎓", + "woman_student_light_skin_tone": "👩🏻\u200d🎓", + "woman_student_medium-dark_skin_tone": "👩🏾\u200d🎓", + "woman_student_medium-light_skin_tone": "👩🏼\u200d🎓", + "woman_student_medium_skin_tone": "👩🏽\u200d🎓", + "woman_surfing": "🏄\u200d♀️", + "woman_surfing_dark_skin_tone": "🏄🏿\u200d♀️", + "woman_surfing_light_skin_tone": "🏄🏻\u200d♀️", + "woman_surfing_medium-dark_skin_tone": "🏄🏾\u200d♀️", + "woman_surfing_medium-light_skin_tone": "🏄🏼\u200d♀️", + "woman_surfing_medium_skin_tone": "🏄🏽\u200d♀️", + "woman_swimming": "🏊\u200d♀️", + "woman_swimming_dark_skin_tone": "🏊🏿\u200d♀️", + "woman_swimming_light_skin_tone": "🏊🏻\u200d♀️", + "woman_swimming_medium-dark_skin_tone": "🏊🏾\u200d♀️", + "woman_swimming_medium-light_skin_tone": "🏊🏼\u200d♀️", + "woman_swimming_medium_skin_tone": "🏊🏽\u200d♀️", + "woman_teacher": "👩\u200d🏫", + "woman_teacher_dark_skin_tone": "👩🏿\u200d🏫", + "woman_teacher_light_skin_tone": "👩🏻\u200d🏫", + "woman_teacher_medium-dark_skin_tone": "👩🏾\u200d🏫", + "woman_teacher_medium-light_skin_tone": "👩🏼\u200d🏫", + "woman_teacher_medium_skin_tone": "👩🏽\u200d🏫", + "woman_technologist": "👩\u200d💻", + "woman_technologist_dark_skin_tone": "👩🏿\u200d💻", + "woman_technologist_light_skin_tone": "👩🏻\u200d💻", + "woman_technologist_medium-dark_skin_tone": "👩🏾\u200d💻", + "woman_technologist_medium-light_skin_tone": "👩🏼\u200d💻", + "woman_technologist_medium_skin_tone": "👩🏽\u200d💻", + "woman_tipping_hand": "💁\u200d♀️", + "woman_tipping_hand_dark_skin_tone": "💁🏿\u200d♀️", + "woman_tipping_hand_light_skin_tone": "💁🏻\u200d♀️", + "woman_tipping_hand_medium-dark_skin_tone": "💁🏾\u200d♀️", + "woman_tipping_hand_medium-light_skin_tone": "💁🏼\u200d♀️", + "woman_tipping_hand_medium_skin_tone": "💁🏽\u200d♀️", + "woman_vampire": "🧛\u200d♀️", + "woman_vampire_dark_skin_tone": "🧛🏿\u200d♀️", + "woman_vampire_light_skin_tone": "🧛🏻\u200d♀️", + "woman_vampire_medium-dark_skin_tone": "🧛🏾\u200d♀️", + "woman_vampire_medium-light_skin_tone": "🧛🏼\u200d♀️", + "woman_vampire_medium_skin_tone": "🧛🏽\u200d♀️", + "woman_walking": "🚶\u200d♀️", + "woman_walking_dark_skin_tone": "🚶🏿\u200d♀️", + "woman_walking_light_skin_tone": "🚶🏻\u200d♀️", + "woman_walking_medium-dark_skin_tone": "🚶🏾\u200d♀️", + "woman_walking_medium-light_skin_tone": "🚶🏼\u200d♀️", + "woman_walking_medium_skin_tone": "🚶🏽\u200d♀️", + "woman_wearing_turban": "👳\u200d♀️", + "woman_wearing_turban_dark_skin_tone": "👳🏿\u200d♀️", + "woman_wearing_turban_light_skin_tone": "👳🏻\u200d♀️", + "woman_wearing_turban_medium-dark_skin_tone": "👳🏾\u200d♀️", + "woman_wearing_turban_medium-light_skin_tone": "👳🏼\u200d♀️", + "woman_wearing_turban_medium_skin_tone": "👳🏽\u200d♀️", + "woman_with_headscarf": "🧕", + "woman_with_headscarf_dark_skin_tone": "🧕🏿", + "woman_with_headscarf_light_skin_tone": "🧕🏻", + "woman_with_headscarf_medium-dark_skin_tone": "🧕🏾", + "woman_with_headscarf_medium-light_skin_tone": "🧕🏼", + "woman_with_headscarf_medium_skin_tone": "🧕🏽", + "woman_with_probing_cane": "👩\u200d🦯", + "woman_zombie": "🧟\u200d♀️", + "woman’s_boot": "👢", + "woman’s_clothes": "👚", + "woman’s_hat": "👒", + "woman’s_sandal": "👡", + "women_with_bunny_ears": "👯\u200d♀️", + "women_wrestling": "🤼\u200d♀️", + "women’s_room": "🚺", + "woozy_face": "🥴", + "world_map": "🗺", + "worried_face": "😟", + "wrapped_gift": "🎁", + "wrench": "🔧", + "writing_hand": "✍", + "writing_hand_dark_skin_tone": "✍🏿", + "writing_hand_light_skin_tone": "✍🏻", + "writing_hand_medium-dark_skin_tone": "✍🏾", + "writing_hand_medium-light_skin_tone": "✍🏼", + "writing_hand_medium_skin_tone": "✍🏽", + "yarn": "🧶", + "yawning_face": "🥱", + "yellow_circle": "🟡", + "yellow_heart": "💛", + "yellow_square": "🟨", + "yen_banknote": "💴", + "yo-yo": "🪀", + "yin_yang": "☯", + "zany_face": "🤪", + "zebra": "🦓", + "zipper-mouth_face": "🤐", + "zombie": "🧟", + "zzz": "💤", + "åland_islands": "🇦🇽", + "keycap_asterisk": "*⃣", + "keycap_digit_eight": "8⃣", + "keycap_digit_five": "5⃣", + "keycap_digit_four": "4⃣", + "keycap_digit_nine": "9⃣", + "keycap_digit_one": "1⃣", + "keycap_digit_seven": "7⃣", + "keycap_digit_six": "6⃣", + "keycap_digit_three": "3⃣", + "keycap_digit_two": "2⃣", + "keycap_digit_zero": "0⃣", + "keycap_number_sign": "#⃣", + "light_skin_tone": "🏻", + "medium_light_skin_tone": "🏼", + "medium_skin_tone": "🏽", + "medium_dark_skin_tone": "🏾", + "dark_skin_tone": "🏿", + "regional_indicator_symbol_letter_a": "🇦", + "regional_indicator_symbol_letter_b": "🇧", + "regional_indicator_symbol_letter_c": "🇨", + "regional_indicator_symbol_letter_d": "🇩", + "regional_indicator_symbol_letter_e": "🇪", + "regional_indicator_symbol_letter_f": "🇫", + "regional_indicator_symbol_letter_g": "🇬", + "regional_indicator_symbol_letter_h": "🇭", + "regional_indicator_symbol_letter_i": "🇮", + "regional_indicator_symbol_letter_j": "🇯", + "regional_indicator_symbol_letter_k": "🇰", + "regional_indicator_symbol_letter_l": "🇱", + "regional_indicator_symbol_letter_m": "🇲", + "regional_indicator_symbol_letter_n": "🇳", + "regional_indicator_symbol_letter_o": "🇴", + "regional_indicator_symbol_letter_p": "🇵", + "regional_indicator_symbol_letter_q": "🇶", + "regional_indicator_symbol_letter_r": "🇷", + "regional_indicator_symbol_letter_s": "🇸", + "regional_indicator_symbol_letter_t": "🇹", + "regional_indicator_symbol_letter_u": "🇺", + "regional_indicator_symbol_letter_v": "🇻", + "regional_indicator_symbol_letter_w": "🇼", + "regional_indicator_symbol_letter_x": "🇽", + "regional_indicator_symbol_letter_y": "🇾", + "regional_indicator_symbol_letter_z": "🇿", + "airplane_arriving": "🛬", + "space_invader": "👾", + "football": "🏈", + "anger": "💢", + "angry": "😠", + "anguished": "😧", + "signal_strength": "📶", + "arrows_counterclockwise": "🔄", + "arrow_heading_down": "⤵", + "arrow_heading_up": "⤴", + "art": "🎨", + "astonished": "😲", + "athletic_shoe": "👟", + "atm": "🏧", + "car": "🚗", + "red_car": "🚗", + "angel": "👼", + "back": "🔙", + "badminton_racquet_and_shuttlecock": "🏸", + "dollar": "💵", + "euro": "💶", + "pound": "💷", + "yen": "💴", + "barber": "💈", + "bath": "🛀", + "bear": "🐻", + "heartbeat": "💓", + "beer": "🍺", + "no_bell": "🔕", + "bento": "🍱", + "bike": "🚲", + "bicyclist": "🚴", + "8ball": "🎱", + "biohazard_sign": "☣", + "birthday": "🎂", + "black_circle_for_record": "⏺", + "clubs": "♣", + "diamonds": "♦", + "arrow_double_down": "⏬", + "hearts": "♥", + "rewind": "⏪", + "black_left__pointing_double_triangle_with_vertical_bar": "⏮", + "arrow_backward": "◀", + "black_medium_small_square": "◾", + "question": "❓", + "fast_forward": "⏩", + "black_right__pointing_double_triangle_with_vertical_bar": "⏭", + "arrow_forward": "▶", + "black_right__pointing_triangle_with_double_vertical_bar": "⏯", + "arrow_right": "➡", + "spades": "♠", + "black_square_for_stop": "⏹", + "sunny": "☀", + "phone": "☎", + "recycle": "♻", + "arrow_double_up": "⏫", + "busstop": "🚏", + "date": "📅", + "flags": "🎏", + "cat2": "🐈", + "joy_cat": "😹", + "smirk_cat": "😼", + "chart_with_downwards_trend": "📉", + "chart_with_upwards_trend": "📈", + "chart": "💹", + "mega": "📣", + "checkered_flag": "🏁", + "accept": "🉑", + "ideograph_advantage": "🉐", + "congratulations": "㊗", + "secret": "㊙", + "m": "Ⓜ", + "city_sunset": "🌆", + "clapper": "🎬", + "clap": "👏", + "beers": "🍻", + "clock830": "🕣", + "clock8": "🕗", + "clock1130": "🕦", + "clock11": "🕚", + "clock530": "🕠", + "clock5": "🕔", + "clock430": "🕟", + "clock4": "🕓", + "clock930": "🕤", + "clock9": "🕘", + "clock130": "🕜", + "clock1": "🕐", + "clock730": "🕢", + "clock7": "🕖", + "clock630": "🕡", + "clock6": "🕕", + "clock1030": "🕥", + "clock10": "🕙", + "clock330": "🕞", + "clock3": "🕒", + "clock1230": "🕧", + "clock12": "🕛", + "clock230": "🕝", + "clock2": "🕑", + "arrows_clockwise": "🔃", + "repeat": "🔁", + "repeat_one": "🔂", + "closed_lock_with_key": "🔐", + "mailbox_closed": "📪", + "mailbox": "📫", + "cloud_with_tornado": "🌪", + "cocktail": "🍸", + "boom": "💥", + "compression": "🗜", + "confounded": "😖", + "confused": "😕", + "rice": "🍚", + "cow2": "🐄", + "cricket_bat_and_ball": "🏏", + "x": "❌", + "cry": "😢", + "curry": "🍛", + "dagger_knife": "🗡", + "dancer": "💃", + "dark_sunglasses": "🕶", + "dash": "💨", + "truck": "🚚", + "derelict_house_building": "🏚", + "diamond_shape_with_a_dot_inside": "💠", + "dart": "🎯", + "disappointed_relieved": "😥", + "disappointed": "😞", + "do_not_litter": "🚯", + "dog2": "🐕", + "flipper": "🐬", + "loop": "➿", + "bangbang": "‼", + "double_vertical_bar": "⏸", + "dove_of_peace": "🕊", + "small_red_triangle_down": "🔻", + "arrow_down_small": "🔽", + "arrow_down": "⬇", + "dromedary_camel": "🐪", + "e__mail": "📧", + "corn": "🌽", + "ear_of_rice": "🌾", + "earth_americas": "🌎", + "earth_asia": "🌏", + "earth_africa": "🌍", + "eight_pointed_black_star": "✴", + "eight_spoked_asterisk": "✳", + "eject_symbol": "⏏", + "bulb": "💡", + "emoji_modifier_fitzpatrick_type__1__2": "🏻", + "emoji_modifier_fitzpatrick_type__3": "🏼", + "emoji_modifier_fitzpatrick_type__4": "🏽", + "emoji_modifier_fitzpatrick_type__5": "🏾", + "emoji_modifier_fitzpatrick_type__6": "🏿", + "end": "🔚", + "email": "✉", + "european_castle": "🏰", + "european_post_office": "🏤", + "interrobang": "⁉", + "expressionless": "😑", + "eyeglasses": "👓", + "massage": "💆", + "yum": "😋", + "scream": "😱", + "kissing_heart": "😘", + "sweat": "😓", + "face_with_head__bandage": "🤕", + "triumph": "😤", + "mask": "😷", + "no_good": "🙅", + "ok_woman": "🙆", + "open_mouth": "😮", + "cold_sweat": "😰", + "stuck_out_tongue": "😛", + "stuck_out_tongue_closed_eyes": "😝", + "stuck_out_tongue_winking_eye": "😜", + "joy": "😂", + "no_mouth": "😶", + "santa": "🎅", + "fax": "📠", + "fearful": "😨", + "field_hockey_stick_and_ball": "🏑", + "first_quarter_moon_with_face": "🌛", + "fish_cake": "🍥", + "fishing_pole_and_fish": "🎣", + "facepunch": "👊", + "punch": "👊", + "flag_for_afghanistan": "🇦🇫", + "flag_for_albania": "🇦🇱", + "flag_for_algeria": "🇩🇿", + "flag_for_american_samoa": "🇦🇸", + "flag_for_andorra": "🇦🇩", + "flag_for_angola": "🇦🇴", + "flag_for_anguilla": "🇦🇮", + "flag_for_antarctica": "🇦🇶", + "flag_for_antigua_&_barbuda": "🇦🇬", + "flag_for_argentina": "🇦🇷", + "flag_for_armenia": "🇦🇲", + "flag_for_aruba": "🇦🇼", + "flag_for_ascension_island": "🇦🇨", + "flag_for_australia": "🇦🇺", + "flag_for_austria": "🇦🇹", + "flag_for_azerbaijan": "🇦🇿", + "flag_for_bahamas": "🇧🇸", + "flag_for_bahrain": "🇧🇭", + "flag_for_bangladesh": "🇧🇩", + "flag_for_barbados": "🇧🇧", + "flag_for_belarus": "🇧🇾", + "flag_for_belgium": "🇧🇪", + "flag_for_belize": "🇧🇿", + "flag_for_benin": "🇧🇯", + "flag_for_bermuda": "🇧🇲", + "flag_for_bhutan": "🇧🇹", + "flag_for_bolivia": "🇧🇴", + "flag_for_bosnia_&_herzegovina": "🇧🇦", + "flag_for_botswana": "🇧🇼", + "flag_for_bouvet_island": "🇧🇻", + "flag_for_brazil": "🇧🇷", + "flag_for_british_indian_ocean_territory": "🇮🇴", + "flag_for_british_virgin_islands": "🇻🇬", + "flag_for_brunei": "🇧🇳", + "flag_for_bulgaria": "🇧🇬", + "flag_for_burkina_faso": "🇧🇫", + "flag_for_burundi": "🇧🇮", + "flag_for_cambodia": "🇰🇭", + "flag_for_cameroon": "🇨🇲", + "flag_for_canada": "🇨🇦", + "flag_for_canary_islands": "🇮🇨", + "flag_for_cape_verde": "🇨🇻", + "flag_for_caribbean_netherlands": "🇧🇶", + "flag_for_cayman_islands": "🇰🇾", + "flag_for_central_african_republic": "🇨🇫", + "flag_for_ceuta_&_melilla": "🇪🇦", + "flag_for_chad": "🇹🇩", + "flag_for_chile": "🇨🇱", + "flag_for_china": "🇨🇳", + "flag_for_christmas_island": "🇨🇽", + "flag_for_clipperton_island": "🇨🇵", + "flag_for_cocos__islands": "🇨🇨", + "flag_for_colombia": "🇨🇴", + "flag_for_comoros": "🇰🇲", + "flag_for_congo____brazzaville": "🇨🇬", + "flag_for_congo____kinshasa": "🇨🇩", + "flag_for_cook_islands": "🇨🇰", + "flag_for_costa_rica": "🇨🇷", + "flag_for_croatia": "🇭🇷", + "flag_for_cuba": "🇨🇺", + "flag_for_curaçao": "🇨🇼", + "flag_for_cyprus": "🇨🇾", + "flag_for_czech_republic": "🇨🇿", + "flag_for_côte_d’ivoire": "🇨🇮", + "flag_for_denmark": "🇩🇰", + "flag_for_diego_garcia": "🇩🇬", + "flag_for_djibouti": "🇩🇯", + "flag_for_dominica": "🇩🇲", + "flag_for_dominican_republic": "🇩🇴", + "flag_for_ecuador": "🇪🇨", + "flag_for_egypt": "🇪🇬", + "flag_for_el_salvador": "🇸🇻", + "flag_for_equatorial_guinea": "🇬🇶", + "flag_for_eritrea": "🇪🇷", + "flag_for_estonia": "🇪🇪", + "flag_for_ethiopia": "🇪🇹", + "flag_for_european_union": "🇪🇺", + "flag_for_falkland_islands": "🇫🇰", + "flag_for_faroe_islands": "🇫🇴", + "flag_for_fiji": "🇫🇯", + "flag_for_finland": "🇫🇮", + "flag_for_france": "🇫🇷", + "flag_for_french_guiana": "🇬🇫", + "flag_for_french_polynesia": "🇵🇫", + "flag_for_french_southern_territories": "🇹🇫", + "flag_for_gabon": "🇬🇦", + "flag_for_gambia": "🇬🇲", + "flag_for_georgia": "🇬🇪", + "flag_for_germany": "🇩🇪", + "flag_for_ghana": "🇬🇭", + "flag_for_gibraltar": "🇬🇮", + "flag_for_greece": "🇬🇷", + "flag_for_greenland": "🇬🇱", + "flag_for_grenada": "🇬🇩", + "flag_for_guadeloupe": "🇬🇵", + "flag_for_guam": "🇬🇺", + "flag_for_guatemala": "🇬🇹", + "flag_for_guernsey": "🇬🇬", + "flag_for_guinea": "🇬🇳", + "flag_for_guinea__bissau": "🇬🇼", + "flag_for_guyana": "🇬🇾", + "flag_for_haiti": "🇭🇹", + "flag_for_heard_&_mcdonald_islands": "🇭🇲", + "flag_for_honduras": "🇭🇳", + "flag_for_hong_kong": "🇭🇰", + "flag_for_hungary": "🇭🇺", + "flag_for_iceland": "🇮🇸", + "flag_for_india": "🇮🇳", + "flag_for_indonesia": "🇮🇩", + "flag_for_iran": "🇮🇷", + "flag_for_iraq": "🇮🇶", + "flag_for_ireland": "🇮🇪", + "flag_for_isle_of_man": "🇮🇲", + "flag_for_israel": "🇮🇱", + "flag_for_italy": "🇮🇹", + "flag_for_jamaica": "🇯🇲", + "flag_for_japan": "🇯🇵", + "flag_for_jersey": "🇯🇪", + "flag_for_jordan": "🇯🇴", + "flag_for_kazakhstan": "🇰🇿", + "flag_for_kenya": "🇰🇪", + "flag_for_kiribati": "🇰🇮", + "flag_for_kosovo": "🇽🇰", + "flag_for_kuwait": "🇰🇼", + "flag_for_kyrgyzstan": "🇰🇬", + "flag_for_laos": "🇱🇦", + "flag_for_latvia": "🇱🇻", + "flag_for_lebanon": "🇱🇧", + "flag_for_lesotho": "🇱🇸", + "flag_for_liberia": "🇱🇷", + "flag_for_libya": "🇱🇾", + "flag_for_liechtenstein": "🇱🇮", + "flag_for_lithuania": "🇱🇹", + "flag_for_luxembourg": "🇱🇺", + "flag_for_macau": "🇲🇴", + "flag_for_macedonia": "🇲🇰", + "flag_for_madagascar": "🇲🇬", + "flag_for_malawi": "🇲🇼", + "flag_for_malaysia": "🇲🇾", + "flag_for_maldives": "🇲🇻", + "flag_for_mali": "🇲🇱", + "flag_for_malta": "🇲🇹", + "flag_for_marshall_islands": "🇲🇭", + "flag_for_martinique": "🇲🇶", + "flag_for_mauritania": "🇲🇷", + "flag_for_mauritius": "🇲🇺", + "flag_for_mayotte": "🇾🇹", + "flag_for_mexico": "🇲🇽", + "flag_for_micronesia": "🇫🇲", + "flag_for_moldova": "🇲🇩", + "flag_for_monaco": "🇲🇨", + "flag_for_mongolia": "🇲🇳", + "flag_for_montenegro": "🇲🇪", + "flag_for_montserrat": "🇲🇸", + "flag_for_morocco": "🇲🇦", + "flag_for_mozambique": "🇲🇿", + "flag_for_myanmar": "🇲🇲", + "flag_for_namibia": "🇳🇦", + "flag_for_nauru": "🇳🇷", + "flag_for_nepal": "🇳🇵", + "flag_for_netherlands": "🇳🇱", + "flag_for_new_caledonia": "🇳🇨", + "flag_for_new_zealand": "🇳🇿", + "flag_for_nicaragua": "🇳🇮", + "flag_for_niger": "🇳🇪", + "flag_for_nigeria": "🇳🇬", + "flag_for_niue": "🇳🇺", + "flag_for_norfolk_island": "🇳🇫", + "flag_for_north_korea": "🇰🇵", + "flag_for_northern_mariana_islands": "🇲🇵", + "flag_for_norway": "🇳🇴", + "flag_for_oman": "🇴🇲", + "flag_for_pakistan": "🇵🇰", + "flag_for_palau": "🇵🇼", + "flag_for_palestinian_territories": "🇵🇸", + "flag_for_panama": "🇵🇦", + "flag_for_papua_new_guinea": "🇵🇬", + "flag_for_paraguay": "🇵🇾", + "flag_for_peru": "🇵🇪", + "flag_for_philippines": "🇵🇭", + "flag_for_pitcairn_islands": "🇵🇳", + "flag_for_poland": "🇵🇱", + "flag_for_portugal": "🇵🇹", + "flag_for_puerto_rico": "🇵🇷", + "flag_for_qatar": "🇶🇦", + "flag_for_romania": "🇷🇴", + "flag_for_russia": "🇷🇺", + "flag_for_rwanda": "🇷🇼", + "flag_for_réunion": "🇷🇪", + "flag_for_samoa": "🇼🇸", + "flag_for_san_marino": "🇸🇲", + "flag_for_saudi_arabia": "🇸🇦", + "flag_for_senegal": "🇸🇳", + "flag_for_serbia": "🇷🇸", + "flag_for_seychelles": "🇸🇨", + "flag_for_sierra_leone": "🇸🇱", + "flag_for_singapore": "🇸🇬", + "flag_for_sint_maarten": "🇸🇽", + "flag_for_slovakia": "🇸🇰", + "flag_for_slovenia": "🇸🇮", + "flag_for_solomon_islands": "🇸🇧", + "flag_for_somalia": "🇸🇴", + "flag_for_south_africa": "🇿🇦", + "flag_for_south_georgia_&_south_sandwich_islands": "🇬🇸", + "flag_for_south_korea": "🇰🇷", + "flag_for_south_sudan": "🇸🇸", + "flag_for_spain": "🇪🇸", + "flag_for_sri_lanka": "🇱🇰", + "flag_for_st._barthélemy": "🇧🇱", + "flag_for_st._helena": "🇸🇭", + "flag_for_st._kitts_&_nevis": "🇰🇳", + "flag_for_st._lucia": "🇱🇨", + "flag_for_st._martin": "🇲🇫", + "flag_for_st._pierre_&_miquelon": "🇵🇲", + "flag_for_st._vincent_&_grenadines": "🇻🇨", + "flag_for_sudan": "🇸🇩", + "flag_for_suriname": "🇸🇷", + "flag_for_svalbard_&_jan_mayen": "🇸🇯", + "flag_for_swaziland": "🇸🇿", + "flag_for_sweden": "🇸🇪", + "flag_for_switzerland": "🇨🇭", + "flag_for_syria": "🇸🇾", + "flag_for_são_tomé_&_príncipe": "🇸🇹", + "flag_for_taiwan": "🇹🇼", + "flag_for_tajikistan": "🇹🇯", + "flag_for_tanzania": "🇹🇿", + "flag_for_thailand": "🇹🇭", + "flag_for_timor__leste": "🇹🇱", + "flag_for_togo": "🇹🇬", + "flag_for_tokelau": "🇹🇰", + "flag_for_tonga": "🇹🇴", + "flag_for_trinidad_&_tobago": "🇹🇹", + "flag_for_tristan_da_cunha": "🇹🇦", + "flag_for_tunisia": "🇹🇳", + "flag_for_turkey": "🇹🇷", + "flag_for_turkmenistan": "🇹🇲", + "flag_for_turks_&_caicos_islands": "🇹🇨", + "flag_for_tuvalu": "🇹🇻", + "flag_for_u.s._outlying_islands": "🇺🇲", + "flag_for_u.s._virgin_islands": "🇻🇮", + "flag_for_uganda": "🇺🇬", + "flag_for_ukraine": "🇺🇦", + "flag_for_united_arab_emirates": "🇦🇪", + "flag_for_united_kingdom": "🇬🇧", + "flag_for_united_states": "🇺🇸", + "flag_for_uruguay": "🇺🇾", + "flag_for_uzbekistan": "🇺🇿", + "flag_for_vanuatu": "🇻🇺", + "flag_for_vatican_city": "🇻🇦", + "flag_for_venezuela": "🇻🇪", + "flag_for_vietnam": "🇻🇳", + "flag_for_wallis_&_futuna": "🇼🇫", + "flag_for_western_sahara": "🇪🇭", + "flag_for_yemen": "🇾🇪", + "flag_for_zambia": "🇿🇲", + "flag_for_zimbabwe": "🇿🇼", + "flag_for_åland_islands": "🇦🇽", + "golf": "⛳", + "fleur__de__lis": "⚜", + "muscle": "💪", + "flushed": "😳", + "frame_with_picture": "🖼", + "fries": "🍟", + "frog": "🐸", + "hatched_chick": "🐥", + "frowning": "😦", + "fuelpump": "⛽", + "full_moon_with_face": "🌝", + "gem": "💎", + "star2": "🌟", + "golfer": "🏌", + "mortar_board": "🎓", + "grimacing": "😬", + "smile_cat": "😸", + "grinning": "😀", + "grin": "😁", + "heartpulse": "💗", + "guardsman": "💂", + "haircut": "💇", + "hamster": "🐹", + "raising_hand": "🙋", + "headphones": "🎧", + "hear_no_evil": "🙉", + "cupid": "💘", + "gift_heart": "💝", + "heart": "❤", + "exclamation": "❗", + "heavy_exclamation_mark": "❗", + "heavy_heart_exclamation_mark_ornament": "❣", + "o": "⭕", + "helm_symbol": "⎈", + "helmet_with_white_cross": "⛑", + "high_heel": "👠", + "bullettrain_side": "🚄", + "bullettrain_front": "🚅", + "high_brightness": "🔆", + "zap": "⚡", + "hocho": "🔪", + "knife": "🔪", + "bee": "🐝", + "traffic_light": "🚥", + "racehorse": "🐎", + "coffee": "☕", + "hotsprings": "♨", + "hourglass": "⌛", + "hourglass_flowing_sand": "⏳", + "house_buildings": "🏘", + "100": "💯", + "hushed": "😯", + "ice_hockey_stick_and_puck": "🏒", + "imp": "👿", + "information_desk_person": "💁", + "information_source": "ℹ", + "capital_abcd": "🔠", + "abc": "🔤", + "abcd": "🔡", + "1234": "🔢", + "symbols": "🔣", + "izakaya_lantern": "🏮", + "lantern": "🏮", + "jack_o_lantern": "🎃", + "dolls": "🎎", + "japanese_goblin": "👺", + "japanese_ogre": "👹", + "beginner": "🔰", + "zero": "0️⃣", + "one": "1️⃣", + "ten": "🔟", + "two": "2️⃣", + "three": "3️⃣", + "four": "4️⃣", + "five": "5️⃣", + "six": "6️⃣", + "seven": "7️⃣", + "eight": "8️⃣", + "nine": "9️⃣", + "couplekiss": "💏", + "kissing_cat": "😽", + "kissing": "😗", + "kissing_closed_eyes": "😚", + "kissing_smiling_eyes": "😙", + "beetle": "🐞", + "large_blue_circle": "🔵", + "last_quarter_moon_with_face": "🌜", + "leaves": "🍃", + "mag": "🔍", + "left_right_arrow": "↔", + "leftwards_arrow_with_hook": "↩", + "arrow_left": "⬅", + "lock": "🔒", + "lock_with_ink_pen": "🔏", + "sob": "😭", + "low_brightness": "🔅", + "lower_left_ballpoint_pen": "🖊", + "lower_left_crayon": "🖍", + "lower_left_fountain_pen": "🖋", + "lower_left_paintbrush": "🖌", + "mahjong": "🀄", + "couple": "👫", + "man_in_business_suit_levitating": "🕴", + "man_with_gua_pi_mao": "👲", + "man_with_turban": "👳", + "mans_shoe": "👞", + "shoe": "👞", + "menorah_with_nine_branches": "🕎", + "mens": "🚹", + "minidisc": "💽", + "iphone": "📱", + "calling": "📲", + "money__mouth_face": "🤑", + "moneybag": "💰", + "rice_scene": "🎑", + "mountain_bicyclist": "🚵", + "mouse2": "🐁", + "lips": "👄", + "moyai": "🗿", + "notes": "🎶", + "nail_care": "💅", + "ab": "🆎", + "negative_squared_cross_mark": "❎", + "a": "🅰", + "b": "🅱", + "o2": "🅾", + "parking": "🅿", + "new_moon_with_face": "🌚", + "no_entry_sign": "🚫", + "underage": "🔞", + "non__potable_water": "🚱", + "arrow_upper_right": "↗", + "arrow_upper_left": "↖", + "office": "🏢", + "older_man": "👴", + "older_woman": "👵", + "om_symbol": "🕉", + "on": "🔛", + "book": "📖", + "unlock": "🔓", + "mailbox_with_no_mail": "📭", + "mailbox_with_mail": "📬", + "cd": "💿", + "tada": "🎉", + "feet": "🐾", + "walking": "🚶", + "pencil2": "✏", + "pensive": "😔", + "persevere": "😣", + "bow": "🙇", + "raised_hands": "🙌", + "person_with_ball": "⛹", + "person_with_blond_hair": "👱", + "pray": "🙏", + "person_with_pouting_face": "🙎", + "computer": "💻", + "pig2": "🐖", + "hankey": "💩", + "poop": "💩", + "shit": "💩", + "bamboo": "🎍", + "gun": "🔫", + "black_joker": "🃏", + "rotating_light": "🚨", + "cop": "👮", + "stew": "🍲", + "pouch": "👝", + "pouting_cat": "😾", + "rage": "😡", + "put_litter_in_its_place": "🚮", + "rabbit2": "🐇", + "racing_motorcycle": "🏍", + "radioactive_sign": "☢", + "fist": "✊", + "hand": "✋", + "raised_hand_with_fingers_splayed": "🖐", + "raised_hand_with_part_between_middle_and_ring_fingers": "🖖", + "blue_car": "🚙", + "apple": "🍎", + "relieved": "😌", + "reversed_hand_with_middle_finger_extended": "🖕", + "mag_right": "🔎", + "arrow_right_hook": "↪", + "sweet_potato": "🍠", + "robot": "🤖", + "rolled__up_newspaper": "🗞", + "rowboat": "🚣", + "runner": "🏃", + "running": "🏃", + "running_shirt_with_sash": "🎽", + "boat": "⛵", + "scales": "⚖", + "school_satchel": "🎒", + "scorpius": "♏", + "see_no_evil": "🙈", + "sheep": "🐑", + "stars": "🌠", + "cake": "🍰", + "six_pointed_star": "🔯", + "ski": "🎿", + "sleeping_accommodation": "🛌", + "sleeping": "😴", + "sleepy": "😪", + "sleuth_or_spy": "🕵", + "heart_eyes_cat": "😻", + "smiley_cat": "😺", + "innocent": "😇", + "heart_eyes": "😍", + "smiling_imp": "😈", + "smiley": "😃", + "sweat_smile": "😅", + "smile": "😄", + "laughing": "😆", + "satisfied": "😆", + "blush": "😊", + "smirk": "😏", + "smoking": "🚬", + "snow_capped_mountain": "🏔", + "soccer": "⚽", + "icecream": "🍦", + "soon": "🔜", + "arrow_lower_right": "↘", + "arrow_lower_left": "↙", + "speak_no_evil": "🙊", + "speaker": "🔈", + "mute": "🔇", + "sound": "🔉", + "loud_sound": "🔊", + "speaking_head_in_silhouette": "🗣", + "spiral_calendar_pad": "🗓", + "spiral_note_pad": "🗒", + "shell": "🐚", + "sweat_drops": "💦", + "u5272": "🈹", + "u5408": "🈴", + "u55b6": "🈺", + "u6307": "🈯", + "u6708": "🈷", + "u6709": "🈶", + "u6e80": "🈵", + "u7121": "🈚", + "u7533": "🈸", + "u7981": "🈲", + "u7a7a": "🈳", + "cl": "🆑", + "cool": "🆒", + "free": "🆓", + "id": "🆔", + "koko": "🈁", + "sa": "🈂", + "new": "🆕", + "ng": "🆖", + "ok": "🆗", + "sos": "🆘", + "up": "🆙", + "vs": "🆚", + "steam_locomotive": "🚂", + "ramen": "🍜", + "partly_sunny": "⛅", + "city_sunrise": "🌇", + "surfer": "🏄", + "swimmer": "🏊", + "shirt": "👕", + "tshirt": "👕", + "table_tennis_paddle_and_ball": "🏓", + "tea": "🍵", + "tv": "📺", + "three_button_mouse": "🖱", + "+1": "👍", + "thumbsup": "👍", + "__1": "👎", + "-1": "👎", + "thumbsdown": "👎", + "thunder_cloud_and_rain": "⛈", + "tiger2": "🐅", + "tophat": "🎩", + "top": "🔝", + "tm": "™", + "train2": "🚆", + "triangular_flag_on_post": "🚩", + "trident": "🔱", + "twisted_rightwards_arrows": "🔀", + "unamused": "😒", + "small_red_triangle": "🔺", + "arrow_up_small": "🔼", + "arrow_up_down": "↕", + "upside__down_face": "🙃", + "arrow_up": "⬆", + "v": "✌", + "vhs": "📼", + "wc": "🚾", + "ocean": "🌊", + "waving_black_flag": "🏴", + "wave": "👋", + "waving_white_flag": "🏳", + "moon": "🌔", + "scream_cat": "🙀", + "weary": "😩", + "weight_lifter": "🏋", + "whale2": "🐋", + "wheelchair": "♿", + "point_down": "👇", + "grey_exclamation": "❕", + "white_frowning_face": "☹", + "white_check_mark": "✅", + "point_left": "👈", + "white_medium_small_square": "◽", + "star": "⭐", + "grey_question": "❔", + "point_right": "👉", + "relaxed": "☺", + "white_sun_behind_cloud": "🌥", + "white_sun_behind_cloud_with_rain": "🌦", + "white_sun_with_small_cloud": "🌤", + "point_up_2": "👆", + "point_up": "☝", + "wind_blowing_face": "🌬", + "wink": "😉", + "wolf": "🐺", + "dancers": "👯", + "boot": "👢", + "womans_clothes": "👚", + "womans_hat": "👒", + "sandal": "👡", + "womens": "🚺", + "worried": "😟", + "gift": "🎁", + "zipper__mouth_face": "🤐", + "regional_indicator_a": "🇦", + "regional_indicator_b": "🇧", + "regional_indicator_c": "🇨", + "regional_indicator_d": "🇩", + "regional_indicator_e": "🇪", + "regional_indicator_f": "🇫", + "regional_indicator_g": "🇬", + "regional_indicator_h": "🇭", + "regional_indicator_i": "🇮", + "regional_indicator_j": "🇯", + "regional_indicator_k": "🇰", + "regional_indicator_l": "🇱", + "regional_indicator_m": "🇲", + "regional_indicator_n": "🇳", + "regional_indicator_o": "🇴", + "regional_indicator_p": "🇵", + "regional_indicator_q": "🇶", + "regional_indicator_r": "🇷", + "regional_indicator_s": "🇸", + "regional_indicator_t": "🇹", + "regional_indicator_u": "🇺", + "regional_indicator_v": "🇻", + "regional_indicator_w": "🇼", + "regional_indicator_x": "🇽", + "regional_indicator_y": "🇾", + "regional_indicator_z": "🇿", +} diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_replace.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_replace.py new file mode 100644 index 00000000..bb2cafa1 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_replace.py @@ -0,0 +1,32 @@ +from typing import Callable, Match, Optional +import re + +from ._emoji_codes import EMOJI + + +_ReStringMatch = Match[str] # regex match object +_ReSubCallable = Callable[[_ReStringMatch], str] # Callable invoked by re.sub +_EmojiSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compiled re + + +def _emoji_replace( + text: str, + default_variant: Optional[str] = None, + _emoji_sub: _EmojiSubMethod = re.compile(r"(:(\S*?)(?:(?:\-)(emoji|text))?:)").sub, +) -> str: + """Replace emoji code in text.""" + get_emoji = EMOJI.__getitem__ + variants = {"text": "\uFE0E", "emoji": "\uFE0F"} + get_variant = variants.get + default_variant_code = variants.get(default_variant, "") if default_variant else "" + + def do_replace(match: Match[str]) -> str: + emoji_code, emoji_name, variant = match.groups() + try: + return get_emoji(emoji_name.lower()) + get_variant( + variant, default_variant_code + ) + except KeyError: + return emoji_code + + return _emoji_sub(do_replace, text) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_export_format.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_export_format.py new file mode 100644 index 00000000..094d2dc2 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_export_format.py @@ -0,0 +1,76 @@ +CONSOLE_HTML_FORMAT = """\ + + + + + + + +
{code}
+ + +""" + +CONSOLE_SVG_FORMAT = """\ + + + + + + + + + {lines} + + + {chrome} + + {backgrounds} + + {matrix} + + + +""" + +_SVG_FONT_FAMILY = "Rich Fira Code" +_SVG_CLASSES_PREFIX = "rich-svg" diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_extension.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_extension.py new file mode 100644 index 00000000..cbd6da9b --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_extension.py @@ -0,0 +1,10 @@ +from typing import Any + + +def load_ipython_extension(ip: Any) -> None: # pragma: no cover + # prevent circular import + from pip._vendor.rich.pretty import install + from pip._vendor.rich.traceback import install as tr_install + + install() + tr_install() diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_fileno.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_fileno.py new file mode 100644 index 00000000..b17ee651 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_fileno.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import IO, Callable + + +def get_fileno(file_like: IO[str]) -> int | None: + """Get fileno() from a file, accounting for poorly implemented file-like objects. + + Args: + file_like (IO): A file-like object. + + Returns: + int | None: The result of fileno if available, or None if operation failed. + """ + fileno: Callable[[], int] | None = getattr(file_like, "fileno", None) + if fileno is not None: + try: + return fileno() + except Exception: + # `fileno` is documented as potentially raising a OSError + # Alas, from the issues, there are so many poorly implemented file-like objects, + # that `fileno()` can raise just about anything. + return None + return None diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_inspect.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_inspect.py new file mode 100644 index 00000000..30446ceb --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_inspect.py @@ -0,0 +1,270 @@ +from __future__ import absolute_import + +import inspect +from inspect import cleandoc, getdoc, getfile, isclass, ismodule, signature +from typing import Any, Collection, Iterable, Optional, Tuple, Type, Union + +from .console import Group, RenderableType +from .control import escape_control_codes +from .highlighter import ReprHighlighter +from .jupyter import JupyterMixin +from .panel import Panel +from .pretty import Pretty +from .table import Table +from .text import Text, TextType + + +def _first_paragraph(doc: str) -> str: + """Get the first paragraph from a docstring.""" + paragraph, _, _ = doc.partition("\n\n") + return paragraph + + +class Inspect(JupyterMixin): + """A renderable to inspect any Python Object. + + Args: + obj (Any): An object to inspect. + title (str, optional): Title to display over inspect result, or None use type. Defaults to None. + help (bool, optional): Show full help text rather than just first paragraph. Defaults to False. + methods (bool, optional): Enable inspection of callables. Defaults to False. + docs (bool, optional): Also render doc strings. Defaults to True. + private (bool, optional): Show private attributes (beginning with underscore). Defaults to False. + dunder (bool, optional): Show attributes starting with double underscore. Defaults to False. + sort (bool, optional): Sort attributes alphabetically. Defaults to True. + all (bool, optional): Show all attributes. Defaults to False. + value (bool, optional): Pretty print value of object. Defaults to True. + """ + + def __init__( + self, + obj: Any, + *, + title: Optional[TextType] = None, + help: bool = False, + methods: bool = False, + docs: bool = True, + private: bool = False, + dunder: bool = False, + sort: bool = True, + all: bool = True, + value: bool = True, + ) -> None: + self.highlighter = ReprHighlighter() + self.obj = obj + self.title = title or self._make_title(obj) + if all: + methods = private = dunder = True + self.help = help + self.methods = methods + self.docs = docs or help + self.private = private or dunder + self.dunder = dunder + self.sort = sort + self.value = value + + def _make_title(self, obj: Any) -> Text: + """Make a default title.""" + title_str = ( + str(obj) + if (isclass(obj) or callable(obj) or ismodule(obj)) + else str(type(obj)) + ) + title_text = self.highlighter(title_str) + return title_text + + def __rich__(self) -> Panel: + return Panel.fit( + Group(*self._render()), + title=self.title, + border_style="scope.border", + padding=(0, 1), + ) + + def _get_signature(self, name: str, obj: Any) -> Optional[Text]: + """Get a signature for a callable.""" + try: + _signature = str(signature(obj)) + ":" + except ValueError: + _signature = "(...)" + except TypeError: + return None + + source_filename: Optional[str] = None + try: + source_filename = getfile(obj) + except (OSError, TypeError): + # OSError is raised if obj has no source file, e.g. when defined in REPL. + pass + + callable_name = Text(name, style="inspect.callable") + if source_filename: + callable_name.stylize(f"link file://{source_filename}") + signature_text = self.highlighter(_signature) + + qualname = name or getattr(obj, "__qualname__", name) + + # If obj is a module, there may be classes (which are callable) to display + if inspect.isclass(obj): + prefix = "class" + elif inspect.iscoroutinefunction(obj): + prefix = "async def" + else: + prefix = "def" + + qual_signature = Text.assemble( + (f"{prefix} ", f"inspect.{prefix.replace(' ', '_')}"), + (qualname, "inspect.callable"), + signature_text, + ) + + return qual_signature + + def _render(self) -> Iterable[RenderableType]: + """Render object.""" + + def sort_items(item: Tuple[str, Any]) -> Tuple[bool, str]: + key, (_error, value) = item + return (callable(value), key.strip("_").lower()) + + def safe_getattr(attr_name: str) -> Tuple[Any, Any]: + """Get attribute or any exception.""" + try: + return (None, getattr(obj, attr_name)) + except Exception as error: + return (error, None) + + obj = self.obj + keys = dir(obj) + total_items = len(keys) + if not self.dunder: + keys = [key for key in keys if not key.startswith("__")] + if not self.private: + keys = [key for key in keys if not key.startswith("_")] + not_shown_count = total_items - len(keys) + items = [(key, safe_getattr(key)) for key in keys] + if self.sort: + items.sort(key=sort_items) + + items_table = Table.grid(padding=(0, 1), expand=False) + items_table.add_column(justify="right") + add_row = items_table.add_row + highlighter = self.highlighter + + if callable(obj): + signature = self._get_signature("", obj) + if signature is not None: + yield signature + yield "" + + if self.docs: + _doc = self._get_formatted_doc(obj) + if _doc is not None: + doc_text = Text(_doc, style="inspect.help") + doc_text = highlighter(doc_text) + yield doc_text + yield "" + + if self.value and not (isclass(obj) or callable(obj) or ismodule(obj)): + yield Panel( + Pretty(obj, indent_guides=True, max_length=10, max_string=60), + border_style="inspect.value.border", + ) + yield "" + + for key, (error, value) in items: + key_text = Text.assemble( + ( + key, + "inspect.attr.dunder" if key.startswith("__") else "inspect.attr", + ), + (" =", "inspect.equals"), + ) + if error is not None: + warning = key_text.copy() + warning.stylize("inspect.error") + add_row(warning, highlighter(repr(error))) + continue + + if callable(value): + if not self.methods: + continue + + _signature_text = self._get_signature(key, value) + if _signature_text is None: + add_row(key_text, Pretty(value, highlighter=highlighter)) + else: + if self.docs: + docs = self._get_formatted_doc(value) + if docs is not None: + _signature_text.append("\n" if "\n" in docs else " ") + doc = highlighter(docs) + doc.stylize("inspect.doc") + _signature_text.append(doc) + + add_row(key_text, _signature_text) + else: + add_row(key_text, Pretty(value, highlighter=highlighter)) + if items_table.row_count: + yield items_table + elif not_shown_count: + yield Text.from_markup( + f"[b cyan]{not_shown_count}[/][i] attribute(s) not shown.[/i] " + f"Run [b][magenta]inspect[/]([not b]inspect[/])[/b] for options." + ) + + def _get_formatted_doc(self, object_: Any) -> Optional[str]: + """ + Extract the docstring of an object, process it and returns it. + The processing consists in cleaning up the doctring's indentation, + taking only its 1st paragraph if `self.help` is not True, + and escape its control codes. + + Args: + object_ (Any): the object to get the docstring from. + + Returns: + Optional[str]: the processed docstring, or None if no docstring was found. + """ + docs = getdoc(object_) + if docs is None: + return None + docs = cleandoc(docs).strip() + if not self.help: + docs = _first_paragraph(docs) + return escape_control_codes(docs) + + +def get_object_types_mro(obj: Union[object, Type[Any]]) -> Tuple[type, ...]: + """Returns the MRO of an object's class, or of the object itself if it's a class.""" + if not hasattr(obj, "__mro__"): + # N.B. we cannot use `if type(obj) is type` here because it doesn't work with + # some types of classes, such as the ones that use abc.ABCMeta. + obj = type(obj) + return getattr(obj, "__mro__", ()) + + +def get_object_types_mro_as_strings(obj: object) -> Collection[str]: + """ + Returns the MRO of an object's class as full qualified names, or of the object itself if it's a class. + + Examples: + `object_types_mro_as_strings(JSONDecoder)` will return `['json.decoder.JSONDecoder', 'builtins.object']` + """ + return [ + f'{getattr(type_, "__module__", "")}.{getattr(type_, "__qualname__", "")}' + for type_ in get_object_types_mro(obj) + ] + + +def is_object_one_of_types( + obj: object, fully_qualified_types_names: Collection[str] +) -> bool: + """ + Returns `True` if the given object's class (or the object itself, if it's a class) has one of the + fully qualified names in its MRO. + """ + for type_name in get_object_types_mro_as_strings(obj): + if type_name in fully_qualified_types_names: + return True + return False diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_log_render.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_log_render.py new file mode 100644 index 00000000..fc16c844 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_log_render.py @@ -0,0 +1,94 @@ +from datetime import datetime +from typing import Iterable, List, Optional, TYPE_CHECKING, Union, Callable + + +from .text import Text, TextType + +if TYPE_CHECKING: + from .console import Console, ConsoleRenderable, RenderableType + from .table import Table + +FormatTimeCallable = Callable[[datetime], Text] + + +class LogRender: + def __init__( + self, + show_time: bool = True, + show_level: bool = False, + show_path: bool = True, + time_format: Union[str, FormatTimeCallable] = "[%x %X]", + omit_repeated_times: bool = True, + level_width: Optional[int] = 8, + ) -> None: + self.show_time = show_time + self.show_level = show_level + self.show_path = show_path + self.time_format = time_format + self.omit_repeated_times = omit_repeated_times + self.level_width = level_width + self._last_time: Optional[Text] = None + + def __call__( + self, + console: "Console", + renderables: Iterable["ConsoleRenderable"], + log_time: Optional[datetime] = None, + time_format: Optional[Union[str, FormatTimeCallable]] = None, + level: TextType = "", + path: Optional[str] = None, + line_no: Optional[int] = None, + link_path: Optional[str] = None, + ) -> "Table": + from .containers import Renderables + from .table import Table + + output = Table.grid(padding=(0, 1)) + output.expand = True + if self.show_time: + output.add_column(style="log.time") + if self.show_level: + output.add_column(style="log.level", width=self.level_width) + output.add_column(ratio=1, style="log.message", overflow="fold") + if self.show_path and path: + output.add_column(style="log.path") + row: List["RenderableType"] = [] + if self.show_time: + log_time = log_time or console.get_datetime() + time_format = time_format or self.time_format + if callable(time_format): + log_time_display = time_format(log_time) + else: + log_time_display = Text(log_time.strftime(time_format)) + if log_time_display == self._last_time and self.omit_repeated_times: + row.append(Text(" " * len(log_time_display))) + else: + row.append(log_time_display) + self._last_time = log_time_display + if self.show_level: + row.append(level) + + row.append(Renderables(renderables)) + if self.show_path and path: + path_text = Text() + path_text.append( + path, style=f"link file://{link_path}" if link_path else "" + ) + if line_no: + path_text.append(":") + path_text.append( + f"{line_no}", + style=f"link file://{link_path}#{line_no}" if link_path else "", + ) + row.append(path_text) + + output.add_row(*row) + return output + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + + c = Console() + c.print("[on blue]Hello", justify="right") + c.log("[on blue]hello", justify="right") diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_loop.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_loop.py new file mode 100644 index 00000000..01c6cafb --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_loop.py @@ -0,0 +1,43 @@ +from typing import Iterable, Tuple, TypeVar + +T = TypeVar("T") + + +def loop_first(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for first value.""" + iter_values = iter(values) + try: + value = next(iter_values) + except StopIteration: + return + yield True, value + for value in iter_values: + yield False, value + + +def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value + + +def loop_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]: + """Iterate and generate a tuple with a flag for first and last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + first = True + for value in iter_values: + yield first, False, previous_value + first = False + previous_value = value + yield first, True, previous_value diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_null_file.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_null_file.py new file mode 100644 index 00000000..b659673e --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_null_file.py @@ -0,0 +1,69 @@ +from types import TracebackType +from typing import IO, Iterable, Iterator, List, Optional, Type + + +class NullFile(IO[str]): + def close(self) -> None: + pass + + def isatty(self) -> bool: + return False + + def read(self, __n: int = 1) -> str: + return "" + + def readable(self) -> bool: + return False + + def readline(self, __limit: int = 1) -> str: + return "" + + def readlines(self, __hint: int = 1) -> List[str]: + return [] + + def seek(self, __offset: int, __whence: int = 1) -> int: + return 0 + + def seekable(self) -> bool: + return False + + def tell(self) -> int: + return 0 + + def truncate(self, __size: Optional[int] = 1) -> int: + return 0 + + def writable(self) -> bool: + return False + + def writelines(self, __lines: Iterable[str]) -> None: + pass + + def __next__(self) -> str: + return "" + + def __iter__(self) -> Iterator[str]: + return iter([""]) + + def __enter__(self) -> IO[str]: + pass + + def __exit__( + self, + __t: Optional[Type[BaseException]], + __value: Optional[BaseException], + __traceback: Optional[TracebackType], + ) -> None: + pass + + def write(self, text: str) -> int: + return 0 + + def flush(self) -> None: + pass + + def fileno(self) -> int: + return -1 + + +NULL_FILE = NullFile() diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_palettes.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_palettes.py new file mode 100644 index 00000000..3c748d33 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_palettes.py @@ -0,0 +1,309 @@ +from .palette import Palette + + +# Taken from https://en.wikipedia.org/wiki/ANSI_escape_code (Windows 10 column) +WINDOWS_PALETTE = Palette( + [ + (12, 12, 12), + (197, 15, 31), + (19, 161, 14), + (193, 156, 0), + (0, 55, 218), + (136, 23, 152), + (58, 150, 221), + (204, 204, 204), + (118, 118, 118), + (231, 72, 86), + (22, 198, 12), + (249, 241, 165), + (59, 120, 255), + (180, 0, 158), + (97, 214, 214), + (242, 242, 242), + ] +) + +# # The standard ansi colors (including bright variants) +STANDARD_PALETTE = Palette( + [ + (0, 0, 0), + (170, 0, 0), + (0, 170, 0), + (170, 85, 0), + (0, 0, 170), + (170, 0, 170), + (0, 170, 170), + (170, 170, 170), + (85, 85, 85), + (255, 85, 85), + (85, 255, 85), + (255, 255, 85), + (85, 85, 255), + (255, 85, 255), + (85, 255, 255), + (255, 255, 255), + ] +) + + +# The 256 color palette +EIGHT_BIT_PALETTE = Palette( + [ + (0, 0, 0), + (128, 0, 0), + (0, 128, 0), + (128, 128, 0), + (0, 0, 128), + (128, 0, 128), + (0, 128, 128), + (192, 192, 192), + (128, 128, 128), + (255, 0, 0), + (0, 255, 0), + (255, 255, 0), + (0, 0, 255), + (255, 0, 255), + (0, 255, 255), + (255, 255, 255), + (0, 0, 0), + (0, 0, 95), + (0, 0, 135), + (0, 0, 175), + (0, 0, 215), + (0, 0, 255), + (0, 95, 0), + (0, 95, 95), + (0, 95, 135), + (0, 95, 175), + (0, 95, 215), + (0, 95, 255), + (0, 135, 0), + (0, 135, 95), + (0, 135, 135), + (0, 135, 175), + (0, 135, 215), + (0, 135, 255), + (0, 175, 0), + (0, 175, 95), + (0, 175, 135), + (0, 175, 175), + (0, 175, 215), + (0, 175, 255), + (0, 215, 0), + (0, 215, 95), + (0, 215, 135), + (0, 215, 175), + (0, 215, 215), + (0, 215, 255), + (0, 255, 0), + (0, 255, 95), + (0, 255, 135), + (0, 255, 175), + (0, 255, 215), + (0, 255, 255), + (95, 0, 0), + (95, 0, 95), + (95, 0, 135), + (95, 0, 175), + (95, 0, 215), + (95, 0, 255), + (95, 95, 0), + (95, 95, 95), + (95, 95, 135), + (95, 95, 175), + (95, 95, 215), + (95, 95, 255), + (95, 135, 0), + (95, 135, 95), + (95, 135, 135), + (95, 135, 175), + (95, 135, 215), + (95, 135, 255), + (95, 175, 0), + (95, 175, 95), + (95, 175, 135), + (95, 175, 175), + (95, 175, 215), + (95, 175, 255), + (95, 215, 0), + (95, 215, 95), + (95, 215, 135), + (95, 215, 175), + (95, 215, 215), + (95, 215, 255), + (95, 255, 0), + (95, 255, 95), + (95, 255, 135), + (95, 255, 175), + (95, 255, 215), + (95, 255, 255), + (135, 0, 0), + (135, 0, 95), + (135, 0, 135), + (135, 0, 175), + (135, 0, 215), + (135, 0, 255), + (135, 95, 0), + (135, 95, 95), + (135, 95, 135), + (135, 95, 175), + (135, 95, 215), + (135, 95, 255), + (135, 135, 0), + (135, 135, 95), + (135, 135, 135), + (135, 135, 175), + (135, 135, 215), + (135, 135, 255), + (135, 175, 0), + (135, 175, 95), + (135, 175, 135), + (135, 175, 175), + (135, 175, 215), + (135, 175, 255), + (135, 215, 0), + (135, 215, 95), + (135, 215, 135), + (135, 215, 175), + (135, 215, 215), + (135, 215, 255), + (135, 255, 0), + (135, 255, 95), + (135, 255, 135), + (135, 255, 175), + (135, 255, 215), + (135, 255, 255), + (175, 0, 0), + (175, 0, 95), + (175, 0, 135), + (175, 0, 175), + (175, 0, 215), + (175, 0, 255), + (175, 95, 0), + (175, 95, 95), + (175, 95, 135), + (175, 95, 175), + (175, 95, 215), + (175, 95, 255), + (175, 135, 0), + (175, 135, 95), + (175, 135, 135), + (175, 135, 175), + (175, 135, 215), + (175, 135, 255), + (175, 175, 0), + (175, 175, 95), + (175, 175, 135), + (175, 175, 175), + (175, 175, 215), + (175, 175, 255), + (175, 215, 0), + (175, 215, 95), + (175, 215, 135), + (175, 215, 175), + (175, 215, 215), + (175, 215, 255), + (175, 255, 0), + (175, 255, 95), + (175, 255, 135), + (175, 255, 175), + (175, 255, 215), + (175, 255, 255), + (215, 0, 0), + (215, 0, 95), + (215, 0, 135), + (215, 0, 175), + (215, 0, 215), + (215, 0, 255), + (215, 95, 0), + (215, 95, 95), + (215, 95, 135), + (215, 95, 175), + (215, 95, 215), + (215, 95, 255), + (215, 135, 0), + (215, 135, 95), + (215, 135, 135), + (215, 135, 175), + (215, 135, 215), + (215, 135, 255), + (215, 175, 0), + (215, 175, 95), + (215, 175, 135), + (215, 175, 175), + (215, 175, 215), + (215, 175, 255), + (215, 215, 0), + (215, 215, 95), + (215, 215, 135), + (215, 215, 175), + (215, 215, 215), + (215, 215, 255), + (215, 255, 0), + (215, 255, 95), + (215, 255, 135), + (215, 255, 175), + (215, 255, 215), + (215, 255, 255), + (255, 0, 0), + (255, 0, 95), + (255, 0, 135), + (255, 0, 175), + (255, 0, 215), + (255, 0, 255), + (255, 95, 0), + (255, 95, 95), + (255, 95, 135), + (255, 95, 175), + (255, 95, 215), + (255, 95, 255), + (255, 135, 0), + (255, 135, 95), + (255, 135, 135), + (255, 135, 175), + (255, 135, 215), + (255, 135, 255), + (255, 175, 0), + (255, 175, 95), + (255, 175, 135), + (255, 175, 175), + (255, 175, 215), + (255, 175, 255), + (255, 215, 0), + (255, 215, 95), + (255, 215, 135), + (255, 215, 175), + (255, 215, 215), + (255, 215, 255), + (255, 255, 0), + (255, 255, 95), + (255, 255, 135), + (255, 255, 175), + (255, 255, 215), + (255, 255, 255), + (8, 8, 8), + (18, 18, 18), + (28, 28, 28), + (38, 38, 38), + (48, 48, 48), + (58, 58, 58), + (68, 68, 68), + (78, 78, 78), + (88, 88, 88), + (98, 98, 98), + (108, 108, 108), + (118, 118, 118), + (128, 128, 128), + (138, 138, 138), + (148, 148, 148), + (158, 158, 158), + (168, 168, 168), + (178, 178, 178), + (188, 188, 188), + (198, 198, 198), + (208, 208, 208), + (218, 218, 218), + (228, 228, 228), + (238, 238, 238), + ] +) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_pick.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_pick.py new file mode 100644 index 00000000..4f6d8b2d --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_pick.py @@ -0,0 +1,17 @@ +from typing import Optional + + +def pick_bool(*values: Optional[bool]) -> bool: + """Pick the first non-none bool or return the last value. + + Args: + *values (bool): Any number of boolean or None values. + + Returns: + bool: First non-none boolean. + """ + assert values, "1 or more values required" + for value in values: + if value is not None: + return value + return bool(value) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_ratio.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_ratio.py new file mode 100644 index 00000000..e8a3a674 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_ratio.py @@ -0,0 +1,160 @@ +import sys +from fractions import Fraction +from math import ceil +from typing import cast, List, Optional, Sequence + +if sys.version_info >= (3, 8): + from typing import Protocol +else: + from pip._vendor.typing_extensions import Protocol # pragma: no cover + + +class Edge(Protocol): + """Any object that defines an edge (such as Layout).""" + + size: Optional[int] = None + ratio: int = 1 + minimum_size: int = 1 + + +def ratio_resolve(total: int, edges: Sequence[Edge]) -> List[int]: + """Divide total space to satisfy size, ratio, and minimum_size, constraints. + + The returned list of integers should add up to total in most cases, unless it is + impossible to satisfy all the constraints. For instance, if there are two edges + with a minimum size of 20 each and `total` is 30 then the returned list will be + greater than total. In practice, this would mean that a Layout object would + clip the rows that would overflow the screen height. + + Args: + total (int): Total number of characters. + edges (List[Edge]): Edges within total space. + + Returns: + List[int]: Number of characters for each edge. + """ + # Size of edge or None for yet to be determined + sizes = [(edge.size or None) for edge in edges] + + _Fraction = Fraction + + # While any edges haven't been calculated + while None in sizes: + # Get flexible edges and index to map these back on to sizes list + flexible_edges = [ + (index, edge) + for index, (size, edge) in enumerate(zip(sizes, edges)) + if size is None + ] + # Remaining space in total + remaining = total - sum(size or 0 for size in sizes) + if remaining <= 0: + # No room for flexible edges + return [ + ((edge.minimum_size or 1) if size is None else size) + for size, edge in zip(sizes, edges) + ] + # Calculate number of characters in a ratio portion + portion = _Fraction( + remaining, sum((edge.ratio or 1) for _, edge in flexible_edges) + ) + + # If any edges will be less than their minimum, replace size with the minimum + for index, edge in flexible_edges: + if portion * edge.ratio <= edge.minimum_size: + sizes[index] = edge.minimum_size + # New fixed size will invalidate calculations, so we need to repeat the process + break + else: + # Distribute flexible space and compensate for rounding error + # Since edge sizes can only be integers we need to add the remainder + # to the following line + remainder = _Fraction(0) + for index, edge in flexible_edges: + size, remainder = divmod(portion * edge.ratio + remainder, 1) + sizes[index] = size + break + # Sizes now contains integers only + return cast(List[int], sizes) + + +def ratio_reduce( + total: int, ratios: List[int], maximums: List[int], values: List[int] +) -> List[int]: + """Divide an integer total in to parts based on ratios. + + Args: + total (int): The total to divide. + ratios (List[int]): A list of integer ratios. + maximums (List[int]): List of maximums values for each slot. + values (List[int]): List of values + + Returns: + List[int]: A list of integers guaranteed to sum to total. + """ + ratios = [ratio if _max else 0 for ratio, _max in zip(ratios, maximums)] + total_ratio = sum(ratios) + if not total_ratio: + return values[:] + total_remaining = total + result: List[int] = [] + append = result.append + for ratio, maximum, value in zip(ratios, maximums, values): + if ratio and total_ratio > 0: + distributed = min(maximum, round(ratio * total_remaining / total_ratio)) + append(value - distributed) + total_remaining -= distributed + total_ratio -= ratio + else: + append(value) + return result + + +def ratio_distribute( + total: int, ratios: List[int], minimums: Optional[List[int]] = None +) -> List[int]: + """Distribute an integer total in to parts based on ratios. + + Args: + total (int): The total to divide. + ratios (List[int]): A list of integer ratios. + minimums (List[int]): List of minimum values for each slot. + + Returns: + List[int]: A list of integers guaranteed to sum to total. + """ + if minimums: + ratios = [ratio if _min else 0 for ratio, _min in zip(ratios, minimums)] + total_ratio = sum(ratios) + assert total_ratio > 0, "Sum of ratios must be > 0" + + total_remaining = total + distributed_total: List[int] = [] + append = distributed_total.append + if minimums is None: + _minimums = [0] * len(ratios) + else: + _minimums = minimums + for ratio, minimum in zip(ratios, _minimums): + if total_ratio > 0: + distributed = max(minimum, ceil(ratio * total_remaining / total_ratio)) + else: + distributed = total_remaining + append(distributed) + total_ratio -= ratio + total_remaining -= distributed + return distributed_total + + +if __name__ == "__main__": + from dataclasses import dataclass + + @dataclass + class E: + + size: Optional[int] = None + ratio: int = 1 + minimum_size: int = 1 + + resolved = ratio_resolve(110, [E(None, 1, 1), E(None, 1, 1), E(None, 1, 1)]) + print(sum(resolved)) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_spinners.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_spinners.py new file mode 100644 index 00000000..d0bb1fe7 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_spinners.py @@ -0,0 +1,482 @@ +""" +Spinners are from: +* cli-spinners: + MIT License + Copyright (c) Sindre Sorhus (sindresorhus.com) + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +""" + +SPINNERS = { + "dots": { + "interval": 80, + "frames": "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏", + }, + "dots2": {"interval": 80, "frames": "⣾⣽⣻⢿⡿⣟⣯⣷"}, + "dots3": { + "interval": 80, + "frames": "⠋⠙⠚⠞⠖⠦⠴⠲⠳⠓", + }, + "dots4": { + "interval": 80, + "frames": "⠄⠆⠇⠋⠙⠸⠰⠠⠰⠸⠙⠋⠇⠆", + }, + "dots5": { + "interval": 80, + "frames": "⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋", + }, + "dots6": { + "interval": 80, + "frames": "⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁", + }, + "dots7": { + "interval": 80, + "frames": "⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈", + }, + "dots8": { + "interval": 80, + "frames": "⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈", + }, + "dots9": {"interval": 80, "frames": "⢹⢺⢼⣸⣇⡧⡗⡏"}, + "dots10": {"interval": 80, "frames": "⢄⢂⢁⡁⡈⡐⡠"}, + "dots11": {"interval": 100, "frames": "⠁⠂⠄⡀⢀⠠⠐⠈"}, + "dots12": { + "interval": 80, + "frames": [ + "⢀⠀", + "⡀⠀", + "⠄⠀", + "⢂⠀", + "⡂⠀", + "⠅⠀", + "⢃⠀", + "⡃⠀", + "⠍⠀", + "⢋⠀", + "⡋⠀", + "⠍⠁", + "⢋⠁", + "⡋⠁", + "⠍⠉", + "⠋⠉", + "⠋⠉", + "⠉⠙", + "⠉⠙", + "⠉⠩", + "⠈⢙", + "⠈⡙", + "⢈⠩", + "⡀⢙", + "⠄⡙", + "⢂⠩", + "⡂⢘", + "⠅⡘", + "⢃⠨", + "⡃⢐", + "⠍⡐", + "⢋⠠", + "⡋⢀", + "⠍⡁", + "⢋⠁", + "⡋⠁", + "⠍⠉", + "⠋⠉", + "⠋⠉", + "⠉⠙", + "⠉⠙", + "⠉⠩", + "⠈⢙", + "⠈⡙", + "⠈⠩", + "⠀⢙", + "⠀⡙", + "⠀⠩", + "⠀⢘", + "⠀⡘", + "⠀⠨", + "⠀⢐", + "⠀⡐", + "⠀⠠", + "⠀⢀", + "⠀⡀", + ], + }, + "dots8Bit": { + "interval": 80, + "frames": "⠀⠁⠂⠃⠄⠅⠆⠇⡀⡁⡂⡃⡄⡅⡆⡇⠈⠉⠊⠋⠌⠍⠎⠏⡈⡉⡊⡋⡌⡍⡎⡏⠐⠑⠒⠓⠔⠕⠖⠗⡐⡑⡒⡓⡔⡕⡖⡗⠘⠙⠚⠛⠜⠝⠞⠟⡘⡙" + "⡚⡛⡜⡝⡞⡟⠠⠡⠢⠣⠤⠥⠦⠧⡠⡡⡢⡣⡤⡥⡦⡧⠨⠩⠪⠫⠬⠭⠮⠯⡨⡩⡪⡫⡬⡭⡮⡯⠰⠱⠲⠳⠴⠵⠶⠷⡰⡱⡲⡳⡴⡵⡶⡷⠸⠹⠺⠻" + "⠼⠽⠾⠿⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⣀⣁⣂⣃⣄⣅⣆⣇⢈⢉⢊⢋⢌⢍⢎⢏⣈⣉⣊⣋⣌⣍⣎⣏⢐⢑⢒⢓⢔⢕⢖⢗⣐⣑⣒⣓⣔⣕" + "⣖⣗⢘⢙⢚⢛⢜⢝⢞⢟⣘⣙⣚⣛⣜⣝⣞⣟⢠⢡⢢⢣⢤⢥⢦⢧⣠⣡⣢⣣⣤⣥⣦⣧⢨⢩⢪⢫⢬⢭⢮⢯⣨⣩⣪⣫⣬⣭⣮⣯⢰⢱⢲⢳⢴⢵⢶⢷" + "⣰⣱⣲⣳⣴⣵⣶⣷⢸⢹⢺⢻⢼⢽⢾⢿⣸⣹⣺⣻⣼⣽⣾⣿", + }, + "line": {"interval": 130, "frames": ["-", "\\", "|", "/"]}, + "line2": {"interval": 100, "frames": "⠂-–—–-"}, + "pipe": {"interval": 100, "frames": "┤┘┴└├┌┬┐"}, + "simpleDots": {"interval": 400, "frames": [". ", ".. ", "...", " "]}, + "simpleDotsScrolling": { + "interval": 200, + "frames": [". ", ".. ", "...", " ..", " .", " "], + }, + "star": {"interval": 70, "frames": "✶✸✹✺✹✷"}, + "star2": {"interval": 80, "frames": "+x*"}, + "flip": { + "interval": 70, + "frames": "___-``'´-___", + }, + "hamburger": {"interval": 100, "frames": "☱☲☴"}, + "growVertical": { + "interval": 120, + "frames": "▁▃▄▅▆▇▆▅▄▃", + }, + "growHorizontal": { + "interval": 120, + "frames": "▏▎▍▌▋▊▉▊▋▌▍▎", + }, + "balloon": {"interval": 140, "frames": " .oO@* "}, + "balloon2": {"interval": 120, "frames": ".oO°Oo."}, + "noise": {"interval": 100, "frames": "▓▒░"}, + "bounce": {"interval": 120, "frames": "⠁⠂⠄⠂"}, + "boxBounce": {"interval": 120, "frames": "▖▘▝▗"}, + "boxBounce2": {"interval": 100, "frames": "▌▀▐▄"}, + "triangle": {"interval": 50, "frames": "◢◣◤◥"}, + "arc": {"interval": 100, "frames": "◜◠◝◞◡◟"}, + "circle": {"interval": 120, "frames": "◡⊙◠"}, + "squareCorners": {"interval": 180, "frames": "◰◳◲◱"}, + "circleQuarters": {"interval": 120, "frames": "◴◷◶◵"}, + "circleHalves": {"interval": 50, "frames": "◐◓◑◒"}, + "squish": {"interval": 100, "frames": "╫╪"}, + "toggle": {"interval": 250, "frames": "⊶⊷"}, + "toggle2": {"interval": 80, "frames": "▫▪"}, + "toggle3": {"interval": 120, "frames": "□■"}, + "toggle4": {"interval": 100, "frames": "■□▪▫"}, + "toggle5": {"interval": 100, "frames": "▮▯"}, + "toggle6": {"interval": 300, "frames": "ဝ၀"}, + "toggle7": {"interval": 80, "frames": "⦾⦿"}, + "toggle8": {"interval": 100, "frames": "◍◌"}, + "toggle9": {"interval": 100, "frames": "◉◎"}, + "toggle10": {"interval": 100, "frames": "㊂㊀㊁"}, + "toggle11": {"interval": 50, "frames": "⧇⧆"}, + "toggle12": {"interval": 120, "frames": "☗☖"}, + "toggle13": {"interval": 80, "frames": "=*-"}, + "arrow": {"interval": 100, "frames": "←↖↑↗→↘↓↙"}, + "arrow2": { + "interval": 80, + "frames": ["⬆️ ", "↗️ ", "➡️ ", "↘️ ", "⬇️ ", "↙️ ", "⬅️ ", "↖️ "], + }, + "arrow3": { + "interval": 120, + "frames": ["▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"], + }, + "bouncingBar": { + "interval": 80, + "frames": [ + "[ ]", + "[= ]", + "[== ]", + "[=== ]", + "[ ===]", + "[ ==]", + "[ =]", + "[ ]", + "[ =]", + "[ ==]", + "[ ===]", + "[====]", + "[=== ]", + "[== ]", + "[= ]", + ], + }, + "bouncingBall": { + "interval": 80, + "frames": [ + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "( ●)", + "( ● )", + "( ● )", + "( ● )", + "( ● )", + "(● )", + ], + }, + "smiley": {"interval": 200, "frames": ["😄 ", "😝 "]}, + "monkey": {"interval": 300, "frames": ["🙈 ", "🙈 ", "🙉 ", "🙊 "]}, + "hearts": {"interval": 100, "frames": ["💛 ", "💙 ", "💜 ", "💚 ", "❤️ "]}, + "clock": { + "interval": 100, + "frames": [ + "🕛 ", + "🕐 ", + "🕑 ", + "🕒 ", + "🕓 ", + "🕔 ", + "🕕 ", + "🕖 ", + "🕗 ", + "🕘 ", + "🕙 ", + "🕚 ", + ], + }, + "earth": {"interval": 180, "frames": ["🌍 ", "🌎 ", "🌏 "]}, + "material": { + "interval": 17, + "frames": [ + "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "███████▁▁▁▁▁▁▁▁▁▁▁▁▁", + "████████▁▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "██████████▁▁▁▁▁▁▁▁▁▁", + "███████████▁▁▁▁▁▁▁▁▁", + "█████████████▁▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁▁██████████████▁▁▁▁", + "▁▁▁██████████████▁▁▁", + "▁▁▁▁█████████████▁▁▁", + "▁▁▁▁██████████████▁▁", + "▁▁▁▁██████████████▁▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁██████████████▁", + "▁▁▁▁▁▁██████████████", + "▁▁▁▁▁▁██████████████", + "▁▁▁▁▁▁▁█████████████", + "▁▁▁▁▁▁▁█████████████", + "▁▁▁▁▁▁▁▁████████████", + "▁▁▁▁▁▁▁▁████████████", + "▁▁▁▁▁▁▁▁▁███████████", + "▁▁▁▁▁▁▁▁▁███████████", + "▁▁▁▁▁▁▁▁▁▁██████████", + "▁▁▁▁▁▁▁▁▁▁██████████", + "▁▁▁▁▁▁▁▁▁▁▁▁████████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "██████▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "████████▁▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "█████████▁▁▁▁▁▁▁▁▁▁▁", + "███████████▁▁▁▁▁▁▁▁▁", + "████████████▁▁▁▁▁▁▁▁", + "████████████▁▁▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "██████████████▁▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁██████████████▁▁▁▁▁", + "▁▁▁█████████████▁▁▁▁", + "▁▁▁▁▁████████████▁▁▁", + "▁▁▁▁▁████████████▁▁▁", + "▁▁▁▁▁▁███████████▁▁▁", + "▁▁▁▁▁▁▁▁█████████▁▁▁", + "▁▁▁▁▁▁▁▁█████████▁▁▁", + "▁▁▁▁▁▁▁▁▁█████████▁▁", + "▁▁▁▁▁▁▁▁▁█████████▁▁", + "▁▁▁▁▁▁▁▁▁▁█████████▁", + "▁▁▁▁▁▁▁▁▁▁▁████████▁", + "▁▁▁▁▁▁▁▁▁▁▁████████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁███████▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁███████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + "▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁", + ], + }, + "moon": { + "interval": 80, + "frames": ["🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "], + }, + "runner": {"interval": 140, "frames": ["🚶 ", "🏃 "]}, + "pong": { + "interval": 80, + "frames": [ + "▐⠂ ▌", + "▐⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂▌", + "▐ ⠠▌", + "▐ ⡀▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐ ⠠ ▌", + "▐ ⠂ ▌", + "▐ ⠈ ▌", + "▐ ⠂ ▌", + "▐ ⠠ ▌", + "▐ ⡀ ▌", + "▐⠠ ▌", + ], + }, + "shark": { + "interval": 120, + "frames": [ + "▐|\\____________▌", + "▐_|\\___________▌", + "▐__|\\__________▌", + "▐___|\\_________▌", + "▐____|\\________▌", + "▐_____|\\_______▌", + "▐______|\\______▌", + "▐_______|\\_____▌", + "▐________|\\____▌", + "▐_________|\\___▌", + "▐__________|\\__▌", + "▐___________|\\_▌", + "▐____________|\\▌", + "▐____________/|▌", + "▐___________/|_▌", + "▐__________/|__▌", + "▐_________/|___▌", + "▐________/|____▌", + "▐_______/|_____▌", + "▐______/|______▌", + "▐_____/|_______▌", + "▐____/|________▌", + "▐___/|_________▌", + "▐__/|__________▌", + "▐_/|___________▌", + "▐/|____________▌", + ], + }, + "dqpb": {"interval": 100, "frames": "dqpb"}, + "weather": { + "interval": 100, + "frames": [ + "☀️ ", + "☀️ ", + "☀️ ", + "🌤 ", + "⛅️ ", + "🌥 ", + "☁️ ", + "🌧 ", + "🌨 ", + "🌧 ", + "🌨 ", + "🌧 ", + "🌨 ", + "⛈ ", + "🌨 ", + "🌧 ", + "🌨 ", + "☁️ ", + "🌥 ", + "⛅️ ", + "🌤 ", + "☀️ ", + "☀️ ", + ], + }, + "christmas": {"interval": 400, "frames": "🌲🎄"}, + "grenade": { + "interval": 80, + "frames": [ + "، ", + "′ ", + " ´ ", + " ‾ ", + " ⸌", + " ⸊", + " |", + " ⁎", + " ⁕", + " ෴ ", + " ⁓", + " ", + " ", + " ", + ], + }, + "point": {"interval": 125, "frames": ["∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"]}, + "layer": {"interval": 150, "frames": "-=≡"}, + "betaWave": { + "interval": 80, + "frames": [ + "ρββββββ", + "βρβββββ", + "ββρββββ", + "βββρβββ", + "ββββρββ", + "βββββρβ", + "ββββββρ", + ], + }, + "aesthetic": { + "interval": 80, + "frames": [ + "▰▱▱▱▱▱▱", + "▰▰▱▱▱▱▱", + "▰▰▰▱▱▱▱", + "▰▰▰▰▱▱▱", + "▰▰▰▰▰▱▱", + "▰▰▰▰▰▰▱", + "▰▰▰▰▰▰▰", + "▰▱▱▱▱▱▱", + ], + }, +} diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_stack.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_stack.py new file mode 100644 index 00000000..194564e7 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_stack.py @@ -0,0 +1,16 @@ +from typing import List, TypeVar + +T = TypeVar("T") + + +class Stack(List[T]): + """A small shim over builtin list.""" + + @property + def top(self) -> T: + """Get top of stack.""" + return self[-1] + + def push(self, item: T) -> None: + """Push an item on to the stack (append in stack nomenclature).""" + self.append(item) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_timer.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_timer.py new file mode 100644 index 00000000..a2ca6be0 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_timer.py @@ -0,0 +1,19 @@ +""" +Timer context manager, only used in debug. + +""" + +from time import time + +import contextlib +from typing import Generator + + +@contextlib.contextmanager +def timer(subject: str = "time") -> Generator[None, None, None]: + """print the elapsed time. (only used in debugging)""" + start = time() + yield + elapsed = time() - start + elapsed_ms = elapsed * 1000 + print(f"{subject} elapsed {elapsed_ms:.1f}ms") diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_win32_console.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_win32_console.py new file mode 100644 index 00000000..81b10829 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_win32_console.py @@ -0,0 +1,662 @@ +"""Light wrapper around the Win32 Console API - this module should only be imported on Windows + +The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions +""" +import ctypes +import sys +from typing import Any + +windll: Any = None +if sys.platform == "win32": + windll = ctypes.LibraryLoader(ctypes.WinDLL) +else: + raise ImportError(f"{__name__} can only be imported on Windows") + +import time +from ctypes import Structure, byref, wintypes +from typing import IO, NamedTuple, Type, cast + +from pip._vendor.rich.color import ColorSystem +from pip._vendor.rich.style import Style + +STDOUT = -11 +ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 + +COORD = wintypes._COORD + + +class LegacyWindowsError(Exception): + pass + + +class WindowsCoordinates(NamedTuple): + """Coordinates in the Windows Console API are (y, x), not (x, y). + This class is intended to prevent that confusion. + Rows and columns are indexed from 0. + This class can be used in place of wintypes._COORD in arguments and argtypes. + """ + + row: int + col: int + + @classmethod + def from_param(cls, value: "WindowsCoordinates") -> COORD: + """Converts a WindowsCoordinates into a wintypes _COORD structure. + This classmethod is internally called by ctypes to perform the conversion. + + Args: + value (WindowsCoordinates): The input coordinates to convert. + + Returns: + wintypes._COORD: The converted coordinates struct. + """ + return COORD(value.col, value.row) + + +class CONSOLE_SCREEN_BUFFER_INFO(Structure): + _fields_ = [ + ("dwSize", COORD), + ("dwCursorPosition", COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", COORD), + ] + + +class CONSOLE_CURSOR_INFO(ctypes.Structure): + _fields_ = [("dwSize", wintypes.DWORD), ("bVisible", wintypes.BOOL)] + + +_GetStdHandle = windll.kernel32.GetStdHandle +_GetStdHandle.argtypes = [ + wintypes.DWORD, +] +_GetStdHandle.restype = wintypes.HANDLE + + +def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE: + """Retrieves a handle to the specified standard device (standard input, standard output, or standard error). + + Args: + handle (int): Integer identifier for the handle. Defaults to -11 (stdout). + + Returns: + wintypes.HANDLE: The handle + """ + return cast(wintypes.HANDLE, _GetStdHandle(handle)) + + +_GetConsoleMode = windll.kernel32.GetConsoleMode +_GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD] +_GetConsoleMode.restype = wintypes.BOOL + + +def GetConsoleMode(std_handle: wintypes.HANDLE) -> int: + """Retrieves the current input mode of a console's input buffer + or the current output mode of a console screen buffer. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + + Raises: + LegacyWindowsError: If any error occurs while calling the Windows console API. + + Returns: + int: Value representing the current console mode as documented at + https://docs.microsoft.com/en-us/windows/console/getconsolemode#parameters + """ + + console_mode = wintypes.DWORD() + success = bool(_GetConsoleMode(std_handle, console_mode)) + if not success: + raise LegacyWindowsError("Unable to get legacy Windows Console Mode") + return console_mode.value + + +_FillConsoleOutputCharacterW = windll.kernel32.FillConsoleOutputCharacterW +_FillConsoleOutputCharacterW.argtypes = [ + wintypes.HANDLE, + ctypes.c_char, + wintypes.DWORD, + cast(Type[COORD], WindowsCoordinates), + ctypes.POINTER(wintypes.DWORD), +] +_FillConsoleOutputCharacterW.restype = wintypes.BOOL + + +def FillConsoleOutputCharacter( + std_handle: wintypes.HANDLE, + char: str, + length: int, + start: WindowsCoordinates, +) -> int: + """Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + char (str): The character to write. Must be a string of length 1. + length (int): The number of times to write the character. + start (WindowsCoordinates): The coordinates to start writing at. + + Returns: + int: The number of characters written. + """ + character = ctypes.c_char(char.encode()) + num_characters = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + _FillConsoleOutputCharacterW( + std_handle, + character, + num_characters, + start, + byref(num_written), + ) + return num_written.value + + +_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute +_FillConsoleOutputAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + wintypes.DWORD, + cast(Type[COORD], WindowsCoordinates), + ctypes.POINTER(wintypes.DWORD), +] +_FillConsoleOutputAttribute.restype = wintypes.BOOL + + +def FillConsoleOutputAttribute( + std_handle: wintypes.HANDLE, + attributes: int, + length: int, + start: WindowsCoordinates, +) -> int: + """Sets the character attributes for a specified number of character cells, + beginning at the specified coordinates in a screen buffer. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + attributes (int): Integer value representing the foreground and background colours of the cells. + length (int): The number of cells to set the output attribute of. + start (WindowsCoordinates): The coordinates of the first cell whose attributes are to be set. + + Returns: + int: The number of cells whose attributes were actually set. + """ + num_cells = wintypes.DWORD(length) + style_attrs = wintypes.WORD(attributes) + num_written = wintypes.DWORD(0) + _FillConsoleOutputAttribute( + std_handle, style_attrs, num_cells, start, byref(num_written) + ) + return num_written.value + + +_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute +_SetConsoleTextAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, +] +_SetConsoleTextAttribute.restype = wintypes.BOOL + + +def SetConsoleTextAttribute( + std_handle: wintypes.HANDLE, attributes: wintypes.WORD +) -> bool: + """Set the colour attributes for all text written after this function is called. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + attributes (int): Integer value representing the foreground and background colours. + + + Returns: + bool: True if the attribute was set successfully, otherwise False. + """ + return bool(_SetConsoleTextAttribute(std_handle, attributes)) + + +_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo +_GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO), +] +_GetConsoleScreenBufferInfo.restype = wintypes.BOOL + + +def GetConsoleScreenBufferInfo( + std_handle: wintypes.HANDLE, +) -> CONSOLE_SCREEN_BUFFER_INFO: + """Retrieves information about the specified console screen buffer. + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + + Returns: + CONSOLE_SCREEN_BUFFER_INFO: A CONSOLE_SCREEN_BUFFER_INFO ctype struct contain information about + screen size, cursor position, colour attributes, and more.""" + console_screen_buffer_info = CONSOLE_SCREEN_BUFFER_INFO() + _GetConsoleScreenBufferInfo(std_handle, byref(console_screen_buffer_info)) + return console_screen_buffer_info + + +_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition +_SetConsoleCursorPosition.argtypes = [ + wintypes.HANDLE, + cast(Type[COORD], WindowsCoordinates), +] +_SetConsoleCursorPosition.restype = wintypes.BOOL + + +def SetConsoleCursorPosition( + std_handle: wintypes.HANDLE, coords: WindowsCoordinates +) -> bool: + """Set the position of the cursor in the console screen + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + coords (WindowsCoordinates): The coordinates to move the cursor to. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_SetConsoleCursorPosition(std_handle, coords)) + + +_GetConsoleCursorInfo = windll.kernel32.GetConsoleCursorInfo +_GetConsoleCursorInfo.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(CONSOLE_CURSOR_INFO), +] +_GetConsoleCursorInfo.restype = wintypes.BOOL + + +def GetConsoleCursorInfo( + std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO +) -> bool: + """Get the cursor info - used to get cursor visibility and width + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct that receives information + about the console's cursor. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_GetConsoleCursorInfo(std_handle, byref(cursor_info))) + + +_SetConsoleCursorInfo = windll.kernel32.SetConsoleCursorInfo +_SetConsoleCursorInfo.argtypes = [ + wintypes.HANDLE, + ctypes.POINTER(CONSOLE_CURSOR_INFO), +] +_SetConsoleCursorInfo.restype = wintypes.BOOL + + +def SetConsoleCursorInfo( + std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO +) -> bool: + """Set the cursor info - used for adjusting cursor visibility and width + + Args: + std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer. + cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct containing the new cursor info. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_SetConsoleCursorInfo(std_handle, byref(cursor_info))) + + +_SetConsoleTitle = windll.kernel32.SetConsoleTitleW +_SetConsoleTitle.argtypes = [wintypes.LPCWSTR] +_SetConsoleTitle.restype = wintypes.BOOL + + +def SetConsoleTitle(title: str) -> bool: + """Sets the title of the current console window + + Args: + title (str): The new title of the console window. + + Returns: + bool: True if the function succeeds, otherwise False. + """ + return bool(_SetConsoleTitle(title)) + + +class LegacyWindowsTerm: + """This class allows interaction with the legacy Windows Console API. It should only be used in the context + of environments where virtual terminal processing is not available. However, if it is used in a Windows environment, + the entire API should work. + + Args: + file (IO[str]): The file which the Windows Console API HANDLE is retrieved from, defaults to sys.stdout. + """ + + BRIGHT_BIT = 8 + + # Indices are ANSI color numbers, values are the corresponding Windows Console API color numbers + ANSI_TO_WINDOWS = [ + 0, # black The Windows colours are defined in wincon.h as follows: + 4, # red define FOREGROUND_BLUE 0x0001 -- 0000 0001 + 2, # green define FOREGROUND_GREEN 0x0002 -- 0000 0010 + 6, # yellow define FOREGROUND_RED 0x0004 -- 0000 0100 + 1, # blue define FOREGROUND_INTENSITY 0x0008 -- 0000 1000 + 5, # magenta define BACKGROUND_BLUE 0x0010 -- 0001 0000 + 3, # cyan define BACKGROUND_GREEN 0x0020 -- 0010 0000 + 7, # white define BACKGROUND_RED 0x0040 -- 0100 0000 + 8, # bright black (grey) define BACKGROUND_INTENSITY 0x0080 -- 1000 0000 + 12, # bright red + 10, # bright green + 14, # bright yellow + 9, # bright blue + 13, # bright magenta + 11, # bright cyan + 15, # bright white + ] + + def __init__(self, file: "IO[str]") -> None: + handle = GetStdHandle(STDOUT) + self._handle = handle + default_text = GetConsoleScreenBufferInfo(handle).wAttributes + self._default_text = default_text + + self._default_fore = default_text & 7 + self._default_back = (default_text >> 4) & 7 + self._default_attrs = self._default_fore | (self._default_back << 4) + + self._file = file + self.write = file.write + self.flush = file.flush + + @property + def cursor_position(self) -> WindowsCoordinates: + """Returns the current position of the cursor (0-based) + + Returns: + WindowsCoordinates: The current cursor position. + """ + coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition + return WindowsCoordinates(row=cast(int, coord.Y), col=cast(int, coord.X)) + + @property + def screen_size(self) -> WindowsCoordinates: + """Returns the current size of the console screen buffer, in character columns and rows + + Returns: + WindowsCoordinates: The width and height of the screen as WindowsCoordinates. + """ + screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize + return WindowsCoordinates( + row=cast(int, screen_size.Y), col=cast(int, screen_size.X) + ) + + def write_text(self, text: str) -> None: + """Write text directly to the terminal without any modification of styles + + Args: + text (str): The text to write to the console + """ + self.write(text) + self.flush() + + def write_styled(self, text: str, style: Style) -> None: + """Write styled text to the terminal. + + Args: + text (str): The text to write + style (Style): The style of the text + """ + color = style.color + bgcolor = style.bgcolor + if style.reverse: + color, bgcolor = bgcolor, color + + if color: + fore = color.downgrade(ColorSystem.WINDOWS).number + fore = fore if fore is not None else 7 # Default to ANSI 7: White + if style.bold: + fore = fore | self.BRIGHT_BIT + if style.dim: + fore = fore & ~self.BRIGHT_BIT + fore = self.ANSI_TO_WINDOWS[fore] + else: + fore = self._default_fore + + if bgcolor: + back = bgcolor.downgrade(ColorSystem.WINDOWS).number + back = back if back is not None else 0 # Default to ANSI 0: Black + back = self.ANSI_TO_WINDOWS[back] + else: + back = self._default_back + + assert fore is not None + assert back is not None + + SetConsoleTextAttribute( + self._handle, attributes=ctypes.c_ushort(fore | (back << 4)) + ) + self.write_text(text) + SetConsoleTextAttribute(self._handle, attributes=self._default_text) + + def move_cursor_to(self, new_position: WindowsCoordinates) -> None: + """Set the position of the cursor + + Args: + new_position (WindowsCoordinates): The WindowsCoordinates representing the new position of the cursor. + """ + if new_position.col < 0 or new_position.row < 0: + return + SetConsoleCursorPosition(self._handle, coords=new_position) + + def erase_line(self) -> None: + """Erase all content on the line the cursor is currently located at""" + screen_size = self.screen_size + cursor_position = self.cursor_position + cells_to_erase = screen_size.col + start_coordinates = WindowsCoordinates(row=cursor_position.row, col=0) + FillConsoleOutputCharacter( + self._handle, " ", length=cells_to_erase, start=start_coordinates + ) + FillConsoleOutputAttribute( + self._handle, + self._default_attrs, + length=cells_to_erase, + start=start_coordinates, + ) + + def erase_end_of_line(self) -> None: + """Erase all content from the cursor position to the end of that line""" + cursor_position = self.cursor_position + cells_to_erase = self.screen_size.col - cursor_position.col + FillConsoleOutputCharacter( + self._handle, " ", length=cells_to_erase, start=cursor_position + ) + FillConsoleOutputAttribute( + self._handle, + self._default_attrs, + length=cells_to_erase, + start=cursor_position, + ) + + def erase_start_of_line(self) -> None: + """Erase all content from the cursor position to the start of that line""" + row, col = self.cursor_position + start = WindowsCoordinates(row, 0) + FillConsoleOutputCharacter(self._handle, " ", length=col, start=start) + FillConsoleOutputAttribute( + self._handle, self._default_attrs, length=col, start=start + ) + + def move_cursor_up(self) -> None: + """Move the cursor up a single cell""" + cursor_position = self.cursor_position + SetConsoleCursorPosition( + self._handle, + coords=WindowsCoordinates( + row=cursor_position.row - 1, col=cursor_position.col + ), + ) + + def move_cursor_down(self) -> None: + """Move the cursor down a single cell""" + cursor_position = self.cursor_position + SetConsoleCursorPosition( + self._handle, + coords=WindowsCoordinates( + row=cursor_position.row + 1, + col=cursor_position.col, + ), + ) + + def move_cursor_forward(self) -> None: + """Move the cursor forward a single cell. Wrap to the next line if required.""" + row, col = self.cursor_position + if col == self.screen_size.col - 1: + row += 1 + col = 0 + else: + col += 1 + SetConsoleCursorPosition( + self._handle, coords=WindowsCoordinates(row=row, col=col) + ) + + def move_cursor_to_column(self, column: int) -> None: + """Move cursor to the column specified by the zero-based column index, staying on the same row + + Args: + column (int): The zero-based column index to move the cursor to. + """ + row, _ = self.cursor_position + SetConsoleCursorPosition(self._handle, coords=WindowsCoordinates(row, column)) + + def move_cursor_backward(self) -> None: + """Move the cursor backward a single cell. Wrap to the previous line if required.""" + row, col = self.cursor_position + if col == 0: + row -= 1 + col = self.screen_size.col - 1 + else: + col -= 1 + SetConsoleCursorPosition( + self._handle, coords=WindowsCoordinates(row=row, col=col) + ) + + def hide_cursor(self) -> None: + """Hide the cursor""" + current_cursor_size = self._get_cursor_size() + invisible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=0) + SetConsoleCursorInfo(self._handle, cursor_info=invisible_cursor) + + def show_cursor(self) -> None: + """Show the cursor""" + current_cursor_size = self._get_cursor_size() + visible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=1) + SetConsoleCursorInfo(self._handle, cursor_info=visible_cursor) + + def set_title(self, title: str) -> None: + """Set the title of the terminal window + + Args: + title (str): The new title of the console window + """ + assert len(title) < 255, "Console title must be less than 255 characters" + SetConsoleTitle(title) + + def _get_cursor_size(self) -> int: + """Get the percentage of the character cell that is filled by the cursor""" + cursor_info = CONSOLE_CURSOR_INFO() + GetConsoleCursorInfo(self._handle, cursor_info=cursor_info) + return int(cursor_info.dwSize) + + +if __name__ == "__main__": + handle = GetStdHandle() + + from pip._vendor.rich.console import Console + + console = Console() + + term = LegacyWindowsTerm(sys.stdout) + term.set_title("Win32 Console Examples") + + style = Style(color="black", bgcolor="red") + + heading = Style.parse("black on green") + + # Check colour output + console.rule("Checking colour output") + console.print("[on red]on red!") + console.print("[blue]blue!") + console.print("[yellow]yellow!") + console.print("[bold yellow]bold yellow!") + console.print("[bright_yellow]bright_yellow!") + console.print("[dim bright_yellow]dim bright_yellow!") + console.print("[italic cyan]italic cyan!") + console.print("[bold white on blue]bold white on blue!") + console.print("[reverse bold white on blue]reverse bold white on blue!") + console.print("[bold black on cyan]bold black on cyan!") + console.print("[black on green]black on green!") + console.print("[blue on green]blue on green!") + console.print("[white on black]white on black!") + console.print("[black on white]black on white!") + console.print("[#1BB152 on #DA812D]#1BB152 on #DA812D!") + + # Check cursor movement + console.rule("Checking cursor movement") + console.print() + term.move_cursor_backward() + term.move_cursor_backward() + term.write_text("went back and wrapped to prev line") + time.sleep(1) + term.move_cursor_up() + term.write_text("we go up") + time.sleep(1) + term.move_cursor_down() + term.write_text("and down") + time.sleep(1) + term.move_cursor_up() + term.move_cursor_backward() + term.move_cursor_backward() + term.write_text("we went up and back 2") + time.sleep(1) + term.move_cursor_down() + term.move_cursor_backward() + term.move_cursor_backward() + term.write_text("we went down and back 2") + time.sleep(1) + + # Check erasing of lines + term.hide_cursor() + console.print() + console.rule("Checking line erasing") + console.print("\n...Deleting to the start of the line...") + term.write_text("The red arrow shows the cursor location, and direction of erase") + time.sleep(1) + term.move_cursor_to_column(16) + term.write_styled("<", Style.parse("black on red")) + term.move_cursor_backward() + time.sleep(1) + term.erase_start_of_line() + time.sleep(1) + + console.print("\n\n...And to the end of the line...") + term.write_text("The red arrow shows the cursor location, and direction of erase") + time.sleep(1) + + term.move_cursor_to_column(16) + term.write_styled(">", Style.parse("black on red")) + time.sleep(1) + term.erase_end_of_line() + time.sleep(1) + + console.print("\n\n...Now the whole line will be erased...") + term.write_styled("I'm going to disappear!", style=Style.parse("black on cyan")) + time.sleep(1) + term.erase_line() + + term.show_cursor() + print("\n") diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_windows.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_windows.py new file mode 100644 index 00000000..10fc0d7e --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_windows.py @@ -0,0 +1,72 @@ +import sys +from dataclasses import dataclass + + +@dataclass +class WindowsConsoleFeatures: + """Windows features available.""" + + vt: bool = False + """The console supports VT codes.""" + truecolor: bool = False + """The console supports truecolor.""" + + +try: + import ctypes + from ctypes import LibraryLoader + + if sys.platform == "win32": + windll = LibraryLoader(ctypes.WinDLL) + else: + windll = None + raise ImportError("Not windows") + + from pip._vendor.rich._win32_console import ( + ENABLE_VIRTUAL_TERMINAL_PROCESSING, + GetConsoleMode, + GetStdHandle, + LegacyWindowsError, + ) + +except (AttributeError, ImportError, ValueError): + + # Fallback if we can't load the Windows DLL + def get_windows_console_features() -> WindowsConsoleFeatures: + features = WindowsConsoleFeatures() + return features + +else: + + def get_windows_console_features() -> WindowsConsoleFeatures: + """Get windows console features. + + Returns: + WindowsConsoleFeatures: An instance of WindowsConsoleFeatures. + """ + handle = GetStdHandle() + try: + console_mode = GetConsoleMode(handle) + success = True + except LegacyWindowsError: + console_mode = 0 + success = False + vt = bool(success and console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + truecolor = False + if vt: + win_version = sys.getwindowsversion() + truecolor = win_version.major > 10 or ( + win_version.major == 10 and win_version.build >= 15063 + ) + features = WindowsConsoleFeatures(vt=vt, truecolor=truecolor) + return features + + +if __name__ == "__main__": + import platform + + features = get_windows_console_features() + from pip._vendor.rich import print + + print(f'platform="{platform.system()}"') + print(repr(features)) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_windows_renderer.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_windows_renderer.py new file mode 100644 index 00000000..5ece0564 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_windows_renderer.py @@ -0,0 +1,56 @@ +from typing import Iterable, Sequence, Tuple, cast + +from pip._vendor.rich._win32_console import LegacyWindowsTerm, WindowsCoordinates +from pip._vendor.rich.segment import ControlCode, ControlType, Segment + + +def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> None: + """Makes appropriate Windows Console API calls based on the segments in the buffer. + + Args: + buffer (Iterable[Segment]): Iterable of Segments to convert to Win32 API calls. + term (LegacyWindowsTerm): Used to call the Windows Console API. + """ + for text, style, control in buffer: + if not control: + if style: + term.write_styled(text, style) + else: + term.write_text(text) + else: + control_codes: Sequence[ControlCode] = control + for control_code in control_codes: + control_type = control_code[0] + if control_type == ControlType.CURSOR_MOVE_TO: + _, x, y = cast(Tuple[ControlType, int, int], control_code) + term.move_cursor_to(WindowsCoordinates(row=y - 1, col=x - 1)) + elif control_type == ControlType.CARRIAGE_RETURN: + term.write_text("\r") + elif control_type == ControlType.HOME: + term.move_cursor_to(WindowsCoordinates(0, 0)) + elif control_type == ControlType.CURSOR_UP: + term.move_cursor_up() + elif control_type == ControlType.CURSOR_DOWN: + term.move_cursor_down() + elif control_type == ControlType.CURSOR_FORWARD: + term.move_cursor_forward() + elif control_type == ControlType.CURSOR_BACKWARD: + term.move_cursor_backward() + elif control_type == ControlType.CURSOR_MOVE_TO_COLUMN: + _, column = cast(Tuple[ControlType, int], control_code) + term.move_cursor_to_column(column - 1) + elif control_type == ControlType.HIDE_CURSOR: + term.hide_cursor() + elif control_type == ControlType.SHOW_CURSOR: + term.show_cursor() + elif control_type == ControlType.ERASE_IN_LINE: + _, mode = cast(Tuple[ControlType, int], control_code) + if mode == 0: + term.erase_end_of_line() + elif mode == 1: + term.erase_start_of_line() + elif mode == 2: + term.erase_line() + elif control_type == ControlType.SET_WINDOW_TITLE: + _, title = cast(Tuple[ControlType, str], control_code) + term.set_title(title) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_wrap.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_wrap.py new file mode 100644 index 00000000..c45f193f --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/_wrap.py @@ -0,0 +1,56 @@ +import re +from typing import Iterable, List, Tuple + +from ._loop import loop_last +from .cells import cell_len, chop_cells + +re_word = re.compile(r"\s*\S+\s*") + + +def words(text: str) -> Iterable[Tuple[int, int, str]]: + position = 0 + word_match = re_word.match(text, position) + while word_match is not None: + start, end = word_match.span() + word = word_match.group(0) + yield start, end, word + word_match = re_word.match(text, end) + + +def divide_line(text: str, width: int, fold: bool = True) -> List[int]: + divides: List[int] = [] + append = divides.append + line_position = 0 + _cell_len = cell_len + for start, _end, word in words(text): + word_length = _cell_len(word.rstrip()) + if line_position + word_length > width: + if word_length > width: + if fold: + chopped_words = chop_cells(word, max_size=width, position=0) + for last, line in loop_last(chopped_words): + if start: + append(start) + + if last: + line_position = _cell_len(line) + else: + start += len(line) + else: + if start: + append(start) + line_position = _cell_len(word) + elif line_position and start: + append(start) + line_position = _cell_len(word) + else: + line_position += _cell_len(word) + return divides + + +if __name__ == "__main__": # pragma: no cover + from .console import Console + + console = Console(width=10) + console.print("12345 abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ 12345") + print(chop_cells("abcdefghijklmnopqrstuvwxyz", 10, position=2)) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/abc.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/abc.py new file mode 100644 index 00000000..e6e498ef --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/abc.py @@ -0,0 +1,33 @@ +from abc import ABC + + +class RichRenderable(ABC): + """An abstract base class for Rich renderables. + + Note that there is no need to extend this class, the intended use is to check if an + object supports the Rich renderable protocol. For example:: + + if isinstance(my_object, RichRenderable): + console.print(my_object) + + """ + + @classmethod + def __subclasshook__(cls, other: type) -> bool: + """Check if this class supports the rich render protocol.""" + return hasattr(other, "__rich_console__") or hasattr(other, "__rich__") + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.text import Text + + t = Text() + print(isinstance(Text, RichRenderable)) + print(isinstance(t, RichRenderable)) + + class Foo: + pass + + f = Foo() + print(isinstance(f, RichRenderable)) + print(isinstance("", RichRenderable)) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/align.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/align.py new file mode 100644 index 00000000..c310b66e --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/align.py @@ -0,0 +1,311 @@ +import sys +from itertools import chain +from typing import TYPE_CHECKING, Iterable, Optional + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + +from .constrain import Constrain +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import StyleType + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderableType, RenderResult + +AlignMethod = Literal["left", "center", "right"] +VerticalAlignMethod = Literal["top", "middle", "bottom"] + + +class Align(JupyterMixin): + """Align a renderable by adding spaces if necessary. + + Args: + renderable (RenderableType): A console renderable. + align (AlignMethod): One of "left", "center", or "right"" + style (StyleType, optional): An optional style to apply to the background. + vertical (Optional[VerticalAlginMethod], optional): Optional vertical align, one of "top", "middle", or "bottom". Defaults to None. + pad (bool, optional): Pad the right with spaces. Defaults to True. + width (int, optional): Restrict contents to given width, or None to use default width. Defaults to None. + height (int, optional): Set height of align renderable, or None to fit to contents. Defaults to None. + + Raises: + ValueError: if ``align`` is not one of the expected values. + """ + + def __init__( + self, + renderable: "RenderableType", + align: AlignMethod = "left", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> None: + if align not in ("left", "center", "right"): + raise ValueError( + f'invalid value for align, expected "left", "center", or "right" (not {align!r})' + ) + if vertical is not None and vertical not in ("top", "middle", "bottom"): + raise ValueError( + f'invalid value for vertical, expected "top", "middle", or "bottom" (not {vertical!r})' + ) + self.renderable = renderable + self.align = align + self.style = style + self.vertical = vertical + self.pad = pad + self.width = width + self.height = height + + def __repr__(self) -> str: + return f"Align({self.renderable!r}, {self.align!r})" + + @classmethod + def left( + cls, + renderable: "RenderableType", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> "Align": + """Align a renderable to the left.""" + return cls( + renderable, + "left", + style=style, + vertical=vertical, + pad=pad, + width=width, + height=height, + ) + + @classmethod + def center( + cls, + renderable: "RenderableType", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> "Align": + """Align a renderable to the center.""" + return cls( + renderable, + "center", + style=style, + vertical=vertical, + pad=pad, + width=width, + height=height, + ) + + @classmethod + def right( + cls, + renderable: "RenderableType", + style: Optional[StyleType] = None, + *, + vertical: Optional[VerticalAlignMethod] = None, + pad: bool = True, + width: Optional[int] = None, + height: Optional[int] = None, + ) -> "Align": + """Align a renderable to the right.""" + return cls( + renderable, + "right", + style=style, + vertical=vertical, + pad=pad, + width=width, + height=height, + ) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + align = self.align + width = console.measure(self.renderable, options=options).maximum + rendered = console.render( + Constrain( + self.renderable, width if self.width is None else min(width, self.width) + ), + options.update(height=None), + ) + lines = list(Segment.split_lines(rendered)) + width, height = Segment.get_shape(lines) + lines = Segment.set_shape(lines, width, height) + new_line = Segment.line() + excess_space = options.max_width - width + style = console.get_style(self.style) if self.style is not None else None + + def generate_segments() -> Iterable[Segment]: + if excess_space <= 0: + # Exact fit + for line in lines: + yield from line + yield new_line + + elif align == "left": + # Pad on the right + pad = Segment(" " * excess_space, style) if self.pad else None + for line in lines: + yield from line + if pad: + yield pad + yield new_line + + elif align == "center": + # Pad left and right + left = excess_space // 2 + pad = Segment(" " * left, style) + pad_right = ( + Segment(" " * (excess_space - left), style) if self.pad else None + ) + for line in lines: + if left: + yield pad + yield from line + if pad_right: + yield pad_right + yield new_line + + elif align == "right": + # Padding on left + pad = Segment(" " * excess_space, style) + for line in lines: + yield pad + yield from line + yield new_line + + blank_line = ( + Segment(f"{' ' * (self.width or options.max_width)}\n", style) + if self.pad + else Segment("\n") + ) + + def blank_lines(count: int) -> Iterable[Segment]: + if count > 0: + for _ in range(count): + yield blank_line + + vertical_height = self.height or options.height + iter_segments: Iterable[Segment] + if self.vertical and vertical_height is not None: + if self.vertical == "top": + bottom_space = vertical_height - height + iter_segments = chain(generate_segments(), blank_lines(bottom_space)) + elif self.vertical == "middle": + top_space = (vertical_height - height) // 2 + bottom_space = vertical_height - top_space - height + iter_segments = chain( + blank_lines(top_space), + generate_segments(), + blank_lines(bottom_space), + ) + else: # self.vertical == "bottom": + top_space = vertical_height - height + iter_segments = chain(blank_lines(top_space), generate_segments()) + else: + iter_segments = generate_segments() + if self.style: + style = console.get_style(self.style) + iter_segments = Segment.apply_style(iter_segments, style) + yield from iter_segments + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + measurement = Measurement.get(console, options, self.renderable) + return measurement + + +class VerticalCenter(JupyterMixin): + """Vertically aligns a renderable. + + Warn: + This class is deprecated and may be removed in a future version. Use Align class with + `vertical="middle"`. + + Args: + renderable (RenderableType): A renderable object. + """ + + def __init__( + self, + renderable: "RenderableType", + style: Optional[StyleType] = None, + ) -> None: + self.renderable = renderable + self.style = style + + def __repr__(self) -> str: + return f"VerticalCenter({self.renderable!r})" + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + style = console.get_style(self.style) if self.style is not None else None + lines = console.render_lines( + self.renderable, options.update(height=None), pad=False + ) + width, _height = Segment.get_shape(lines) + new_line = Segment.line() + height = options.height or options.size.height + top_space = (height - len(lines)) // 2 + bottom_space = height - top_space - len(lines) + blank_line = Segment(f"{' ' * width}", style) + + def blank_lines(count: int) -> Iterable[Segment]: + for _ in range(count): + yield blank_line + yield new_line + + if top_space > 0: + yield from blank_lines(top_space) + for line in lines: + yield from line + yield new_line + if bottom_space > 0: + yield from blank_lines(bottom_space) + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + measurement = Measurement.get(console, options, self.renderable) + return measurement + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console, Group + from pip._vendor.rich.highlighter import ReprHighlighter + from pip._vendor.rich.panel import Panel + + highlighter = ReprHighlighter() + console = Console() + + panel = Panel( + Group( + Align.left(highlighter("align='left'")), + Align.center(highlighter("align='center'")), + Align.right(highlighter("align='right'")), + ), + width=60, + style="on dark_blue", + title="Align", + ) + + console.print( + Align.center(panel, vertical="middle", style="on red", height=console.height) + ) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/ansi.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/ansi.py new file mode 100644 index 00000000..66365e65 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/ansi.py @@ -0,0 +1,240 @@ +import re +import sys +from contextlib import suppress +from typing import Iterable, NamedTuple, Optional + +from .color import Color +from .style import Style +from .text import Text + +re_ansi = re.compile( + r""" +(?:\x1b\](.*?)\x1b\\)| +(?:\x1b([(@-Z\\-_]|\[[0-?]*[ -/]*[@-~])) +""", + re.VERBOSE, +) + + +class _AnsiToken(NamedTuple): + """Result of ansi tokenized string.""" + + plain: str = "" + sgr: Optional[str] = "" + osc: Optional[str] = "" + + +def _ansi_tokenize(ansi_text: str) -> Iterable[_AnsiToken]: + """Tokenize a string in to plain text and ANSI codes. + + Args: + ansi_text (str): A String containing ANSI codes. + + Yields: + AnsiToken: A named tuple of (plain, sgr, osc) + """ + + position = 0 + sgr: Optional[str] + osc: Optional[str] + for match in re_ansi.finditer(ansi_text): + start, end = match.span(0) + osc, sgr = match.groups() + if start > position: + yield _AnsiToken(ansi_text[position:start]) + if sgr: + if sgr == "(": + position = end + 1 + continue + if sgr.endswith("m"): + yield _AnsiToken("", sgr[1:-1], osc) + else: + yield _AnsiToken("", sgr, osc) + position = end + if position < len(ansi_text): + yield _AnsiToken(ansi_text[position:]) + + +SGR_STYLE_MAP = { + 1: "bold", + 2: "dim", + 3: "italic", + 4: "underline", + 5: "blink", + 6: "blink2", + 7: "reverse", + 8: "conceal", + 9: "strike", + 21: "underline2", + 22: "not dim not bold", + 23: "not italic", + 24: "not underline", + 25: "not blink", + 26: "not blink2", + 27: "not reverse", + 28: "not conceal", + 29: "not strike", + 30: "color(0)", + 31: "color(1)", + 32: "color(2)", + 33: "color(3)", + 34: "color(4)", + 35: "color(5)", + 36: "color(6)", + 37: "color(7)", + 39: "default", + 40: "on color(0)", + 41: "on color(1)", + 42: "on color(2)", + 43: "on color(3)", + 44: "on color(4)", + 45: "on color(5)", + 46: "on color(6)", + 47: "on color(7)", + 49: "on default", + 51: "frame", + 52: "encircle", + 53: "overline", + 54: "not frame not encircle", + 55: "not overline", + 90: "color(8)", + 91: "color(9)", + 92: "color(10)", + 93: "color(11)", + 94: "color(12)", + 95: "color(13)", + 96: "color(14)", + 97: "color(15)", + 100: "on color(8)", + 101: "on color(9)", + 102: "on color(10)", + 103: "on color(11)", + 104: "on color(12)", + 105: "on color(13)", + 106: "on color(14)", + 107: "on color(15)", +} + + +class AnsiDecoder: + """Translate ANSI code in to styled Text.""" + + def __init__(self) -> None: + self.style = Style.null() + + def decode(self, terminal_text: str) -> Iterable[Text]: + """Decode ANSI codes in an iterable of lines. + + Args: + lines (Iterable[str]): An iterable of lines of terminal output. + + Yields: + Text: Marked up Text. + """ + for line in terminal_text.splitlines(): + yield self.decode_line(line) + + def decode_line(self, line: str) -> Text: + """Decode a line containing ansi codes. + + Args: + line (str): A line of terminal output. + + Returns: + Text: A Text instance marked up according to ansi codes. + """ + from_ansi = Color.from_ansi + from_rgb = Color.from_rgb + _Style = Style + text = Text() + append = text.append + line = line.rsplit("\r", 1)[-1] + for plain_text, sgr, osc in _ansi_tokenize(line): + if plain_text: + append(plain_text, self.style or None) + elif osc is not None: + if osc.startswith("8;"): + _params, semicolon, link = osc[2:].partition(";") + if semicolon: + self.style = self.style.update_link(link or None) + elif sgr is not None: + # Translate in to semi-colon separated codes + # Ignore invalid codes, because we want to be lenient + codes = [ + min(255, int(_code) if _code else 0) + for _code in sgr.split(";") + if _code.isdigit() or _code == "" + ] + iter_codes = iter(codes) + for code in iter_codes: + if code == 0: + # reset + self.style = _Style.null() + elif code in SGR_STYLE_MAP: + # styles + self.style += _Style.parse(SGR_STYLE_MAP[code]) + elif code == 38: + #  Foreground + with suppress(StopIteration): + color_type = next(iter_codes) + if color_type == 5: + self.style += _Style.from_color( + from_ansi(next(iter_codes)) + ) + elif color_type == 2: + self.style += _Style.from_color( + from_rgb( + next(iter_codes), + next(iter_codes), + next(iter_codes), + ) + ) + elif code == 48: + # Background + with suppress(StopIteration): + color_type = next(iter_codes) + if color_type == 5: + self.style += _Style.from_color( + None, from_ansi(next(iter_codes)) + ) + elif color_type == 2: + self.style += _Style.from_color( + None, + from_rgb( + next(iter_codes), + next(iter_codes), + next(iter_codes), + ), + ) + + return text + + +if sys.platform != "win32" and __name__ == "__main__": # pragma: no cover + import io + import os + import pty + import sys + + decoder = AnsiDecoder() + + stdout = io.BytesIO() + + def read(fd: int) -> bytes: + data = os.read(fd, 1024) + stdout.write(data) + return data + + pty.spawn(sys.argv[1:], read) + + from .console import Console + + console = Console(record=True) + + stdout_result = stdout.getvalue().decode("utf-8") + print(stdout_result) + + for line in decoder.decode(stdout_result): + console.print(line) + + console.save_html("stdout.html") diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/bar.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/bar.py new file mode 100644 index 00000000..ed86a552 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/bar.py @@ -0,0 +1,94 @@ +from typing import Optional, Union + +from .color import Color +from .console import Console, ConsoleOptions, RenderResult +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import Style + +# There are left-aligned characters for 1/8 to 7/8, but +# the right-aligned characters exist only for 1/8 and 4/8. +BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"] +END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"] +FULL_BLOCK = "█" + + +class Bar(JupyterMixin): + """Renders a solid block bar. + + Args: + size (float): Value for the end of the bar. + begin (float): Begin point (between 0 and size, inclusive). + end (float): End point (between 0 and size, inclusive). + width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None. + color (Union[Color, str], optional): Color of the bar. Defaults to "default". + bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default". + """ + + def __init__( + self, + size: float, + begin: float, + end: float, + *, + width: Optional[int] = None, + color: Union[Color, str] = "default", + bgcolor: Union[Color, str] = "default", + ): + self.size = size + self.begin = max(begin, 0) + self.end = min(end, size) + self.width = width + self.style = Style(color=color, bgcolor=bgcolor) + + def __repr__(self) -> str: + return f"Bar({self.size}, {self.begin}, {self.end})" + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + + width = min( + self.width if self.width is not None else options.max_width, + options.max_width, + ) + + if self.begin >= self.end: + yield Segment(" " * width, self.style) + yield Segment.line() + return + + prefix_complete_eights = int(width * 8 * self.begin / self.size) + prefix_bar_count = prefix_complete_eights // 8 + prefix_eights_count = prefix_complete_eights % 8 + + body_complete_eights = int(width * 8 * self.end / self.size) + body_bar_count = body_complete_eights // 8 + body_eights_count = body_complete_eights % 8 + + # When start and end fall into the same cell, we ideally should render + # a symbol that's "center-aligned", but there is no good symbol in Unicode. + # In this case, we fall back to right-aligned block symbol for simplicity. + + prefix = " " * prefix_bar_count + if prefix_eights_count: + prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count] + + body = FULL_BLOCK * body_bar_count + if body_eights_count: + body += END_BLOCK_ELEMENTS[body_eights_count] + + suffix = " " * (width - len(body)) + + yield Segment(prefix + body[len(prefix) :] + suffix, self.style) + yield Segment.line() + + def __rich_measure__( + self, console: Console, options: ConsoleOptions + ) -> Measurement: + return ( + Measurement(self.width, self.width) + if self.width is not None + else Measurement(4, options.max_width) + ) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/box.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/box.py new file mode 100644 index 00000000..97d2a944 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/box.py @@ -0,0 +1,517 @@ +import sys +from typing import TYPE_CHECKING, Iterable, List + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + + +from ._loop import loop_last + +if TYPE_CHECKING: + from pip._vendor.rich.console import ConsoleOptions + + +class Box: + """Defines characters to render boxes. + + ┌─┬┐ top + │ ││ head + ├─┼┤ head_row + │ ││ mid + ├─┼┤ row + ├─┼┤ foot_row + │ ││ foot + └─┴┘ bottom + + Args: + box (str): Characters making up box. + ascii (bool, optional): True if this box uses ascii characters only. Default is False. + """ + + def __init__(self, box: str, *, ascii: bool = False) -> None: + self._box = box + self.ascii = ascii + line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines() + # top + self.top_left, self.top, self.top_divider, self.top_right = iter(line1) + # head + self.head_left, _, self.head_vertical, self.head_right = iter(line2) + # head_row + ( + self.head_row_left, + self.head_row_horizontal, + self.head_row_cross, + self.head_row_right, + ) = iter(line3) + + # mid + self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4) + # row + self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5) + # foot_row + ( + self.foot_row_left, + self.foot_row_horizontal, + self.foot_row_cross, + self.foot_row_right, + ) = iter(line6) + # foot + self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7) + # bottom + self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter( + line8 + ) + + def __repr__(self) -> str: + return "Box(...)" + + def __str__(self) -> str: + return self._box + + def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box": + """Substitute this box for another if it won't render due to platform issues. + + Args: + options (ConsoleOptions): Console options used in rendering. + safe (bool, optional): Substitute this for another Box if there are known problems + displaying on the platform (currently only relevant on Windows). Default is True. + + Returns: + Box: A different Box or the same Box. + """ + box = self + if options.legacy_windows and safe: + box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box) + if options.ascii_only and not box.ascii: + box = ASCII + return box + + def get_plain_headed_box(self) -> "Box": + """If this box uses special characters for the borders of the header, then + return the equivalent box that does not. + + Returns: + Box: The most similar Box that doesn't use header-specific box characters. + If the current Box already satisfies this criterion, then it's returned. + """ + return PLAIN_HEADED_SUBSTITUTIONS.get(self, self) + + def get_top(self, widths: Iterable[int]) -> str: + """Get the top of a simple box. + + Args: + widths (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + + parts: List[str] = [] + append = parts.append + append(self.top_left) + for last, width in loop_last(widths): + append(self.top * width) + if not last: + append(self.top_divider) + append(self.top_right) + return "".join(parts) + + def get_row( + self, + widths: Iterable[int], + level: Literal["head", "row", "foot", "mid"] = "row", + edge: bool = True, + ) -> str: + """Get the top of a simple box. + + Args: + width (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + if level == "head": + left = self.head_row_left + horizontal = self.head_row_horizontal + cross = self.head_row_cross + right = self.head_row_right + elif level == "row": + left = self.row_left + horizontal = self.row_horizontal + cross = self.row_cross + right = self.row_right + elif level == "mid": + left = self.mid_left + horizontal = " " + cross = self.mid_vertical + right = self.mid_right + elif level == "foot": + left = self.foot_row_left + horizontal = self.foot_row_horizontal + cross = self.foot_row_cross + right = self.foot_row_right + else: + raise ValueError("level must be 'head', 'row' or 'foot'") + + parts: List[str] = [] + append = parts.append + if edge: + append(left) + for last, width in loop_last(widths): + append(horizontal * width) + if not last: + append(cross) + if edge: + append(right) + return "".join(parts) + + def get_bottom(self, widths: Iterable[int]) -> str: + """Get the bottom of a simple box. + + Args: + widths (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + + parts: List[str] = [] + append = parts.append + append(self.bottom_left) + for last, width in loop_last(widths): + append(self.bottom * width) + if not last: + append(self.bottom_divider) + append(self.bottom_right) + return "".join(parts) + + +ASCII: Box = Box( + """\ ++--+ +| || +|-+| +| || +|-+| +|-+| +| || ++--+ +""", + ascii=True, +) + +ASCII2: Box = Box( + """\ ++-++ +| || ++-++ +| || ++-++ ++-++ +| || ++-++ +""", + ascii=True, +) + +ASCII_DOUBLE_HEAD: Box = Box( + """\ ++-++ +| || ++=++ +| || ++-++ ++-++ +| || ++-++ +""", + ascii=True, +) + +SQUARE: Box = Box( + """\ +┌─┬┐ +│ ││ +├─┼┤ +│ ││ +├─┼┤ +├─┼┤ +│ ││ +└─┴┘ +""" +) + +SQUARE_DOUBLE_HEAD: Box = Box( + """\ +┌─┬┐ +│ ││ +╞═╪╡ +│ ││ +├─┼┤ +├─┼┤ +│ ││ +└─┴┘ +""" +) + +MINIMAL: Box = Box( + """\ + ╷ + │ +╶─┼╴ + │ +╶─┼╴ +╶─┼╴ + │ + ╵ +""" +) + + +MINIMAL_HEAVY_HEAD: Box = Box( + """\ + ╷ + │ +╺━┿╸ + │ +╶─┼╴ +╶─┼╴ + │ + ╵ +""" +) + +MINIMAL_DOUBLE_HEAD: Box = Box( + """\ + ╷ + │ + ═╪ + │ + ─┼ + ─┼ + │ + ╵ +""" +) + + +SIMPLE: Box = Box( + """\ + + + ── + + + ── + + +""" +) + +SIMPLE_HEAD: Box = Box( + """\ + + + ── + + + + + +""" +) + + +SIMPLE_HEAVY: Box = Box( + """\ + + + ━━ + + + ━━ + + +""" +) + + +HORIZONTALS: Box = Box( + """\ + ── + + ── + + ── + ── + + ── +""" +) + +ROUNDED: Box = Box( + """\ +╭─┬╮ +│ ││ +├─┼┤ +│ ││ +├─┼┤ +├─┼┤ +│ ││ +╰─┴╯ +""" +) + +HEAVY: Box = Box( + """\ +┏━┳┓ +┃ ┃┃ +┣━╋┫ +┃ ┃┃ +┣━╋┫ +┣━╋┫ +┃ ┃┃ +┗━┻┛ +""" +) + +HEAVY_EDGE: Box = Box( + """\ +┏━┯┓ +┃ │┃ +┠─┼┨ +┃ │┃ +┠─┼┨ +┠─┼┨ +┃ │┃ +┗━┷┛ +""" +) + +HEAVY_HEAD: Box = Box( + """\ +┏━┳┓ +┃ ┃┃ +┡━╇┩ +│ ││ +├─┼┤ +├─┼┤ +│ ││ +└─┴┘ +""" +) + +DOUBLE: Box = Box( + """\ +╔═╦╗ +║ ║║ +╠═╬╣ +║ ║║ +╠═╬╣ +╠═╬╣ +║ ║║ +╚═╩╝ +""" +) + +DOUBLE_EDGE: Box = Box( + """\ +╔═╤╗ +║ │║ +╟─┼╢ +║ │║ +╟─┼╢ +╟─┼╢ +║ │║ +╚═╧╝ +""" +) + +MARKDOWN: Box = Box( + """\ + +| || +|-|| +| || +|-|| +|-|| +| || + +""", + ascii=True, +) + +# Map Boxes that don't render with raster fonts on to equivalent that do +LEGACY_WINDOWS_SUBSTITUTIONS = { + ROUNDED: SQUARE, + MINIMAL_HEAVY_HEAD: MINIMAL, + SIMPLE_HEAVY: SIMPLE, + HEAVY: SQUARE, + HEAVY_EDGE: SQUARE, + HEAVY_HEAD: SQUARE, +} + +# Map headed boxes to their headerless equivalents +PLAIN_HEADED_SUBSTITUTIONS = { + HEAVY_HEAD: SQUARE, + SQUARE_DOUBLE_HEAD: SQUARE, + MINIMAL_DOUBLE_HEAD: MINIMAL, + MINIMAL_HEAVY_HEAD: MINIMAL, + ASCII_DOUBLE_HEAD: ASCII2, +} + + +if __name__ == "__main__": # pragma: no cover + + from pip._vendor.rich.columns import Columns + from pip._vendor.rich.panel import Panel + + from . import box as box + from .console import Console + from .table import Table + from .text import Text + + console = Console(record=True) + + BOXES = [ + "ASCII", + "ASCII2", + "ASCII_DOUBLE_HEAD", + "SQUARE", + "SQUARE_DOUBLE_HEAD", + "MINIMAL", + "MINIMAL_HEAVY_HEAD", + "MINIMAL_DOUBLE_HEAD", + "SIMPLE", + "SIMPLE_HEAD", + "SIMPLE_HEAVY", + "HORIZONTALS", + "ROUNDED", + "HEAVY", + "HEAVY_EDGE", + "HEAVY_HEAD", + "DOUBLE", + "DOUBLE_EDGE", + "MARKDOWN", + ] + + console.print(Panel("[bold green]Box Constants", style="green"), justify="center") + console.print() + + columns = Columns(expand=True, padding=2) + for box_name in sorted(BOXES): + table = Table( + show_footer=True, style="dim", border_style="not dim", expand=True + ) + table.add_column("Header 1", "Footer 1") + table.add_column("Header 2", "Footer 2") + table.add_row("Cell", "Cell") + table.add_row("Cell", "Cell") + table.box = getattr(box, box_name) + table.title = Text(f"box.{box_name}", style="magenta") + columns.add_renderable(table) + console.print(columns) + + # console.save_svg("box.svg") diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/cells.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/cells.py new file mode 100644 index 00000000..9354f9e3 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/cells.py @@ -0,0 +1,154 @@ +import re +from functools import lru_cache +from typing import Callable, List + +from ._cell_widths import CELL_WIDTHS + +# Regex to match sequence of the most common character ranges +_is_single_cell_widths = re.compile("^[\u0020-\u006f\u00a0\u02ff\u0370-\u0482]*$").match + + +@lru_cache(4096) +def cached_cell_len(text: str) -> int: + """Get the number of cells required to display text. + + This method always caches, which may use up a lot of memory. It is recommended to use + `cell_len` over this method. + + Args: + text (str): Text to display. + + Returns: + int: Get the number of cells required to display text. + """ + _get_size = get_character_cell_size + total_size = sum(_get_size(character) for character in text) + return total_size + + +def cell_len(text: str, _cell_len: Callable[[str], int] = cached_cell_len) -> int: + """Get the number of cells required to display text. + + Args: + text (str): Text to display. + + Returns: + int: Get the number of cells required to display text. + """ + if len(text) < 512: + return _cell_len(text) + _get_size = get_character_cell_size + total_size = sum(_get_size(character) for character in text) + return total_size + + +@lru_cache(maxsize=4096) +def get_character_cell_size(character: str) -> int: + """Get the cell size of a character. + + Args: + character (str): A single character. + + Returns: + int: Number of cells (0, 1 or 2) occupied by that character. + """ + return _get_codepoint_cell_size(ord(character)) + + +@lru_cache(maxsize=4096) +def _get_codepoint_cell_size(codepoint: int) -> int: + """Get the cell size of a character. + + Args: + codepoint (int): Codepoint of a character. + + Returns: + int: Number of cells (0, 1 or 2) occupied by that character. + """ + + _table = CELL_WIDTHS + lower_bound = 0 + upper_bound = len(_table) - 1 + index = (lower_bound + upper_bound) // 2 + while True: + start, end, width = _table[index] + if codepoint < start: + upper_bound = index - 1 + elif codepoint > end: + lower_bound = index + 1 + else: + return 0 if width == -1 else width + if upper_bound < lower_bound: + break + index = (lower_bound + upper_bound) // 2 + return 1 + + +def set_cell_size(text: str, total: int) -> str: + """Set the length of a string to fit within given number of cells.""" + + if _is_single_cell_widths(text): + size = len(text) + if size < total: + return text + " " * (total - size) + return text[:total] + + if total <= 0: + return "" + cell_size = cell_len(text) + if cell_size == total: + return text + if cell_size < total: + return text + " " * (total - cell_size) + + start = 0 + end = len(text) + + # Binary search until we find the right size + while True: + pos = (start + end) // 2 + before = text[: pos + 1] + before_len = cell_len(before) + if before_len == total + 1 and cell_len(before[-1]) == 2: + return before[:-1] + " " + if before_len == total: + return before + if before_len > total: + end = pos + else: + start = pos + + +# TODO: This is inefficient +# TODO: This might not work with CWJ type characters +def chop_cells(text: str, max_size: int, position: int = 0) -> List[str]: + """Break text in to equal (cell) length strings, returning the characters in reverse + order""" + _get_character_cell_size = get_character_cell_size + characters = [ + (character, _get_character_cell_size(character)) for character in text + ] + total_size = position + lines: List[List[str]] = [[]] + append = lines[-1].append + + for character, size in reversed(characters): + if total_size + size > max_size: + lines.append([character]) + append = lines[-1].append + total_size = size + else: + total_size += size + append(character) + + return ["".join(line) for line in lines] + + +if __name__ == "__main__": # pragma: no cover + + print(get_character_cell_size("😽")) + for line in chop_cells("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", 8): + print(line) + for n in range(80, 1, -1): + print(set_cell_size("""这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑。""", n) + "|") + print("x" * n) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/color.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/color.py new file mode 100644 index 00000000..dfe45593 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/color.py @@ -0,0 +1,622 @@ +import platform +import re +from colorsys import rgb_to_hls +from enum import IntEnum +from functools import lru_cache +from typing import TYPE_CHECKING, NamedTuple, Optional, Tuple + +from ._palettes import EIGHT_BIT_PALETTE, STANDARD_PALETTE, WINDOWS_PALETTE +from .color_triplet import ColorTriplet +from .repr import Result, rich_repr +from .terminal_theme import DEFAULT_TERMINAL_THEME + +if TYPE_CHECKING: # pragma: no cover + from .terminal_theme import TerminalTheme + from .text import Text + + +WINDOWS = platform.system() == "Windows" + + +class ColorSystem(IntEnum): + """One of the 3 color system supported by terminals.""" + + STANDARD = 1 + EIGHT_BIT = 2 + TRUECOLOR = 3 + WINDOWS = 4 + + def __repr__(self) -> str: + return f"ColorSystem.{self.name}" + + def __str__(self) -> str: + return repr(self) + + +class ColorType(IntEnum): + """Type of color stored in Color class.""" + + DEFAULT = 0 + STANDARD = 1 + EIGHT_BIT = 2 + TRUECOLOR = 3 + WINDOWS = 4 + + def __repr__(self) -> str: + return f"ColorType.{self.name}" + + +ANSI_COLOR_NAMES = { + "black": 0, + "red": 1, + "green": 2, + "yellow": 3, + "blue": 4, + "magenta": 5, + "cyan": 6, + "white": 7, + "bright_black": 8, + "bright_red": 9, + "bright_green": 10, + "bright_yellow": 11, + "bright_blue": 12, + "bright_magenta": 13, + "bright_cyan": 14, + "bright_white": 15, + "grey0": 16, + "gray0": 16, + "navy_blue": 17, + "dark_blue": 18, + "blue3": 20, + "blue1": 21, + "dark_green": 22, + "deep_sky_blue4": 25, + "dodger_blue3": 26, + "dodger_blue2": 27, + "green4": 28, + "spring_green4": 29, + "turquoise4": 30, + "deep_sky_blue3": 32, + "dodger_blue1": 33, + "green3": 40, + "spring_green3": 41, + "dark_cyan": 36, + "light_sea_green": 37, + "deep_sky_blue2": 38, + "deep_sky_blue1": 39, + "spring_green2": 47, + "cyan3": 43, + "dark_turquoise": 44, + "turquoise2": 45, + "green1": 46, + "spring_green1": 48, + "medium_spring_green": 49, + "cyan2": 50, + "cyan1": 51, + "dark_red": 88, + "deep_pink4": 125, + "purple4": 55, + "purple3": 56, + "blue_violet": 57, + "orange4": 94, + "grey37": 59, + "gray37": 59, + "medium_purple4": 60, + "slate_blue3": 62, + "royal_blue1": 63, + "chartreuse4": 64, + "dark_sea_green4": 71, + "pale_turquoise4": 66, + "steel_blue": 67, + "steel_blue3": 68, + "cornflower_blue": 69, + "chartreuse3": 76, + "cadet_blue": 73, + "sky_blue3": 74, + "steel_blue1": 81, + "pale_green3": 114, + "sea_green3": 78, + "aquamarine3": 79, + "medium_turquoise": 80, + "chartreuse2": 112, + "sea_green2": 83, + "sea_green1": 85, + "aquamarine1": 122, + "dark_slate_gray2": 87, + "dark_magenta": 91, + "dark_violet": 128, + "purple": 129, + "light_pink4": 95, + "plum4": 96, + "medium_purple3": 98, + "slate_blue1": 99, + "yellow4": 106, + "wheat4": 101, + "grey53": 102, + "gray53": 102, + "light_slate_grey": 103, + "light_slate_gray": 103, + "medium_purple": 104, + "light_slate_blue": 105, + "dark_olive_green3": 149, + "dark_sea_green": 108, + "light_sky_blue3": 110, + "sky_blue2": 111, + "dark_sea_green3": 150, + "dark_slate_gray3": 116, + "sky_blue1": 117, + "chartreuse1": 118, + "light_green": 120, + "pale_green1": 156, + "dark_slate_gray1": 123, + "red3": 160, + "medium_violet_red": 126, + "magenta3": 164, + "dark_orange3": 166, + "indian_red": 167, + "hot_pink3": 168, + "medium_orchid3": 133, + "medium_orchid": 134, + "medium_purple2": 140, + "dark_goldenrod": 136, + "light_salmon3": 173, + "rosy_brown": 138, + "grey63": 139, + "gray63": 139, + "medium_purple1": 141, + "gold3": 178, + "dark_khaki": 143, + "navajo_white3": 144, + "grey69": 145, + "gray69": 145, + "light_steel_blue3": 146, + "light_steel_blue": 147, + "yellow3": 184, + "dark_sea_green2": 157, + "light_cyan3": 152, + "light_sky_blue1": 153, + "green_yellow": 154, + "dark_olive_green2": 155, + "dark_sea_green1": 193, + "pale_turquoise1": 159, + "deep_pink3": 162, + "magenta2": 200, + "hot_pink2": 169, + "orchid": 170, + "medium_orchid1": 207, + "orange3": 172, + "light_pink3": 174, + "pink3": 175, + "plum3": 176, + "violet": 177, + "light_goldenrod3": 179, + "tan": 180, + "misty_rose3": 181, + "thistle3": 182, + "plum2": 183, + "khaki3": 185, + "light_goldenrod2": 222, + "light_yellow3": 187, + "grey84": 188, + "gray84": 188, + "light_steel_blue1": 189, + "yellow2": 190, + "dark_olive_green1": 192, + "honeydew2": 194, + "light_cyan1": 195, + "red1": 196, + "deep_pink2": 197, + "deep_pink1": 199, + "magenta1": 201, + "orange_red1": 202, + "indian_red1": 204, + "hot_pink": 206, + "dark_orange": 208, + "salmon1": 209, + "light_coral": 210, + "pale_violet_red1": 211, + "orchid2": 212, + "orchid1": 213, + "orange1": 214, + "sandy_brown": 215, + "light_salmon1": 216, + "light_pink1": 217, + "pink1": 218, + "plum1": 219, + "gold1": 220, + "navajo_white1": 223, + "misty_rose1": 224, + "thistle1": 225, + "yellow1": 226, + "light_goldenrod1": 227, + "khaki1": 228, + "wheat1": 229, + "cornsilk1": 230, + "grey100": 231, + "gray100": 231, + "grey3": 232, + "gray3": 232, + "grey7": 233, + "gray7": 233, + "grey11": 234, + "gray11": 234, + "grey15": 235, + "gray15": 235, + "grey19": 236, + "gray19": 236, + "grey23": 237, + "gray23": 237, + "grey27": 238, + "gray27": 238, + "grey30": 239, + "gray30": 239, + "grey35": 240, + "gray35": 240, + "grey39": 241, + "gray39": 241, + "grey42": 242, + "gray42": 242, + "grey46": 243, + "gray46": 243, + "grey50": 244, + "gray50": 244, + "grey54": 245, + "gray54": 245, + "grey58": 246, + "gray58": 246, + "grey62": 247, + "gray62": 247, + "grey66": 248, + "gray66": 248, + "grey70": 249, + "gray70": 249, + "grey74": 250, + "gray74": 250, + "grey78": 251, + "gray78": 251, + "grey82": 252, + "gray82": 252, + "grey85": 253, + "gray85": 253, + "grey89": 254, + "gray89": 254, + "grey93": 255, + "gray93": 255, +} + + +class ColorParseError(Exception): + """The color could not be parsed.""" + + +RE_COLOR = re.compile( + r"""^ +\#([0-9a-f]{6})$| +color\(([0-9]{1,3})\)$| +rgb\(([\d\s,]+)\)$ +""", + re.VERBOSE, +) + + +@rich_repr +class Color(NamedTuple): + """Terminal color definition.""" + + name: str + """The name of the color (typically the input to Color.parse).""" + type: ColorType + """The type of the color.""" + number: Optional[int] = None + """The color number, if a standard color, or None.""" + triplet: Optional[ColorTriplet] = None + """A triplet of color components, if an RGB color.""" + + def __rich__(self) -> "Text": + """Displays the actual color if Rich printed.""" + from .style import Style + from .text import Text + + return Text.assemble( + f"", + ) + + def __rich_repr__(self) -> Result: + yield self.name + yield self.type + yield "number", self.number, None + yield "triplet", self.triplet, None + + @property + def system(self) -> ColorSystem: + """Get the native color system for this color.""" + if self.type == ColorType.DEFAULT: + return ColorSystem.STANDARD + return ColorSystem(int(self.type)) + + @property + def is_system_defined(self) -> bool: + """Check if the color is ultimately defined by the system.""" + return self.system not in (ColorSystem.EIGHT_BIT, ColorSystem.TRUECOLOR) + + @property + def is_default(self) -> bool: + """Check if the color is a default color.""" + return self.type == ColorType.DEFAULT + + def get_truecolor( + self, theme: Optional["TerminalTheme"] = None, foreground: bool = True + ) -> ColorTriplet: + """Get an equivalent color triplet for this color. + + Args: + theme (TerminalTheme, optional): Optional terminal theme, or None to use default. Defaults to None. + foreground (bool, optional): True for a foreground color, or False for background. Defaults to True. + + Returns: + ColorTriplet: A color triplet containing RGB components. + """ + + if theme is None: + theme = DEFAULT_TERMINAL_THEME + if self.type == ColorType.TRUECOLOR: + assert self.triplet is not None + return self.triplet + elif self.type == ColorType.EIGHT_BIT: + assert self.number is not None + return EIGHT_BIT_PALETTE[self.number] + elif self.type == ColorType.STANDARD: + assert self.number is not None + return theme.ansi_colors[self.number] + elif self.type == ColorType.WINDOWS: + assert self.number is not None + return WINDOWS_PALETTE[self.number] + else: # self.type == ColorType.DEFAULT: + assert self.number is None + return theme.foreground_color if foreground else theme.background_color + + @classmethod + def from_ansi(cls, number: int) -> "Color": + """Create a Color number from it's 8-bit ansi number. + + Args: + number (int): A number between 0-255 inclusive. + + Returns: + Color: A new Color instance. + """ + return cls( + name=f"color({number})", + type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT), + number=number, + ) + + @classmethod + def from_triplet(cls, triplet: "ColorTriplet") -> "Color": + """Create a truecolor RGB color from a triplet of values. + + Args: + triplet (ColorTriplet): A color triplet containing red, green and blue components. + + Returns: + Color: A new color object. + """ + return cls(name=triplet.hex, type=ColorType.TRUECOLOR, triplet=triplet) + + @classmethod + def from_rgb(cls, red: float, green: float, blue: float) -> "Color": + """Create a truecolor from three color components in the range(0->255). + + Args: + red (float): Red component in range 0-255. + green (float): Green component in range 0-255. + blue (float): Blue component in range 0-255. + + Returns: + Color: A new color object. + """ + return cls.from_triplet(ColorTriplet(int(red), int(green), int(blue))) + + @classmethod + def default(cls) -> "Color": + """Get a Color instance representing the default color. + + Returns: + Color: Default color. + """ + return cls(name="default", type=ColorType.DEFAULT) + + @classmethod + @lru_cache(maxsize=1024) + def parse(cls, color: str) -> "Color": + """Parse a color definition.""" + original_color = color + color = color.lower().strip() + + if color == "default": + return cls(color, type=ColorType.DEFAULT) + + color_number = ANSI_COLOR_NAMES.get(color) + if color_number is not None: + return cls( + color, + type=(ColorType.STANDARD if color_number < 16 else ColorType.EIGHT_BIT), + number=color_number, + ) + + color_match = RE_COLOR.match(color) + if color_match is None: + raise ColorParseError(f"{original_color!r} is not a valid color") + + color_24, color_8, color_rgb = color_match.groups() + if color_24: + triplet = ColorTriplet( + int(color_24[0:2], 16), int(color_24[2:4], 16), int(color_24[4:6], 16) + ) + return cls(color, ColorType.TRUECOLOR, triplet=triplet) + + elif color_8: + number = int(color_8) + if number > 255: + raise ColorParseError(f"color number must be <= 255 in {color!r}") + return cls( + color, + type=(ColorType.STANDARD if number < 16 else ColorType.EIGHT_BIT), + number=number, + ) + + else: # color_rgb: + components = color_rgb.split(",") + if len(components) != 3: + raise ColorParseError( + f"expected three components in {original_color!r}" + ) + red, green, blue = components + triplet = ColorTriplet(int(red), int(green), int(blue)) + if not all(component <= 255 for component in triplet): + raise ColorParseError( + f"color components must be <= 255 in {original_color!r}" + ) + return cls(color, ColorType.TRUECOLOR, triplet=triplet) + + @lru_cache(maxsize=1024) + def get_ansi_codes(self, foreground: bool = True) -> Tuple[str, ...]: + """Get the ANSI escape codes for this color.""" + _type = self.type + if _type == ColorType.DEFAULT: + return ("39" if foreground else "49",) + + elif _type == ColorType.WINDOWS: + number = self.number + assert number is not None + fore, back = (30, 40) if number < 8 else (82, 92) + return (str(fore + number if foreground else back + number),) + + elif _type == ColorType.STANDARD: + number = self.number + assert number is not None + fore, back = (30, 40) if number < 8 else (82, 92) + return (str(fore + number if foreground else back + number),) + + elif _type == ColorType.EIGHT_BIT: + assert self.number is not None + return ("38" if foreground else "48", "5", str(self.number)) + + else: # self.standard == ColorStandard.TRUECOLOR: + assert self.triplet is not None + red, green, blue = self.triplet + return ("38" if foreground else "48", "2", str(red), str(green), str(blue)) + + @lru_cache(maxsize=1024) + def downgrade(self, system: ColorSystem) -> "Color": + """Downgrade a color system to a system with fewer colors.""" + + if self.type in (ColorType.DEFAULT, system): + return self + # Convert to 8-bit color from truecolor color + if system == ColorSystem.EIGHT_BIT and self.system == ColorSystem.TRUECOLOR: + assert self.triplet is not None + _h, l, s = rgb_to_hls(*self.triplet.normalized) + # If saturation is under 15% assume it is grayscale + if s < 0.15: + gray = round(l * 25.0) + if gray == 0: + color_number = 16 + elif gray == 25: + color_number = 231 + else: + color_number = 231 + gray + return Color(self.name, ColorType.EIGHT_BIT, number=color_number) + + red, green, blue = self.triplet + six_red = red / 95 if red < 95 else 1 + (red - 95) / 40 + six_green = green / 95 if green < 95 else 1 + (green - 95) / 40 + six_blue = blue / 95 if blue < 95 else 1 + (blue - 95) / 40 + + color_number = ( + 16 + 36 * round(six_red) + 6 * round(six_green) + round(six_blue) + ) + return Color(self.name, ColorType.EIGHT_BIT, number=color_number) + + # Convert to standard from truecolor or 8-bit + elif system == ColorSystem.STANDARD: + if self.system == ColorSystem.TRUECOLOR: + assert self.triplet is not None + triplet = self.triplet + else: # self.system == ColorSystem.EIGHT_BIT + assert self.number is not None + triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number]) + + color_number = STANDARD_PALETTE.match(triplet) + return Color(self.name, ColorType.STANDARD, number=color_number) + + elif system == ColorSystem.WINDOWS: + if self.system == ColorSystem.TRUECOLOR: + assert self.triplet is not None + triplet = self.triplet + else: # self.system == ColorSystem.EIGHT_BIT + assert self.number is not None + if self.number < 16: + return Color(self.name, ColorType.WINDOWS, number=self.number) + triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number]) + + color_number = WINDOWS_PALETTE.match(triplet) + return Color(self.name, ColorType.WINDOWS, number=color_number) + + return self + + +def parse_rgb_hex(hex_color: str) -> ColorTriplet: + """Parse six hex characters in to RGB triplet.""" + assert len(hex_color) == 6, "must be 6 characters" + color = ColorTriplet( + int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16) + ) + return color + + +def blend_rgb( + color1: ColorTriplet, color2: ColorTriplet, cross_fade: float = 0.5 +) -> ColorTriplet: + """Blend one RGB color in to another.""" + r1, g1, b1 = color1 + r2, g2, b2 = color2 + new_color = ColorTriplet( + int(r1 + (r2 - r1) * cross_fade), + int(g1 + (g2 - g1) * cross_fade), + int(b1 + (b2 - b1) * cross_fade), + ) + return new_color + + +if __name__ == "__main__": # pragma: no cover + + from .console import Console + from .table import Table + from .text import Text + + console = Console() + + table = Table(show_footer=False, show_edge=True) + table.add_column("Color", width=10, overflow="ellipsis") + table.add_column("Number", justify="right", style="yellow") + table.add_column("Name", style="green") + table.add_column("Hex", style="blue") + table.add_column("RGB", style="magenta") + + colors = sorted((v, k) for k, v in ANSI_COLOR_NAMES.items()) + for color_number, name in colors: + if "grey" in name: + continue + color_cell = Text(" " * 10, style=f"on {name}") + if color_number < 16: + table.add_row(color_cell, f"{color_number}", Text(f'"{name}"')) + else: + color = EIGHT_BIT_PALETTE[color_number] # type: ignore[has-type] + table.add_row( + color_cell, str(color_number), Text(f'"{name}"'), color.hex, color.rgb + ) + + console.print(table) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/color_triplet.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/color_triplet.py new file mode 100644 index 00000000..02cab328 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/color_triplet.py @@ -0,0 +1,38 @@ +from typing import NamedTuple, Tuple + + +class ColorTriplet(NamedTuple): + """The red, green, and blue components of a color.""" + + red: int + """Red component in 0 to 255 range.""" + green: int + """Green component in 0 to 255 range.""" + blue: int + """Blue component in 0 to 255 range.""" + + @property + def hex(self) -> str: + """get the color triplet in CSS style.""" + red, green, blue = self + return f"#{red:02x}{green:02x}{blue:02x}" + + @property + def rgb(self) -> str: + """The color in RGB format. + + Returns: + str: An rgb color, e.g. ``"rgb(100,23,255)"``. + """ + red, green, blue = self + return f"rgb({red},{green},{blue})" + + @property + def normalized(self) -> Tuple[float, float, float]: + """Convert components into floats between 0 and 1. + + Returns: + Tuple[float, float, float]: A tuple of three normalized colour components. + """ + red, green, blue = self + return red / 255.0, green / 255.0, blue / 255.0 diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/columns.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/columns.py new file mode 100644 index 00000000..669a3a70 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/columns.py @@ -0,0 +1,187 @@ +from collections import defaultdict +from itertools import chain +from operator import itemgetter +from typing import Dict, Iterable, List, Optional, Tuple + +from .align import Align, AlignMethod +from .console import Console, ConsoleOptions, RenderableType, RenderResult +from .constrain import Constrain +from .measure import Measurement +from .padding import Padding, PaddingDimensions +from .table import Table +from .text import TextType +from .jupyter import JupyterMixin + + +class Columns(JupyterMixin): + """Display renderables in neat columns. + + Args: + renderables (Iterable[RenderableType]): Any number of Rich renderables (including str). + width (int, optional): The desired width of the columns, or None to auto detect. Defaults to None. + padding (PaddingDimensions, optional): Optional padding around cells. Defaults to (0, 1). + expand (bool, optional): Expand columns to full width. Defaults to False. + equal (bool, optional): Arrange in to equal sized columns. Defaults to False. + column_first (bool, optional): Align items from top to bottom (rather than left to right). Defaults to False. + right_to_left (bool, optional): Start column from right hand side. Defaults to False. + align (str, optional): Align value ("left", "right", or "center") or None for default. Defaults to None. + title (TextType, optional): Optional title for Columns. + """ + + def __init__( + self, + renderables: Optional[Iterable[RenderableType]] = None, + padding: PaddingDimensions = (0, 1), + *, + width: Optional[int] = None, + expand: bool = False, + equal: bool = False, + column_first: bool = False, + right_to_left: bool = False, + align: Optional[AlignMethod] = None, + title: Optional[TextType] = None, + ) -> None: + self.renderables = list(renderables or []) + self.width = width + self.padding = padding + self.expand = expand + self.equal = equal + self.column_first = column_first + self.right_to_left = right_to_left + self.align: Optional[AlignMethod] = align + self.title = title + + def add_renderable(self, renderable: RenderableType) -> None: + """Add a renderable to the columns. + + Args: + renderable (RenderableType): Any renderable object. + """ + self.renderables.append(renderable) + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + render_str = console.render_str + renderables = [ + render_str(renderable) if isinstance(renderable, str) else renderable + for renderable in self.renderables + ] + if not renderables: + return + _top, right, _bottom, left = Padding.unpack(self.padding) + width_padding = max(left, right) + max_width = options.max_width + widths: Dict[int, int] = defaultdict(int) + column_count = len(renderables) + + get_measurement = Measurement.get + renderable_widths = [ + get_measurement(console, options, renderable).maximum + for renderable in renderables + ] + if self.equal: + renderable_widths = [max(renderable_widths)] * len(renderable_widths) + + def iter_renderables( + column_count: int, + ) -> Iterable[Tuple[int, Optional[RenderableType]]]: + item_count = len(renderables) + if self.column_first: + width_renderables = list(zip(renderable_widths, renderables)) + + column_lengths: List[int] = [item_count // column_count] * column_count + for col_no in range(item_count % column_count): + column_lengths[col_no] += 1 + + row_count = (item_count + column_count - 1) // column_count + cells = [[-1] * column_count for _ in range(row_count)] + row = col = 0 + for index in range(item_count): + cells[row][col] = index + column_lengths[col] -= 1 + if column_lengths[col]: + row += 1 + else: + col += 1 + row = 0 + for index in chain.from_iterable(cells): + if index == -1: + break + yield width_renderables[index] + else: + yield from zip(renderable_widths, renderables) + # Pad odd elements with spaces + if item_count % column_count: + for _ in range(column_count - (item_count % column_count)): + yield 0, None + + table = Table.grid(padding=self.padding, collapse_padding=True, pad_edge=False) + table.expand = self.expand + table.title = self.title + + if self.width is not None: + column_count = (max_width) // (self.width + width_padding) + for _ in range(column_count): + table.add_column(width=self.width) + else: + while column_count > 1: + widths.clear() + column_no = 0 + for renderable_width, _ in iter_renderables(column_count): + widths[column_no] = max(widths[column_no], renderable_width) + total_width = sum(widths.values()) + width_padding * ( + len(widths) - 1 + ) + if total_width > max_width: + column_count = len(widths) - 1 + break + else: + column_no = (column_no + 1) % column_count + else: + break + + get_renderable = itemgetter(1) + _renderables = [ + get_renderable(_renderable) + for _renderable in iter_renderables(column_count) + ] + if self.equal: + _renderables = [ + None + if renderable is None + else Constrain(renderable, renderable_widths[0]) + for renderable in _renderables + ] + if self.align: + align = self.align + _Align = Align + _renderables = [ + None if renderable is None else _Align(renderable, align) + for renderable in _renderables + ] + + right_to_left = self.right_to_left + add_row = table.add_row + for start in range(0, len(_renderables), column_count): + row = _renderables[start : start + column_count] + if right_to_left: + row = row[::-1] + add_row(*row) + yield table + + +if __name__ == "__main__": # pragma: no cover + import os + + console = Console() + + files = [f"{i} {s}" for i, s in enumerate(sorted(os.listdir()))] + columns = Columns(files, padding=(0, 1), expand=False, equal=False) + console.print(columns) + console.rule() + columns.column_first = True + console.print(columns) + columns.right_to_left = True + console.rule() + console.print(columns) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/console.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/console.py new file mode 100644 index 00000000..e559cbb4 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/console.py @@ -0,0 +1,2633 @@ +import inspect +import os +import platform +import sys +import threading +import zlib +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from datetime import datetime +from functools import wraps +from getpass import getpass +from html import escape +from inspect import isclass +from itertools import islice +from math import ceil +from time import monotonic +from types import FrameType, ModuleType, TracebackType +from typing import ( + IO, + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + List, + Mapping, + NamedTuple, + Optional, + TextIO, + Tuple, + Type, + Union, + cast, +) + +from pip._vendor.rich._null_file import NULL_FILE + +if sys.version_info >= (3, 8): + from typing import Literal, Protocol, runtime_checkable +else: + from pip._vendor.typing_extensions import ( + Literal, + Protocol, + runtime_checkable, + ) # pragma: no cover + +from . import errors, themes +from ._emoji_replace import _emoji_replace +from ._export_format import CONSOLE_HTML_FORMAT, CONSOLE_SVG_FORMAT +from ._fileno import get_fileno +from ._log_render import FormatTimeCallable, LogRender +from .align import Align, AlignMethod +from .color import ColorSystem, blend_rgb +from .control import Control +from .emoji import EmojiVariant +from .highlighter import NullHighlighter, ReprHighlighter +from .markup import render as render_markup +from .measure import Measurement, measure_renderables +from .pager import Pager, SystemPager +from .pretty import Pretty, is_expandable +from .protocol import rich_cast +from .region import Region +from .scope import render_scope +from .screen import Screen +from .segment import Segment +from .style import Style, StyleType +from .styled import Styled +from .terminal_theme import DEFAULT_TERMINAL_THEME, SVG_EXPORT_THEME, TerminalTheme +from .text import Text, TextType +from .theme import Theme, ThemeStack + +if TYPE_CHECKING: + from ._windows import WindowsConsoleFeatures + from .live import Live + from .status import Status + +JUPYTER_DEFAULT_COLUMNS = 115 +JUPYTER_DEFAULT_LINES = 100 +WINDOWS = platform.system() == "Windows" + +HighlighterType = Callable[[Union[str, "Text"]], "Text"] +JustifyMethod = Literal["default", "left", "center", "right", "full"] +OverflowMethod = Literal["fold", "crop", "ellipsis", "ignore"] + + +class NoChange: + pass + + +NO_CHANGE = NoChange() + +try: + _STDIN_FILENO = sys.__stdin__.fileno() +except Exception: + _STDIN_FILENO = 0 +try: + _STDOUT_FILENO = sys.__stdout__.fileno() +except Exception: + _STDOUT_FILENO = 1 +try: + _STDERR_FILENO = sys.__stderr__.fileno() +except Exception: + _STDERR_FILENO = 2 + +_STD_STREAMS = (_STDIN_FILENO, _STDOUT_FILENO, _STDERR_FILENO) +_STD_STREAMS_OUTPUT = (_STDOUT_FILENO, _STDERR_FILENO) + + +_TERM_COLORS = { + "kitty": ColorSystem.EIGHT_BIT, + "256color": ColorSystem.EIGHT_BIT, + "16color": ColorSystem.STANDARD, +} + + +class ConsoleDimensions(NamedTuple): + """Size of the terminal.""" + + width: int + """The width of the console in 'cells'.""" + height: int + """The height of the console in lines.""" + + +@dataclass +class ConsoleOptions: + """Options for __rich_console__ method.""" + + size: ConsoleDimensions + """Size of console.""" + legacy_windows: bool + """legacy_windows: flag for legacy windows.""" + min_width: int + """Minimum width of renderable.""" + max_width: int + """Maximum width of renderable.""" + is_terminal: bool + """True if the target is a terminal, otherwise False.""" + encoding: str + """Encoding of terminal.""" + max_height: int + """Height of container (starts as terminal)""" + justify: Optional[JustifyMethod] = None + """Justify value override for renderable.""" + overflow: Optional[OverflowMethod] = None + """Overflow value override for renderable.""" + no_wrap: Optional[bool] = False + """Disable wrapping for text.""" + highlight: Optional[bool] = None + """Highlight override for render_str.""" + markup: Optional[bool] = None + """Enable markup when rendering strings.""" + height: Optional[int] = None + + @property + def ascii_only(self) -> bool: + """Check if renderables should use ascii only.""" + return not self.encoding.startswith("utf") + + def copy(self) -> "ConsoleOptions": + """Return a copy of the options. + + Returns: + ConsoleOptions: a copy of self. + """ + options: ConsoleOptions = ConsoleOptions.__new__(ConsoleOptions) + options.__dict__ = self.__dict__.copy() + return options + + def update( + self, + *, + width: Union[int, NoChange] = NO_CHANGE, + min_width: Union[int, NoChange] = NO_CHANGE, + max_width: Union[int, NoChange] = NO_CHANGE, + justify: Union[Optional[JustifyMethod], NoChange] = NO_CHANGE, + overflow: Union[Optional[OverflowMethod], NoChange] = NO_CHANGE, + no_wrap: Union[Optional[bool], NoChange] = NO_CHANGE, + highlight: Union[Optional[bool], NoChange] = NO_CHANGE, + markup: Union[Optional[bool], NoChange] = NO_CHANGE, + height: Union[Optional[int], NoChange] = NO_CHANGE, + ) -> "ConsoleOptions": + """Update values, return a copy.""" + options = self.copy() + if not isinstance(width, NoChange): + options.min_width = options.max_width = max(0, width) + if not isinstance(min_width, NoChange): + options.min_width = min_width + if not isinstance(max_width, NoChange): + options.max_width = max_width + if not isinstance(justify, NoChange): + options.justify = justify + if not isinstance(overflow, NoChange): + options.overflow = overflow + if not isinstance(no_wrap, NoChange): + options.no_wrap = no_wrap + if not isinstance(highlight, NoChange): + options.highlight = highlight + if not isinstance(markup, NoChange): + options.markup = markup + if not isinstance(height, NoChange): + if height is not None: + options.max_height = height + options.height = None if height is None else max(0, height) + return options + + def update_width(self, width: int) -> "ConsoleOptions": + """Update just the width, return a copy. + + Args: + width (int): New width (sets both min_width and max_width) + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.min_width = options.max_width = max(0, width) + return options + + def update_height(self, height: int) -> "ConsoleOptions": + """Update the height, and return a copy. + + Args: + height (int): New height + + Returns: + ~ConsoleOptions: New Console options instance. + """ + options = self.copy() + options.max_height = options.height = height + return options + + def reset_height(self) -> "ConsoleOptions": + """Return a copy of the options with height set to ``None``. + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.height = None + return options + + def update_dimensions(self, width: int, height: int) -> "ConsoleOptions": + """Update the width and height, and return a copy. + + Args: + width (int): New width (sets both min_width and max_width). + height (int): New height. + + Returns: + ~ConsoleOptions: New console options instance. + """ + options = self.copy() + options.min_width = options.max_width = max(0, width) + options.height = options.max_height = height + return options + + +@runtime_checkable +class RichCast(Protocol): + """An object that may be 'cast' to a console renderable.""" + + def __rich__( + self, + ) -> Union["ConsoleRenderable", "RichCast", str]: # pragma: no cover + ... + + +@runtime_checkable +class ConsoleRenderable(Protocol): + """An object that supports the console protocol.""" + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": # pragma: no cover + ... + + +# A type that may be rendered by Console. +RenderableType = Union[ConsoleRenderable, RichCast, str] + +# The result of calling a __rich_console__ method. +RenderResult = Iterable[Union[RenderableType, Segment]] + +_null_highlighter = NullHighlighter() + + +class CaptureError(Exception): + """An error in the Capture context manager.""" + + +class NewLine: + """A renderable to generate new line(s)""" + + def __init__(self, count: int = 1) -> None: + self.count = count + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> Iterable[Segment]: + yield Segment("\n" * self.count) + + +class ScreenUpdate: + """Render a list of lines at a given offset.""" + + def __init__(self, lines: List[List[Segment]], x: int, y: int) -> None: + self._lines = lines + self.x = x + self.y = y + + def __rich_console__( + self, console: "Console", options: ConsoleOptions + ) -> RenderResult: + x = self.x + move_to = Control.move_to + for offset, line in enumerate(self._lines, self.y): + yield move_to(x, offset) + yield from line + + +class Capture: + """Context manager to capture the result of printing to the console. + See :meth:`~rich.console.Console.capture` for how to use. + + Args: + console (Console): A console instance to capture output. + """ + + def __init__(self, console: "Console") -> None: + self._console = console + self._result: Optional[str] = None + + def __enter__(self) -> "Capture": + self._console.begin_capture() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self._result = self._console.end_capture() + + def get(self) -> str: + """Get the result of the capture.""" + if self._result is None: + raise CaptureError( + "Capture result is not available until context manager exits." + ) + return self._result + + +class ThemeContext: + """A context manager to use a temporary theme. See :meth:`~rich.console.Console.use_theme` for usage.""" + + def __init__(self, console: "Console", theme: Theme, inherit: bool = True) -> None: + self.console = console + self.theme = theme + self.inherit = inherit + + def __enter__(self) -> "ThemeContext": + self.console.push_theme(self.theme) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.console.pop_theme() + + +class PagerContext: + """A context manager that 'pages' content. See :meth:`~rich.console.Console.pager` for usage.""" + + def __init__( + self, + console: "Console", + pager: Optional[Pager] = None, + styles: bool = False, + links: bool = False, + ) -> None: + self._console = console + self.pager = SystemPager() if pager is None else pager + self.styles = styles + self.links = links + + def __enter__(self) -> "PagerContext": + self._console._enter_buffer() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + if exc_type is None: + with self._console._lock: + buffer: List[Segment] = self._console._buffer[:] + del self._console._buffer[:] + segments: Iterable[Segment] = buffer + if not self.styles: + segments = Segment.strip_styles(segments) + elif not self.links: + segments = Segment.strip_links(segments) + content = self._console._render_buffer(segments) + self.pager.show(content) + self._console._exit_buffer() + + +class ScreenContext: + """A context manager that enables an alternative screen. See :meth:`~rich.console.Console.screen` for usage.""" + + def __init__( + self, console: "Console", hide_cursor: bool, style: StyleType = "" + ) -> None: + self.console = console + self.hide_cursor = hide_cursor + self.screen = Screen(style=style) + self._changed = False + + def update( + self, *renderables: RenderableType, style: Optional[StyleType] = None + ) -> None: + """Update the screen. + + Args: + renderable (RenderableType, optional): Optional renderable to replace current renderable, + or None for no change. Defaults to None. + style: (Style, optional): Replacement style, or None for no change. Defaults to None. + """ + if renderables: + self.screen.renderable = ( + Group(*renderables) if len(renderables) > 1 else renderables[0] + ) + if style is not None: + self.screen.style = style + self.console.print(self.screen, end="") + + def __enter__(self) -> "ScreenContext": + self._changed = self.console.set_alt_screen(True) + if self._changed and self.hide_cursor: + self.console.show_cursor(False) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + if self._changed: + self.console.set_alt_screen(False) + if self.hide_cursor: + self.console.show_cursor(True) + + +class Group: + """Takes a group of renderables and returns a renderable object that renders the group. + + Args: + renderables (Iterable[RenderableType]): An iterable of renderable objects. + fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. + """ + + def __init__(self, *renderables: "RenderableType", fit: bool = True) -> None: + self._renderables = renderables + self.fit = fit + self._render: Optional[List[RenderableType]] = None + + @property + def renderables(self) -> List["RenderableType"]: + if self._render is None: + self._render = list(self._renderables) + return self._render + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + if self.fit: + return measure_renderables(console, options, self.renderables) + else: + return Measurement(options.max_width, options.max_width) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> RenderResult: + yield from self.renderables + + +def group(fit: bool = True) -> Callable[..., Callable[..., Group]]: + """A decorator that turns an iterable of renderables in to a group. + + Args: + fit (bool, optional): Fit dimension of group to contents, or fill available space. Defaults to True. + """ + + def decorator( + method: Callable[..., Iterable[RenderableType]] + ) -> Callable[..., Group]: + """Convert a method that returns an iterable of renderables in to a Group.""" + + @wraps(method) + def _replace(*args: Any, **kwargs: Any) -> Group: + renderables = method(*args, **kwargs) + return Group(*renderables, fit=fit) + + return _replace + + return decorator + + +def _is_jupyter() -> bool: # pragma: no cover + """Check if we're running in a Jupyter notebook.""" + try: + get_ipython # type: ignore[name-defined] + except NameError: + return False + ipython = get_ipython() # type: ignore[name-defined] + shell = ipython.__class__.__name__ + if ( + "google.colab" in str(ipython.__class__) + or os.getenv("DATABRICKS_RUNTIME_VERSION") + or shell == "ZMQInteractiveShell" + ): + return True # Jupyter notebook or qtconsole + elif shell == "TerminalInteractiveShell": + return False # Terminal running IPython + else: + return False # Other type (?) + + +COLOR_SYSTEMS = { + "standard": ColorSystem.STANDARD, + "256": ColorSystem.EIGHT_BIT, + "truecolor": ColorSystem.TRUECOLOR, + "windows": ColorSystem.WINDOWS, +} + +_COLOR_SYSTEMS_NAMES = {system: name for name, system in COLOR_SYSTEMS.items()} + + +@dataclass +class ConsoleThreadLocals(threading.local): + """Thread local values for Console context.""" + + theme_stack: ThemeStack + buffer: List[Segment] = field(default_factory=list) + buffer_index: int = 0 + + +class RenderHook(ABC): + """Provides hooks in to the render process.""" + + @abstractmethod + def process_renderables( + self, renderables: List[ConsoleRenderable] + ) -> List[ConsoleRenderable]: + """Called with a list of objects to render. + + This method can return a new list of renderables, or modify and return the same list. + + Args: + renderables (List[ConsoleRenderable]): A number of renderable objects. + + Returns: + List[ConsoleRenderable]: A replacement list of renderables. + """ + + +_windows_console_features: Optional["WindowsConsoleFeatures"] = None + + +def get_windows_console_features() -> "WindowsConsoleFeatures": # pragma: no cover + global _windows_console_features + if _windows_console_features is not None: + return _windows_console_features + from ._windows import get_windows_console_features + + _windows_console_features = get_windows_console_features() + return _windows_console_features + + +def detect_legacy_windows() -> bool: + """Detect legacy Windows.""" + return WINDOWS and not get_windows_console_features().vt + + +class Console: + """A high level console interface. + + Args: + color_system (str, optional): The color system supported by your terminal, + either ``"standard"``, ``"256"`` or ``"truecolor"``. Leave as ``"auto"`` to autodetect. + force_terminal (Optional[bool], optional): Enable/disable terminal control codes, or None to auto-detect terminal. Defaults to None. + force_jupyter (Optional[bool], optional): Enable/disable Jupyter rendering, or None to auto-detect Jupyter. Defaults to None. + force_interactive (Optional[bool], optional): Enable/disable interactive mode, or None to auto detect. Defaults to None. + soft_wrap (Optional[bool], optional): Set soft wrap default on print method. Defaults to False. + theme (Theme, optional): An optional style theme object, or ``None`` for default theme. + stderr (bool, optional): Use stderr rather than stdout if ``file`` is not specified. Defaults to False. + file (IO, optional): A file object where the console should write to. Defaults to stdout. + quiet (bool, Optional): Boolean to suppress all output. Defaults to False. + width (int, optional): The width of the terminal. Leave as default to auto-detect width. + height (int, optional): The height of the terminal. Leave as default to auto-detect height. + style (StyleType, optional): Style to apply to all output, or None for no style. Defaults to None. + no_color (Optional[bool], optional): Enabled no color mode, or None to auto detect. Defaults to None. + tab_size (int, optional): Number of spaces used to replace a tab character. Defaults to 8. + record (bool, optional): Boolean to enable recording of terminal output, + required to call :meth:`export_html`, :meth:`export_svg`, and :meth:`export_text`. Defaults to False. + markup (bool, optional): Boolean to enable :ref:`console_markup`. Defaults to True. + emoji (bool, optional): Enable emoji code. Defaults to True. + emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None. + highlight (bool, optional): Enable automatic highlighting. Defaults to True. + log_time (bool, optional): Boolean to enable logging of time by :meth:`log` methods. Defaults to True. + log_path (bool, optional): Boolean to enable the logging of the caller by :meth:`log`. Defaults to True. + log_time_format (Union[str, TimeFormatterCallable], optional): If ``log_time`` is enabled, either string for strftime or callable that formats the time. Defaults to "[%X] ". + highlighter (HighlighterType, optional): Default highlighter. + legacy_windows (bool, optional): Enable legacy Windows mode, or ``None`` to auto detect. Defaults to ``None``. + safe_box (bool, optional): Restrict box options that don't render on legacy Windows. + get_datetime (Callable[[], datetime], optional): Callable that gets the current time as a datetime.datetime object (used by Console.log), + or None for datetime.now. + get_time (Callable[[], time], optional): Callable that gets the current time in seconds, default uses time.monotonic. + """ + + _environ: Mapping[str, str] = os.environ + + def __init__( + self, + *, + color_system: Optional[ + Literal["auto", "standard", "256", "truecolor", "windows"] + ] = "auto", + force_terminal: Optional[bool] = None, + force_jupyter: Optional[bool] = None, + force_interactive: Optional[bool] = None, + soft_wrap: bool = False, + theme: Optional[Theme] = None, + stderr: bool = False, + file: Optional[IO[str]] = None, + quiet: bool = False, + width: Optional[int] = None, + height: Optional[int] = None, + style: Optional[StyleType] = None, + no_color: Optional[bool] = None, + tab_size: int = 8, + record: bool = False, + markup: bool = True, + emoji: bool = True, + emoji_variant: Optional[EmojiVariant] = None, + highlight: bool = True, + log_time: bool = True, + log_path: bool = True, + log_time_format: Union[str, FormatTimeCallable] = "[%X]", + highlighter: Optional["HighlighterType"] = ReprHighlighter(), + legacy_windows: Optional[bool] = None, + safe_box: bool = True, + get_datetime: Optional[Callable[[], datetime]] = None, + get_time: Optional[Callable[[], float]] = None, + _environ: Optional[Mapping[str, str]] = None, + ): + # Copy of os.environ allows us to replace it for testing + if _environ is not None: + self._environ = _environ + + self.is_jupyter = _is_jupyter() if force_jupyter is None else force_jupyter + if self.is_jupyter: + if width is None: + jupyter_columns = self._environ.get("JUPYTER_COLUMNS") + if jupyter_columns is not None and jupyter_columns.isdigit(): + width = int(jupyter_columns) + else: + width = JUPYTER_DEFAULT_COLUMNS + if height is None: + jupyter_lines = self._environ.get("JUPYTER_LINES") + if jupyter_lines is not None and jupyter_lines.isdigit(): + height = int(jupyter_lines) + else: + height = JUPYTER_DEFAULT_LINES + + self.tab_size = tab_size + self.record = record + self._markup = markup + self._emoji = emoji + self._emoji_variant: Optional[EmojiVariant] = emoji_variant + self._highlight = highlight + self.legacy_windows: bool = ( + (detect_legacy_windows() and not self.is_jupyter) + if legacy_windows is None + else legacy_windows + ) + + if width is None: + columns = self._environ.get("COLUMNS") + if columns is not None and columns.isdigit(): + width = int(columns) - self.legacy_windows + if height is None: + lines = self._environ.get("LINES") + if lines is not None and lines.isdigit(): + height = int(lines) + + self.soft_wrap = soft_wrap + self._width = width + self._height = height + + self._color_system: Optional[ColorSystem] + + self._force_terminal = None + if force_terminal is not None: + self._force_terminal = force_terminal + + self._file = file + self.quiet = quiet + self.stderr = stderr + + if color_system is None: + self._color_system = None + elif color_system == "auto": + self._color_system = self._detect_color_system() + else: + self._color_system = COLOR_SYSTEMS[color_system] + + self._lock = threading.RLock() + self._log_render = LogRender( + show_time=log_time, + show_path=log_path, + time_format=log_time_format, + ) + self.highlighter: HighlighterType = highlighter or _null_highlighter + self.safe_box = safe_box + self.get_datetime = get_datetime or datetime.now + self.get_time = get_time or monotonic + self.style = style + self.no_color = ( + no_color if no_color is not None else "NO_COLOR" in self._environ + ) + self.is_interactive = ( + (self.is_terminal and not self.is_dumb_terminal) + if force_interactive is None + else force_interactive + ) + + self._record_buffer_lock = threading.RLock() + self._thread_locals = ConsoleThreadLocals( + theme_stack=ThemeStack(themes.DEFAULT if theme is None else theme) + ) + self._record_buffer: List[Segment] = [] + self._render_hooks: List[RenderHook] = [] + self._live: Optional["Live"] = None + self._is_alt_screen = False + + def __repr__(self) -> str: + return f"" + + @property + def file(self) -> IO[str]: + """Get the file object to write to.""" + file = self._file or (sys.stderr if self.stderr else sys.stdout) + file = getattr(file, "rich_proxied_file", file) + if file is None: + file = NULL_FILE + return file + + @file.setter + def file(self, new_file: IO[str]) -> None: + """Set a new file object.""" + self._file = new_file + + @property + def _buffer(self) -> List[Segment]: + """Get a thread local buffer.""" + return self._thread_locals.buffer + + @property + def _buffer_index(self) -> int: + """Get a thread local buffer.""" + return self._thread_locals.buffer_index + + @_buffer_index.setter + def _buffer_index(self, value: int) -> None: + self._thread_locals.buffer_index = value + + @property + def _theme_stack(self) -> ThemeStack: + """Get the thread local theme stack.""" + return self._thread_locals.theme_stack + + def _detect_color_system(self) -> Optional[ColorSystem]: + """Detect color system from env vars.""" + if self.is_jupyter: + return ColorSystem.TRUECOLOR + if not self.is_terminal or self.is_dumb_terminal: + return None + if WINDOWS: # pragma: no cover + if self.legacy_windows: # pragma: no cover + return ColorSystem.WINDOWS + windows_console_features = get_windows_console_features() + return ( + ColorSystem.TRUECOLOR + if windows_console_features.truecolor + else ColorSystem.EIGHT_BIT + ) + else: + color_term = self._environ.get("COLORTERM", "").strip().lower() + if color_term in ("truecolor", "24bit"): + return ColorSystem.TRUECOLOR + term = self._environ.get("TERM", "").strip().lower() + _term_name, _hyphen, colors = term.rpartition("-") + color_system = _TERM_COLORS.get(colors, ColorSystem.STANDARD) + return color_system + + def _enter_buffer(self) -> None: + """Enter in to a buffer context, and buffer all output.""" + self._buffer_index += 1 + + def _exit_buffer(self) -> None: + """Leave buffer context, and render content if required.""" + self._buffer_index -= 1 + self._check_buffer() + + def set_live(self, live: "Live") -> None: + """Set Live instance. Used by Live context manager. + + Args: + live (Live): Live instance using this Console. + + Raises: + errors.LiveError: If this Console has a Live context currently active. + """ + with self._lock: + if self._live is not None: + raise errors.LiveError("Only one live display may be active at once") + self._live = live + + def clear_live(self) -> None: + """Clear the Live instance.""" + with self._lock: + self._live = None + + def push_render_hook(self, hook: RenderHook) -> None: + """Add a new render hook to the stack. + + Args: + hook (RenderHook): Render hook instance. + """ + with self._lock: + self._render_hooks.append(hook) + + def pop_render_hook(self) -> None: + """Pop the last renderhook from the stack.""" + with self._lock: + self._render_hooks.pop() + + def __enter__(self) -> "Console": + """Own context manager to enter buffer context.""" + self._enter_buffer() + return self + + def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: + """Exit buffer context.""" + self._exit_buffer() + + def begin_capture(self) -> None: + """Begin capturing console output. Call :meth:`end_capture` to exit capture mode and return output.""" + self._enter_buffer() + + def end_capture(self) -> str: + """End capture mode and return captured string. + + Returns: + str: Console output. + """ + render_result = self._render_buffer(self._buffer) + del self._buffer[:] + self._exit_buffer() + return render_result + + def push_theme(self, theme: Theme, *, inherit: bool = True) -> None: + """Push a new theme on to the top of the stack, replacing the styles from the previous theme. + Generally speaking, you should call :meth:`~rich.console.Console.use_theme` to get a context manager, rather + than calling this method directly. + + Args: + theme (Theme): A theme instance. + inherit (bool, optional): Inherit existing styles. Defaults to True. + """ + self._theme_stack.push_theme(theme, inherit=inherit) + + def pop_theme(self) -> None: + """Remove theme from top of stack, restoring previous theme.""" + self._theme_stack.pop_theme() + + def use_theme(self, theme: Theme, *, inherit: bool = True) -> ThemeContext: + """Use a different theme for the duration of the context manager. + + Args: + theme (Theme): Theme instance to user. + inherit (bool, optional): Inherit existing console styles. Defaults to True. + + Returns: + ThemeContext: [description] + """ + return ThemeContext(self, theme, inherit) + + @property + def color_system(self) -> Optional[str]: + """Get color system string. + + Returns: + Optional[str]: "standard", "256" or "truecolor". + """ + + if self._color_system is not None: + return _COLOR_SYSTEMS_NAMES[self._color_system] + else: + return None + + @property + def encoding(self) -> str: + """Get the encoding of the console file, e.g. ``"utf-8"``. + + Returns: + str: A standard encoding string. + """ + return (getattr(self.file, "encoding", "utf-8") or "utf-8").lower() + + @property + def is_terminal(self) -> bool: + """Check if the console is writing to a terminal. + + Returns: + bool: True if the console writing to a device capable of + understanding terminal codes, otherwise False. + """ + if self._force_terminal is not None: + return self._force_terminal + + if hasattr(sys.stdin, "__module__") and sys.stdin.__module__.startswith( + "idlelib" + ): + # Return False for Idle which claims to be a tty but can't handle ansi codes + return False + + if self.is_jupyter: + # return False for Jupyter, which may have FORCE_COLOR set + return False + + # If FORCE_COLOR env var has any value at all, we assume a terminal. + force_color = self._environ.get("FORCE_COLOR") + if force_color is not None: + self._force_terminal = True + return True + + isatty: Optional[Callable[[], bool]] = getattr(self.file, "isatty", None) + try: + return False if isatty is None else isatty() + except ValueError: + # in some situation (at the end of a pytest run for example) isatty() can raise + # ValueError: I/O operation on closed file + # return False because we aren't in a terminal anymore + return False + + @property + def is_dumb_terminal(self) -> bool: + """Detect dumb terminal. + + Returns: + bool: True if writing to a dumb terminal, otherwise False. + + """ + _term = self._environ.get("TERM", "") + is_dumb = _term.lower() in ("dumb", "unknown") + return self.is_terminal and is_dumb + + @property + def options(self) -> ConsoleOptions: + """Get default console options.""" + return ConsoleOptions( + max_height=self.size.height, + size=self.size, + legacy_windows=self.legacy_windows, + min_width=1, + max_width=self.width, + encoding=self.encoding, + is_terminal=self.is_terminal, + ) + + @property + def size(self) -> ConsoleDimensions: + """Get the size of the console. + + Returns: + ConsoleDimensions: A named tuple containing the dimensions. + """ + + if self._width is not None and self._height is not None: + return ConsoleDimensions(self._width - self.legacy_windows, self._height) + + if self.is_dumb_terminal: + return ConsoleDimensions(80, 25) + + width: Optional[int] = None + height: Optional[int] = None + + if WINDOWS: # pragma: no cover + try: + width, height = os.get_terminal_size() + except (AttributeError, ValueError, OSError): # Probably not a terminal + pass + else: + for file_descriptor in _STD_STREAMS: + try: + width, height = os.get_terminal_size(file_descriptor) + except (AttributeError, ValueError, OSError): + pass + else: + break + + columns = self._environ.get("COLUMNS") + if columns is not None and columns.isdigit(): + width = int(columns) + lines = self._environ.get("LINES") + if lines is not None and lines.isdigit(): + height = int(lines) + + # get_terminal_size can report 0, 0 if run from pseudo-terminal + width = width or 80 + height = height or 25 + return ConsoleDimensions( + width - self.legacy_windows if self._width is None else self._width, + height if self._height is None else self._height, + ) + + @size.setter + def size(self, new_size: Tuple[int, int]) -> None: + """Set a new size for the terminal. + + Args: + new_size (Tuple[int, int]): New width and height. + """ + width, height = new_size + self._width = width + self._height = height + + @property + def width(self) -> int: + """Get the width of the console. + + Returns: + int: The width (in characters) of the console. + """ + return self.size.width + + @width.setter + def width(self, width: int) -> None: + """Set width. + + Args: + width (int): New width. + """ + self._width = width + + @property + def height(self) -> int: + """Get the height of the console. + + Returns: + int: The height (in lines) of the console. + """ + return self.size.height + + @height.setter + def height(self, height: int) -> None: + """Set height. + + Args: + height (int): new height. + """ + self._height = height + + def bell(self) -> None: + """Play a 'bell' sound (if supported by the terminal).""" + self.control(Control.bell()) + + def capture(self) -> Capture: + """A context manager to *capture* the result of print() or log() in a string, + rather than writing it to the console. + + Example: + >>> from rich.console import Console + >>> console = Console() + >>> with console.capture() as capture: + ... console.print("[bold magenta]Hello World[/]") + >>> print(capture.get()) + + Returns: + Capture: Context manager with disables writing to the terminal. + """ + capture = Capture(self) + return capture + + def pager( + self, pager: Optional[Pager] = None, styles: bool = False, links: bool = False + ) -> PagerContext: + """A context manager to display anything printed within a "pager". The pager application + is defined by the system and will typically support at least pressing a key to scroll. + + Args: + pager (Pager, optional): A pager object, or None to use :class:`~rich.pager.SystemPager`. Defaults to None. + styles (bool, optional): Show styles in pager. Defaults to False. + links (bool, optional): Show links in pager. Defaults to False. + + Example: + >>> from rich.console import Console + >>> from rich.__main__ import make_test_card + >>> console = Console() + >>> with console.pager(): + console.print(make_test_card()) + + Returns: + PagerContext: A context manager. + """ + return PagerContext(self, pager=pager, styles=styles, links=links) + + def line(self, count: int = 1) -> None: + """Write new line(s). + + Args: + count (int, optional): Number of new lines. Defaults to 1. + """ + + assert count >= 0, "count must be >= 0" + self.print(NewLine(count)) + + def clear(self, home: bool = True) -> None: + """Clear the screen. + + Args: + home (bool, optional): Also move the cursor to 'home' position. Defaults to True. + """ + if home: + self.control(Control.clear(), Control.home()) + else: + self.control(Control.clear()) + + def status( + self, + status: RenderableType, + *, + spinner: str = "dots", + spinner_style: StyleType = "status.spinner", + speed: float = 1.0, + refresh_per_second: float = 12.5, + ) -> "Status": + """Display a status and spinner. + + Args: + status (RenderableType): A status renderable (str or Text typically). + spinner (str, optional): Name of spinner animation (see python -m rich.spinner). Defaults to "dots". + spinner_style (StyleType, optional): Style of spinner. Defaults to "status.spinner". + speed (float, optional): Speed factor for spinner animation. Defaults to 1.0. + refresh_per_second (float, optional): Number of refreshes per second. Defaults to 12.5. + + Returns: + Status: A Status object that may be used as a context manager. + """ + from .status import Status + + status_renderable = Status( + status, + console=self, + spinner=spinner, + spinner_style=spinner_style, + speed=speed, + refresh_per_second=refresh_per_second, + ) + return status_renderable + + def show_cursor(self, show: bool = True) -> bool: + """Show or hide the cursor. + + Args: + show (bool, optional): Set visibility of the cursor. + """ + if self.is_terminal: + self.control(Control.show_cursor(show)) + return True + return False + + def set_alt_screen(self, enable: bool = True) -> bool: + """Enables alternative screen mode. + + Note, if you enable this mode, you should ensure that is disabled before + the application exits. See :meth:`~rich.Console.screen` for a context manager + that handles this for you. + + Args: + enable (bool, optional): Enable (True) or disable (False) alternate screen. Defaults to True. + + Returns: + bool: True if the control codes were written. + + """ + changed = False + if self.is_terminal and not self.legacy_windows: + self.control(Control.alt_screen(enable)) + changed = True + self._is_alt_screen = enable + return changed + + @property + def is_alt_screen(self) -> bool: + """Check if the alt screen was enabled. + + Returns: + bool: True if the alt screen was enabled, otherwise False. + """ + return self._is_alt_screen + + def set_window_title(self, title: str) -> bool: + """Set the title of the console terminal window. + + Warning: There is no means within Rich of "resetting" the window title to its + previous value, meaning the title you set will persist even after your application + exits. + + ``fish`` shell resets the window title before and after each command by default, + negating this issue. Windows Terminal and command prompt will also reset the title for you. + Most other shells and terminals, however, do not do this. + + Some terminals may require configuration changes before you can set the title. + Some terminals may not support setting the title at all. + + Other software (including the terminal itself, the shell, custom prompts, plugins, etc.) + may also set the terminal window title. This could result in whatever value you write + using this method being overwritten. + + Args: + title (str): The new title of the terminal window. + + Returns: + bool: True if the control code to change the terminal title was + written, otherwise False. Note that a return value of True + does not guarantee that the window title has actually changed, + since the feature may be unsupported/disabled in some terminals. + """ + if self.is_terminal: + self.control(Control.title(title)) + return True + return False + + def screen( + self, hide_cursor: bool = True, style: Optional[StyleType] = None + ) -> "ScreenContext": + """Context manager to enable and disable 'alternative screen' mode. + + Args: + hide_cursor (bool, optional): Also hide the cursor. Defaults to False. + style (Style, optional): Optional style for screen. Defaults to None. + + Returns: + ~ScreenContext: Context which enables alternate screen on enter, and disables it on exit. + """ + return ScreenContext(self, hide_cursor=hide_cursor, style=style or "") + + def measure( + self, renderable: RenderableType, *, options: Optional[ConsoleOptions] = None + ) -> Measurement: + """Measure a renderable. Returns a :class:`~rich.measure.Measurement` object which contains + information regarding the number of characters required to print the renderable. + + Args: + renderable (RenderableType): Any renderable or string. + options (Optional[ConsoleOptions], optional): Options to use when measuring, or None + to use default options. Defaults to None. + + Returns: + Measurement: A measurement of the renderable. + """ + measurement = Measurement.get(self, options or self.options, renderable) + return measurement + + def render( + self, renderable: RenderableType, options: Optional[ConsoleOptions] = None + ) -> Iterable[Segment]: + """Render an object in to an iterable of `Segment` instances. + + This method contains the logic for rendering objects with the console protocol. + You are unlikely to need to use it directly, unless you are extending the library. + + Args: + renderable (RenderableType): An object supporting the console protocol, or + an object that may be converted to a string. + options (ConsoleOptions, optional): An options object, or None to use self.options. Defaults to None. + + Returns: + Iterable[Segment]: An iterable of segments that may be rendered. + """ + + _options = options or self.options + if _options.max_width < 1: + # No space to render anything. This prevents potential recursion errors. + return + render_iterable: RenderResult + + renderable = rich_cast(renderable) + if hasattr(renderable, "__rich_console__") and not isclass(renderable): + render_iterable = renderable.__rich_console__(self, _options) # type: ignore[union-attr] + elif isinstance(renderable, str): + text_renderable = self.render_str( + renderable, highlight=_options.highlight, markup=_options.markup + ) + render_iterable = text_renderable.__rich_console__(self, _options) + else: + raise errors.NotRenderableError( + f"Unable to render {renderable!r}; " + "A str, Segment or object with __rich_console__ method is required" + ) + + try: + iter_render = iter(render_iterable) + except TypeError: + raise errors.NotRenderableError( + f"object {render_iterable!r} is not renderable" + ) + _Segment = Segment + _options = _options.reset_height() + for render_output in iter_render: + if isinstance(render_output, _Segment): + yield render_output + else: + yield from self.render(render_output, _options) + + def render_lines( + self, + renderable: RenderableType, + options: Optional[ConsoleOptions] = None, + *, + style: Optional[Style] = None, + pad: bool = True, + new_lines: bool = False, + ) -> List[List[Segment]]: + """Render objects in to a list of lines. + + The output of render_lines is useful when further formatting of rendered console text + is required, such as the Panel class which draws a border around any renderable object. + + Args: + renderable (RenderableType): Any object renderable in the console. + options (Optional[ConsoleOptions], optional): Console options, or None to use self.options. Default to ``None``. + style (Style, optional): Optional style to apply to renderables. Defaults to ``None``. + pad (bool, optional): Pad lines shorter than render width. Defaults to ``True``. + new_lines (bool, optional): Include "\n" characters at end of lines. + + Returns: + List[List[Segment]]: A list of lines, where a line is a list of Segment objects. + """ + with self._lock: + render_options = options or self.options + _rendered = self.render(renderable, render_options) + if style: + _rendered = Segment.apply_style(_rendered, style) + + render_height = render_options.height + if render_height is not None: + render_height = max(0, render_height) + + lines = list( + islice( + Segment.split_and_crop_lines( + _rendered, + render_options.max_width, + include_new_lines=new_lines, + pad=pad, + style=style, + ), + None, + render_height, + ) + ) + if render_options.height is not None: + extra_lines = render_options.height - len(lines) + if extra_lines > 0: + pad_line = [ + [Segment(" " * render_options.max_width, style), Segment("\n")] + if new_lines + else [Segment(" " * render_options.max_width, style)] + ] + lines.extend(pad_line * extra_lines) + + return lines + + def render_str( + self, + text: str, + *, + style: Union[str, Style] = "", + justify: Optional[JustifyMethod] = None, + overflow: Optional[OverflowMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + highlighter: Optional[HighlighterType] = None, + ) -> "Text": + """Convert a string to a Text instance. This is called automatically if + you print or log a string. + + Args: + text (str): Text to render. + style (Union[str, Style], optional): Style to apply to rendered text. + justify (str, optional): Justify method: "default", "left", "center", "full", or "right". Defaults to ``None``. + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to ``None``. + emoji (Optional[bool], optional): Enable emoji, or ``None`` to use Console default. + markup (Optional[bool], optional): Enable markup, or ``None`` to use Console default. + highlight (Optional[bool], optional): Enable highlighting, or ``None`` to use Console default. + highlighter (HighlighterType, optional): Optional highlighter to apply. + Returns: + ConsoleRenderable: Renderable object. + + """ + emoji_enabled = emoji or (emoji is None and self._emoji) + markup_enabled = markup or (markup is None and self._markup) + highlight_enabled = highlight or (highlight is None and self._highlight) + + if markup_enabled: + rich_text = render_markup( + text, + style=style, + emoji=emoji_enabled, + emoji_variant=self._emoji_variant, + ) + rich_text.justify = justify + rich_text.overflow = overflow + else: + rich_text = Text( + _emoji_replace(text, default_variant=self._emoji_variant) + if emoji_enabled + else text, + justify=justify, + overflow=overflow, + style=style, + ) + + _highlighter = (highlighter or self.highlighter) if highlight_enabled else None + if _highlighter is not None: + highlight_text = _highlighter(str(rich_text)) + highlight_text.copy_styles(rich_text) + return highlight_text + + return rich_text + + def get_style( + self, name: Union[str, Style], *, default: Optional[Union[Style, str]] = None + ) -> Style: + """Get a Style instance by its theme name or parse a definition. + + Args: + name (str): The name of a style or a style definition. + + Returns: + Style: A Style object. + + Raises: + MissingStyle: If no style could be parsed from name. + + """ + if isinstance(name, Style): + return name + + try: + style = self._theme_stack.get(name) + if style is None: + style = Style.parse(name) + return style.copy() if style.link else style + except errors.StyleSyntaxError as error: + if default is not None: + return self.get_style(default) + raise errors.MissingStyle( + f"Failed to get style {name!r}; {error}" + ) from None + + def _collect_renderables( + self, + objects: Iterable[Any], + sep: str, + end: str, + *, + justify: Optional[JustifyMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + ) -> List[ConsoleRenderable]: + """Combine a number of renderables and text into one renderable. + + Args: + objects (Iterable[Any]): Anything that Rich can render. + sep (str): String to write between print data. + end (str): String to write at end of print data. + justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. + + Returns: + List[ConsoleRenderable]: A list of things to render. + """ + renderables: List[ConsoleRenderable] = [] + _append = renderables.append + text: List[Text] = [] + append_text = text.append + + append = _append + if justify in ("left", "center", "right"): + + def align_append(renderable: RenderableType) -> None: + _append(Align(renderable, cast(AlignMethod, justify))) + + append = align_append + + _highlighter: HighlighterType = _null_highlighter + if highlight or (highlight is None and self._highlight): + _highlighter = self.highlighter + + def check_text() -> None: + if text: + sep_text = Text(sep, justify=justify, end=end) + append(sep_text.join(text)) + text.clear() + + for renderable in objects: + renderable = rich_cast(renderable) + if isinstance(renderable, str): + append_text( + self.render_str( + renderable, emoji=emoji, markup=markup, highlighter=_highlighter + ) + ) + elif isinstance(renderable, Text): + append_text(renderable) + elif isinstance(renderable, ConsoleRenderable): + check_text() + append(renderable) + elif is_expandable(renderable): + check_text() + append(Pretty(renderable, highlighter=_highlighter)) + else: + append_text(_highlighter(str(renderable))) + + check_text() + + if self.style is not None: + style = self.get_style(self.style) + renderables = [Styled(renderable, style) for renderable in renderables] + + return renderables + + def rule( + self, + title: TextType = "", + *, + characters: str = "─", + style: Union[str, Style] = "rule.line", + align: AlignMethod = "center", + ) -> None: + """Draw a line with optional centered title. + + Args: + title (str, optional): Text to render over the rule. Defaults to "". + characters (str, optional): Character(s) to form the line. Defaults to "─". + style (str, optional): Style of line. Defaults to "rule.line". + align (str, optional): How to align the title, one of "left", "center", or "right". Defaults to "center". + """ + from .rule import Rule + + rule = Rule(title=title, characters=characters, style=style, align=align) + self.print(rule) + + def control(self, *control: Control) -> None: + """Insert non-printing control codes. + + Args: + control_codes (str): Control codes, such as those that may move the cursor. + """ + if not self.is_dumb_terminal: + with self: + self._buffer.extend(_control.segment for _control in control) + + def out( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + highlight: Optional[bool] = None, + ) -> None: + """Output to the terminal. This is a low-level way of writing to the terminal which unlike + :meth:`~rich.console.Console.print` won't pretty print, wrap text, or apply markup, but will + optionally apply highlighting and a basic style. + + Args: + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use + console default. Defaults to ``None``. + """ + raw_output: str = sep.join(str(_object) for _object in objects) + self.print( + raw_output, + style=style, + highlight=highlight, + emoji=False, + markup=False, + no_wrap=True, + overflow="ignore", + crop=False, + end=end, + ) + + def print( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + justify: Optional[JustifyMethod] = None, + overflow: Optional[OverflowMethod] = None, + no_wrap: Optional[bool] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + width: Optional[int] = None, + height: Optional[int] = None, + crop: bool = True, + soft_wrap: Optional[bool] = None, + new_line_start: bool = False, + ) -> None: + """Print to the console. + + Args: + objects (positional args): Objects to log to the terminal. + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + justify (str, optional): Justify method: "default", "left", "right", "center", or "full". Defaults to ``None``. + overflow (str, optional): Overflow method: "ignore", "crop", "fold", or "ellipsis". Defaults to None. + no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to None. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to ``None``. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to ``None``. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to ``None``. + width (Optional[int], optional): Width of output, or ``None`` to auto-detect. Defaults to ``None``. + crop (Optional[bool], optional): Crop output to width of terminal. Defaults to True. + soft_wrap (bool, optional): Enable soft wrap mode which disables word wrapping and cropping of text or ``None`` for + Console default. Defaults to ``None``. + new_line_start (bool, False): Insert a new line at the start if the output contains more than one line. Defaults to ``False``. + """ + if not objects: + objects = (NewLine(),) + + if soft_wrap is None: + soft_wrap = self.soft_wrap + if soft_wrap: + if no_wrap is None: + no_wrap = True + if overflow is None: + overflow = "ignore" + crop = False + render_hooks = self._render_hooks[:] + with self: + renderables = self._collect_renderables( + objects, + sep, + end, + justify=justify, + emoji=emoji, + markup=markup, + highlight=highlight, + ) + for hook in render_hooks: + renderables = hook.process_renderables(renderables) + render_options = self.options.update( + justify=justify, + overflow=overflow, + width=min(width, self.width) if width is not None else NO_CHANGE, + height=height, + no_wrap=no_wrap, + markup=markup, + highlight=highlight, + ) + + new_segments: List[Segment] = [] + extend = new_segments.extend + render = self.render + if style is None: + for renderable in renderables: + extend(render(renderable, render_options)) + else: + for renderable in renderables: + extend( + Segment.apply_style( + render(renderable, render_options), self.get_style(style) + ) + ) + if new_line_start: + if ( + len("".join(segment.text for segment in new_segments).splitlines()) + > 1 + ): + new_segments.insert(0, Segment.line()) + if crop: + buffer_extend = self._buffer.extend + for line in Segment.split_and_crop_lines( + new_segments, self.width, pad=False + ): + buffer_extend(line) + else: + self._buffer.extend(new_segments) + + def print_json( + self, + json: Optional[str] = None, + *, + data: Any = None, + indent: Union[None, int, str] = 2, + highlight: bool = True, + skip_keys: bool = False, + ensure_ascii: bool = False, + check_circular: bool = True, + allow_nan: bool = True, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + ) -> None: + """Pretty prints JSON. Output will be valid JSON. + + Args: + json (Optional[str]): A string containing JSON. + data (Any): If json is not supplied, then encode this data. + indent (Union[None, int, str], optional): Number of spaces to indent. Defaults to 2. + highlight (bool, optional): Enable highlighting of output: Defaults to True. + skip_keys (bool, optional): Skip keys not of a basic type. Defaults to False. + ensure_ascii (bool, optional): Escape all non-ascii characters. Defaults to False. + check_circular (bool, optional): Check for circular references. Defaults to True. + allow_nan (bool, optional): Allow NaN and Infinity values. Defaults to True. + default (Callable, optional): A callable that converts values that can not be encoded + in to something that can be JSON encoded. Defaults to None. + sort_keys (bool, optional): Sort dictionary keys. Defaults to False. + """ + from pip._vendor.rich.json import JSON + + if json is None: + json_renderable = JSON.from_data( + data, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + else: + if not isinstance(json, str): + raise TypeError( + f"json must be str. Did you mean print_json(data={json!r}) ?" + ) + json_renderable = JSON( + json, + indent=indent, + highlight=highlight, + skip_keys=skip_keys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + default=default, + sort_keys=sort_keys, + ) + self.print(json_renderable, soft_wrap=True) + + def update_screen( + self, + renderable: RenderableType, + *, + region: Optional[Region] = None, + options: Optional[ConsoleOptions] = None, + ) -> None: + """Update the screen at a given offset. + + Args: + renderable (RenderableType): A Rich renderable. + region (Region, optional): Region of screen to update, or None for entire screen. Defaults to None. + x (int, optional): x offset. Defaults to 0. + y (int, optional): y offset. Defaults to 0. + + Raises: + errors.NoAltScreen: If the Console isn't in alt screen mode. + + """ + if not self.is_alt_screen: + raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") + render_options = options or self.options + if region is None: + x = y = 0 + render_options = render_options.update_dimensions( + render_options.max_width, render_options.height or self.height + ) + else: + x, y, width, height = region + render_options = render_options.update_dimensions(width, height) + + lines = self.render_lines(renderable, options=render_options) + self.update_screen_lines(lines, x, y) + + def update_screen_lines( + self, lines: List[List[Segment]], x: int = 0, y: int = 0 + ) -> None: + """Update lines of the screen at a given offset. + + Args: + lines (List[List[Segment]]): Rendered lines (as produced by :meth:`~rich.Console.render_lines`). + x (int, optional): x offset (column no). Defaults to 0. + y (int, optional): y offset (column no). Defaults to 0. + + Raises: + errors.NoAltScreen: If the Console isn't in alt screen mode. + """ + if not self.is_alt_screen: + raise errors.NoAltScreen("Alt screen must be enabled to call update_screen") + screen_update = ScreenUpdate(lines, x, y) + segments = self.render(screen_update) + self._buffer.extend(segments) + self._check_buffer() + + def print_exception( + self, + *, + width: Optional[int] = 100, + extra_lines: int = 3, + theme: Optional[str] = None, + word_wrap: bool = False, + show_locals: bool = False, + suppress: Iterable[Union[str, ModuleType]] = (), + max_frames: int = 100, + ) -> None: + """Prints a rich render of the last exception and traceback. + + Args: + width (Optional[int], optional): Number of characters used to render code. Defaults to 100. + extra_lines (int, optional): Additional lines of code to render. Defaults to 3. + theme (str, optional): Override pygments theme used in traceback + word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False. + show_locals (bool, optional): Enable display of local variables. Defaults to False. + suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback. + max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. + """ + from .traceback import Traceback + + traceback = Traceback( + width=width, + extra_lines=extra_lines, + theme=theme, + word_wrap=word_wrap, + show_locals=show_locals, + suppress=suppress, + max_frames=max_frames, + ) + self.print(traceback) + + @staticmethod + def _caller_frame_info( + offset: int, + currentframe: Callable[[], Optional[FrameType]] = inspect.currentframe, + ) -> Tuple[str, int, Dict[str, Any]]: + """Get caller frame information. + + Args: + offset (int): the caller offset within the current frame stack. + currentframe (Callable[[], Optional[FrameType]], optional): the callable to use to + retrieve the current frame. Defaults to ``inspect.currentframe``. + + Returns: + Tuple[str, int, Dict[str, Any]]: A tuple containing the filename, the line number and + the dictionary of local variables associated with the caller frame. + + Raises: + RuntimeError: If the stack offset is invalid. + """ + # Ignore the frame of this local helper + offset += 1 + + frame = currentframe() + if frame is not None: + # Use the faster currentframe where implemented + while offset and frame is not None: + frame = frame.f_back + offset -= 1 + assert frame is not None + return frame.f_code.co_filename, frame.f_lineno, frame.f_locals + else: + # Fallback to the slower stack + frame_info = inspect.stack()[offset] + return frame_info.filename, frame_info.lineno, frame_info.frame.f_locals + + def log( + self, + *objects: Any, + sep: str = " ", + end: str = "\n", + style: Optional[Union[str, Style]] = None, + justify: Optional[JustifyMethod] = None, + emoji: Optional[bool] = None, + markup: Optional[bool] = None, + highlight: Optional[bool] = None, + log_locals: bool = False, + _stack_offset: int = 1, + ) -> None: + """Log rich content to the terminal. + + Args: + objects (positional args): Objects to log to the terminal. + sep (str, optional): String to write between print data. Defaults to " ". + end (str, optional): String to write at end of print data. Defaults to "\\\\n". + style (Union[str, Style], optional): A style to apply to output. Defaults to None. + justify (str, optional): One of "left", "right", "center", or "full". Defaults to ``None``. + overflow (str, optional): Overflow method: "crop", "fold", or "ellipsis". Defaults to None. + emoji (Optional[bool], optional): Enable emoji code, or ``None`` to use console default. Defaults to None. + markup (Optional[bool], optional): Enable markup, or ``None`` to use console default. Defaults to None. + highlight (Optional[bool], optional): Enable automatic highlighting, or ``None`` to use console default. Defaults to None. + log_locals (bool, optional): Boolean to enable logging of locals where ``log()`` + was called. Defaults to False. + _stack_offset (int, optional): Offset of caller from end of call stack. Defaults to 1. + """ + if not objects: + objects = (NewLine(),) + + render_hooks = self._render_hooks[:] + + with self: + renderables = self._collect_renderables( + objects, + sep, + end, + justify=justify, + emoji=emoji, + markup=markup, + highlight=highlight, + ) + if style is not None: + renderables = [Styled(renderable, style) for renderable in renderables] + + filename, line_no, locals = self._caller_frame_info(_stack_offset) + link_path = None if filename.startswith("<") else os.path.abspath(filename) + path = filename.rpartition(os.sep)[-1] + if log_locals: + locals_map = { + key: value + for key, value in locals.items() + if not key.startswith("__") + } + renderables.append(render_scope(locals_map, title="[i]locals")) + + renderables = [ + self._log_render( + self, + renderables, + log_time=self.get_datetime(), + path=path, + line_no=line_no, + link_path=link_path, + ) + ] + for hook in render_hooks: + renderables = hook.process_renderables(renderables) + new_segments: List[Segment] = [] + extend = new_segments.extend + render = self.render + render_options = self.options + for renderable in renderables: + extend(render(renderable, render_options)) + buffer_extend = self._buffer.extend + for line in Segment.split_and_crop_lines( + new_segments, self.width, pad=False + ): + buffer_extend(line) + + def _check_buffer(self) -> None: + """Check if the buffer may be rendered. Render it if it can (e.g. Console.quiet is False) + Rendering is supported on Windows, Unix and Jupyter environments. For + legacy Windows consoles, the win32 API is called directly. + This method will also record what it renders if recording is enabled via Console.record. + """ + if self.quiet: + del self._buffer[:] + return + with self._lock: + if self.record: + with self._record_buffer_lock: + self._record_buffer.extend(self._buffer[:]) + + if self._buffer_index == 0: + if self.is_jupyter: # pragma: no cover + from .jupyter import display + + display(self._buffer, self._render_buffer(self._buffer[:])) + del self._buffer[:] + else: + if WINDOWS: + use_legacy_windows_render = False + if self.legacy_windows: + fileno = get_fileno(self.file) + if fileno is not None: + use_legacy_windows_render = ( + fileno in _STD_STREAMS_OUTPUT + ) + + if use_legacy_windows_render: + from pip._vendor.rich._win32_console import LegacyWindowsTerm + from pip._vendor.rich._windows_renderer import legacy_windows_render + + buffer = self._buffer[:] + if self.no_color and self._color_system: + buffer = list(Segment.remove_color(buffer)) + + legacy_windows_render(buffer, LegacyWindowsTerm(self.file)) + else: + # Either a non-std stream on legacy Windows, or modern Windows. + text = self._render_buffer(self._buffer[:]) + # https://bugs.python.org/issue37871 + # https://github.com/python/cpython/issues/82052 + # We need to avoid writing more than 32Kb in a single write, due to the above bug + write = self.file.write + # Worse case scenario, every character is 4 bytes of utf-8 + MAX_WRITE = 32 * 1024 // 4 + try: + if len(text) <= MAX_WRITE: + write(text) + else: + batch: List[str] = [] + batch_append = batch.append + size = 0 + for line in text.splitlines(True): + if size + len(line) > MAX_WRITE and batch: + write("".join(batch)) + batch.clear() + size = 0 + batch_append(line) + size += len(line) + if batch: + write("".join(batch)) + batch.clear() + except UnicodeEncodeError as error: + error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" + raise + else: + text = self._render_buffer(self._buffer[:]) + try: + self.file.write(text) + except UnicodeEncodeError as error: + error.reason = f"{error.reason}\n*** You may need to add PYTHONIOENCODING=utf-8 to your environment ***" + raise + + self.file.flush() + del self._buffer[:] + + def _render_buffer(self, buffer: Iterable[Segment]) -> str: + """Render buffered output, and clear buffer.""" + output: List[str] = [] + append = output.append + color_system = self._color_system + legacy_windows = self.legacy_windows + not_terminal = not self.is_terminal + if self.no_color and color_system: + buffer = Segment.remove_color(buffer) + for text, style, control in buffer: + if style: + append( + style.render( + text, + color_system=color_system, + legacy_windows=legacy_windows, + ) + ) + elif not (not_terminal and control): + append(text) + + rendered = "".join(output) + return rendered + + def input( + self, + prompt: TextType = "", + *, + markup: bool = True, + emoji: bool = True, + password: bool = False, + stream: Optional[TextIO] = None, + ) -> str: + """Displays a prompt and waits for input from the user. The prompt may contain color / style. + + It works in the same way as Python's builtin :func:`input` function and provides elaborate line editing and history features if Python's builtin :mod:`readline` module is previously loaded. + + Args: + prompt (Union[str, Text]): Text to render in the prompt. + markup (bool, optional): Enable console markup (requires a str prompt). Defaults to True. + emoji (bool, optional): Enable emoji (requires a str prompt). Defaults to True. + password: (bool, optional): Hide typed text. Defaults to False. + stream: (TextIO, optional): Optional file to read input from (rather than stdin). Defaults to None. + + Returns: + str: Text read from stdin. + """ + if prompt: + self.print(prompt, markup=markup, emoji=emoji, end="") + if password: + result = getpass("", stream=stream) + else: + if stream: + result = stream.readline() + else: + result = input() + return result + + def export_text(self, *, clear: bool = True, styles: bool = False) -> str: + """Generate text from console contents (requires record=True argument in constructor). + + Args: + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + styles (bool, optional): If ``True``, ansi escape codes will be included. ``False`` for plain text. + Defaults to ``False``. + + Returns: + str: String containing console contents. + + """ + assert ( + self.record + ), "To export console contents set record=True in the constructor or instance" + + with self._record_buffer_lock: + if styles: + text = "".join( + (style.render(text) if style else text) + for text, style, _ in self._record_buffer + ) + else: + text = "".join( + segment.text + for segment in self._record_buffer + if not segment.control + ) + if clear: + del self._record_buffer[:] + return text + + def save_text(self, path: str, *, clear: bool = True, styles: bool = False) -> None: + """Generate text from console and save to a given location (requires record=True argument in constructor). + + Args: + path (str): Path to write text files. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + styles (bool, optional): If ``True``, ansi style codes will be included. ``False`` for plain text. + Defaults to ``False``. + + """ + text = self.export_text(clear=clear, styles=styles) + with open(path, "wt", encoding="utf-8") as write_file: + write_file.write(text) + + def export_html( + self, + *, + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: Optional[str] = None, + inline_styles: bool = False, + ) -> str: + """Generate HTML from console contents (requires record=True argument in constructor). + + Args: + theme (TerminalTheme, optional): TerminalTheme object containing console colors. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + code_format (str, optional): Format string to render HTML. In addition to '{foreground}', + '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``. + inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files + larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. + Defaults to False. + + Returns: + str: String containing console contents as HTML. + """ + assert ( + self.record + ), "To export console contents set record=True in the constructor or instance" + fragments: List[str] = [] + append = fragments.append + _theme = theme or DEFAULT_TERMINAL_THEME + stylesheet = "" + + render_code_format = CONSOLE_HTML_FORMAT if code_format is None else code_format + + with self._record_buffer_lock: + if inline_styles: + for text, style, _ in Segment.filter_control( + Segment.simplify(self._record_buffer) + ): + text = escape(text) + if style: + rule = style.get_html_style(_theme) + if style.link: + text = f'{text}' + text = f'{text}' if rule else text + append(text) + else: + styles: Dict[str, int] = {} + for text, style, _ in Segment.filter_control( + Segment.simplify(self._record_buffer) + ): + text = escape(text) + if style: + rule = style.get_html_style(_theme) + style_number = styles.setdefault(rule, len(styles) + 1) + if style.link: + text = f'{text}' + else: + text = f'{text}' + append(text) + stylesheet_rules: List[str] = [] + stylesheet_append = stylesheet_rules.append + for style_rule, style_number in styles.items(): + if style_rule: + stylesheet_append(f".r{style_number} {{{style_rule}}}") + stylesheet = "\n".join(stylesheet_rules) + + rendered_code = render_code_format.format( + code="".join(fragments), + stylesheet=stylesheet, + foreground=_theme.foreground_color.hex, + background=_theme.background_color.hex, + ) + if clear: + del self._record_buffer[:] + return rendered_code + + def save_html( + self, + path: str, + *, + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: str = CONSOLE_HTML_FORMAT, + inline_styles: bool = False, + ) -> None: + """Generate HTML from console contents and write to a file (requires record=True argument in constructor). + + Args: + path (str): Path to write html file. + theme (TerminalTheme, optional): TerminalTheme object containing console colors. + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True``. + code_format (str, optional): Format string to render HTML. In addition to '{foreground}', + '{background}', and '{code}', should contain '{stylesheet}' if inline_styles is ``False``. + inline_styles (bool, optional): If ``True`` styles will be inlined in to spans, which makes files + larger but easier to cut and paste markup. If ``False``, styles will be embedded in a style tag. + Defaults to False. + + """ + html = self.export_html( + theme=theme, + clear=clear, + code_format=code_format, + inline_styles=inline_styles, + ) + with open(path, "wt", encoding="utf-8") as write_file: + write_file.write(html) + + def export_svg( + self, + *, + title: str = "Rich", + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: str = CONSOLE_SVG_FORMAT, + font_aspect_ratio: float = 0.61, + unique_id: Optional[str] = None, + ) -> str: + """ + Generate an SVG from the console contents (requires record=True in Console constructor). + + Args: + title (str, optional): The title of the tab in the output image + theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True`` + code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables + into the string in order to form the final SVG output. The default template used and the variables + injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable. + font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format`` + string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font). + If you aren't specifying a different font inside ``code_format``, you probably don't need this. + unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node + ids). If not set, this defaults to a computed value based on the recorded content. + """ + + from pip._vendor.rich.cells import cell_len + + style_cache: Dict[Style, str] = {} + + def get_svg_style(style: Style) -> str: + """Convert a Style to CSS rules for SVG.""" + if style in style_cache: + return style_cache[style] + css_rules = [] + color = ( + _theme.foreground_color + if (style.color is None or style.color.is_default) + else style.color.get_truecolor(_theme) + ) + bgcolor = ( + _theme.background_color + if (style.bgcolor is None or style.bgcolor.is_default) + else style.bgcolor.get_truecolor(_theme) + ) + if style.reverse: + color, bgcolor = bgcolor, color + if style.dim: + color = blend_rgb(color, bgcolor, 0.4) + css_rules.append(f"fill: {color.hex}") + if style.bold: + css_rules.append("font-weight: bold") + if style.italic: + css_rules.append("font-style: italic;") + if style.underline: + css_rules.append("text-decoration: underline;") + if style.strike: + css_rules.append("text-decoration: line-through;") + + css = ";".join(css_rules) + style_cache[style] = css + return css + + _theme = theme or SVG_EXPORT_THEME + + width = self.width + char_height = 20 + char_width = char_height * font_aspect_ratio + line_height = char_height * 1.22 + + margin_top = 1 + margin_right = 1 + margin_bottom = 1 + margin_left = 1 + + padding_top = 40 + padding_right = 8 + padding_bottom = 8 + padding_left = 8 + + padding_width = padding_left + padding_right + padding_height = padding_top + padding_bottom + margin_width = margin_left + margin_right + margin_height = margin_top + margin_bottom + + text_backgrounds: List[str] = [] + text_group: List[str] = [] + classes: Dict[str, int] = {} + style_no = 1 + + def escape_text(text: str) -> str: + """HTML escape text and replace spaces with nbsp.""" + return escape(text).replace(" ", " ") + + def make_tag( + name: str, content: Optional[str] = None, **attribs: object + ) -> str: + """Make a tag from name, content, and attributes.""" + + def stringify(value: object) -> str: + if isinstance(value, (float)): + return format(value, "g") + return str(value) + + tag_attribs = " ".join( + f'{k.lstrip("_").replace("_", "-")}="{stringify(v)}"' + for k, v in attribs.items() + ) + return ( + f"<{name} {tag_attribs}>{content}" + if content + else f"<{name} {tag_attribs}/>" + ) + + with self._record_buffer_lock: + segments = list(Segment.filter_control(self._record_buffer)) + if clear: + self._record_buffer.clear() + + if unique_id is None: + unique_id = "terminal-" + str( + zlib.adler32( + ("".join(repr(segment) for segment in segments)).encode( + "utf-8", + "ignore", + ) + + title.encode("utf-8", "ignore") + ) + ) + y = 0 + for y, line in enumerate(Segment.split_and_crop_lines(segments, length=width)): + x = 0 + for text, style, _control in line: + style = style or Style() + rules = get_svg_style(style) + if rules not in classes: + classes[rules] = style_no + style_no += 1 + class_name = f"r{classes[rules]}" + + if style.reverse: + has_background = True + background = ( + _theme.foreground_color.hex + if style.color is None + else style.color.get_truecolor(_theme).hex + ) + else: + bgcolor = style.bgcolor + has_background = bgcolor is not None and not bgcolor.is_default + background = ( + _theme.background_color.hex + if style.bgcolor is None + else style.bgcolor.get_truecolor(_theme).hex + ) + + text_length = cell_len(text) + if has_background: + text_backgrounds.append( + make_tag( + "rect", + fill=background, + x=x * char_width, + y=y * line_height + 1.5, + width=char_width * text_length, + height=line_height + 0.25, + shape_rendering="crispEdges", + ) + ) + + if text != " " * len(text): + text_group.append( + make_tag( + "text", + escape_text(text), + _class=f"{unique_id}-{class_name}", + x=x * char_width, + y=y * line_height + char_height, + textLength=char_width * len(text), + clip_path=f"url(#{unique_id}-line-{y})", + ) + ) + x += cell_len(text) + + line_offsets = [line_no * line_height + 1.5 for line_no in range(y)] + lines = "\n".join( + f""" + {make_tag("rect", x=0, y=offset, width=char_width * width, height=line_height + 0.25)} + """ + for line_no, offset in enumerate(line_offsets) + ) + + styles = "\n".join( + f".{unique_id}-r{rule_no} {{ {css} }}" for css, rule_no in classes.items() + ) + backgrounds = "".join(text_backgrounds) + matrix = "".join(text_group) + + terminal_width = ceil(width * char_width + padding_width) + terminal_height = (y + 1) * line_height + padding_height + chrome = make_tag( + "rect", + fill=_theme.background_color.hex, + stroke="rgba(255,255,255,0.35)", + stroke_width="1", + x=margin_left, + y=margin_top, + width=terminal_width, + height=terminal_height, + rx=8, + ) + + title_color = _theme.foreground_color.hex + if title: + chrome += make_tag( + "text", + escape_text(title), + _class=f"{unique_id}-title", + fill=title_color, + text_anchor="middle", + x=terminal_width // 2, + y=margin_top + char_height + 6, + ) + chrome += f""" + + + + + + """ + + svg = code_format.format( + unique_id=unique_id, + char_width=char_width, + char_height=char_height, + line_height=line_height, + terminal_width=char_width * width - 1, + terminal_height=(y + 1) * line_height - 1, + width=terminal_width + margin_width, + height=terminal_height + margin_height, + terminal_x=margin_left + padding_left, + terminal_y=margin_top + padding_top, + styles=styles, + chrome=chrome, + backgrounds=backgrounds, + matrix=matrix, + lines=lines, + ) + return svg + + def save_svg( + self, + path: str, + *, + title: str = "Rich", + theme: Optional[TerminalTheme] = None, + clear: bool = True, + code_format: str = CONSOLE_SVG_FORMAT, + font_aspect_ratio: float = 0.61, + unique_id: Optional[str] = None, + ) -> None: + """Generate an SVG file from the console contents (requires record=True in Console constructor). + + Args: + path (str): The path to write the SVG to. + title (str, optional): The title of the tab in the output image + theme (TerminalTheme, optional): The ``TerminalTheme`` object to use to style the terminal + clear (bool, optional): Clear record buffer after exporting. Defaults to ``True`` + code_format (str, optional): Format string used to generate the SVG. Rich will inject a number of variables + into the string in order to form the final SVG output. The default template used and the variables + injected by Rich can be found by inspecting the ``console.CONSOLE_SVG_FORMAT`` variable. + font_aspect_ratio (float, optional): The width to height ratio of the font used in the ``code_format`` + string. Defaults to 0.61, which is the width to height ratio of Fira Code (the default font). + If you aren't specifying a different font inside ``code_format``, you probably don't need this. + unique_id (str, optional): unique id that is used as the prefix for various elements (CSS styles, node + ids). If not set, this defaults to a computed value based on the recorded content. + """ + svg = self.export_svg( + title=title, + theme=theme, + clear=clear, + code_format=code_format, + font_aspect_ratio=font_aspect_ratio, + unique_id=unique_id, + ) + with open(path, "wt", encoding="utf-8") as write_file: + write_file.write(svg) + + +def _svg_hash(svg_main_code: str) -> str: + """Returns a unique hash for the given SVG main code. + + Args: + svg_main_code (str): The content we're going to inject in the SVG envelope. + + Returns: + str: a hash of the given content + """ + return str(zlib.adler32(svg_main_code.encode())) + + +if __name__ == "__main__": # pragma: no cover + console = Console(record=True) + + console.log( + "JSONRPC [i]request[/i]", + 5, + 1.3, + True, + False, + None, + { + "jsonrpc": "2.0", + "method": "subtract", + "params": {"minuend": 42, "subtrahend": 23}, + "id": 3, + }, + ) + + console.log("Hello, World!", "{'a': 1}", repr(console)) + + console.print( + { + "name": None, + "empty": [], + "quiz": { + "sport": { + "answered": True, + "q1": { + "question": "Which one is correct team name in NBA?", + "options": [ + "New York Bulls", + "Los Angeles Kings", + "Golden State Warriors", + "Huston Rocket", + ], + "answer": "Huston Rocket", + }, + }, + "maths": { + "answered": False, + "q1": { + "question": "5 + 7 = ?", + "options": [10, 11, 12, 13], + "answer": 12, + }, + "q2": { + "question": "12 - 8 = ?", + "options": [1, 2, 3, 4], + "answer": 4, + }, + }, + }, + } + ) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/constrain.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/constrain.py new file mode 100644 index 00000000..65fdf563 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/constrain.py @@ -0,0 +1,37 @@ +from typing import Optional, TYPE_CHECKING + +from .jupyter import JupyterMixin +from .measure import Measurement + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderableType, RenderResult + + +class Constrain(JupyterMixin): + """Constrain the width of a renderable to a given number of characters. + + Args: + renderable (RenderableType): A renderable object. + width (int, optional): The maximum width (in characters) to render. Defaults to 80. + """ + + def __init__(self, renderable: "RenderableType", width: Optional[int] = 80) -> None: + self.renderable = renderable + self.width = width + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if self.width is None: + yield self.renderable + else: + child_options = options.update_width(min(self.width, options.max_width)) + yield from console.render(self.renderable, child_options) + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + if self.width is not None: + options = options.update_width(self.width) + measurement = Measurement.get(console, options, self.renderable) + return measurement diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/containers.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/containers.py new file mode 100644 index 00000000..e29cf368 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/containers.py @@ -0,0 +1,167 @@ +from itertools import zip_longest +from typing import ( + Iterator, + Iterable, + List, + Optional, + Union, + overload, + TypeVar, + TYPE_CHECKING, +) + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + JustifyMethod, + OverflowMethod, + RenderResult, + RenderableType, + ) + from .text import Text + +from .cells import cell_len +from .measure import Measurement + +T = TypeVar("T") + + +class Renderables: + """A list subclass which renders its contents to the console.""" + + def __init__( + self, renderables: Optional[Iterable["RenderableType"]] = None + ) -> None: + self._renderables: List["RenderableType"] = ( + list(renderables) if renderables is not None else [] + ) + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + """Console render method to insert line-breaks.""" + yield from self._renderables + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + dimensions = [ + Measurement.get(console, options, renderable) + for renderable in self._renderables + ] + if not dimensions: + return Measurement(1, 1) + _min = max(dimension.minimum for dimension in dimensions) + _max = max(dimension.maximum for dimension in dimensions) + return Measurement(_min, _max) + + def append(self, renderable: "RenderableType") -> None: + self._renderables.append(renderable) + + def __iter__(self) -> Iterable["RenderableType"]: + return iter(self._renderables) + + +class Lines: + """A list subclass which can render to the console.""" + + def __init__(self, lines: Iterable["Text"] = ()) -> None: + self._lines: List["Text"] = list(lines) + + def __repr__(self) -> str: + return f"Lines({self._lines!r})" + + def __iter__(self) -> Iterator["Text"]: + return iter(self._lines) + + @overload + def __getitem__(self, index: int) -> "Text": + ... + + @overload + def __getitem__(self, index: slice) -> List["Text"]: + ... + + def __getitem__(self, index: Union[slice, int]) -> Union["Text", List["Text"]]: + return self._lines[index] + + def __setitem__(self, index: int, value: "Text") -> "Lines": + self._lines[index] = value + return self + + def __len__(self) -> int: + return self._lines.__len__() + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + """Console render method to insert line-breaks.""" + yield from self._lines + + def append(self, line: "Text") -> None: + self._lines.append(line) + + def extend(self, lines: Iterable["Text"]) -> None: + self._lines.extend(lines) + + def pop(self, index: int = -1) -> "Text": + return self._lines.pop(index) + + def justify( + self, + console: "Console", + width: int, + justify: "JustifyMethod" = "left", + overflow: "OverflowMethod" = "fold", + ) -> None: + """Justify and overflow text to a given width. + + Args: + console (Console): Console instance. + width (int): Number of characters per line. + justify (str, optional): Default justify method for text: "left", "center", "full" or "right". Defaults to "left". + overflow (str, optional): Default overflow for text: "crop", "fold", or "ellipsis". Defaults to "fold". + + """ + from .text import Text + + if justify == "left": + for line in self._lines: + line.truncate(width, overflow=overflow, pad=True) + elif justify == "center": + for line in self._lines: + line.rstrip() + line.truncate(width, overflow=overflow) + line.pad_left((width - cell_len(line.plain)) // 2) + line.pad_right(width - cell_len(line.plain)) + elif justify == "right": + for line in self._lines: + line.rstrip() + line.truncate(width, overflow=overflow) + line.pad_left(width - cell_len(line.plain)) + elif justify == "full": + for line_index, line in enumerate(self._lines): + if line_index == len(self._lines) - 1: + break + words = line.split(" ") + words_size = sum(cell_len(word.plain) for word in words) + num_spaces = len(words) - 1 + spaces = [1 for _ in range(num_spaces)] + index = 0 + if spaces: + while words_size + num_spaces < width: + spaces[len(spaces) - index - 1] += 1 + num_spaces += 1 + index = (index + 1) % len(spaces) + tokens: List[Text] = [] + for index, (word, next_word) in enumerate( + zip_longest(words, words[1:]) + ): + tokens.append(word) + if index < len(spaces): + style = word.get_style_at_offset(console, -1) + next_style = next_word.get_style_at_offset(console, 0) + space_style = style if style == next_style else line.style + tokens.append(Text(" " * spaces[index], style=space_style)) + self[line_index] = Text("").join(tokens) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/control.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/control.py new file mode 100644 index 00000000..88fcb929 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/control.py @@ -0,0 +1,225 @@ +import sys +import time +from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Union + +if sys.version_info >= (3, 8): + from typing import Final +else: + from pip._vendor.typing_extensions import Final # pragma: no cover + +from .segment import ControlCode, ControlType, Segment + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult + +STRIP_CONTROL_CODES: Final = [ + 7, # Bell + 8, # Backspace + 11, # Vertical tab + 12, # Form feed + 13, # Carriage return +] +_CONTROL_STRIP_TRANSLATE: Final = { + _codepoint: None for _codepoint in STRIP_CONTROL_CODES +} + +CONTROL_ESCAPE: Final = { + 7: "\\a", + 8: "\\b", + 11: "\\v", + 12: "\\f", + 13: "\\r", +} + +CONTROL_CODES_FORMAT: Dict[int, Callable[..., str]] = { + ControlType.BELL: lambda: "\x07", + ControlType.CARRIAGE_RETURN: lambda: "\r", + ControlType.HOME: lambda: "\x1b[H", + ControlType.CLEAR: lambda: "\x1b[2J", + ControlType.ENABLE_ALT_SCREEN: lambda: "\x1b[?1049h", + ControlType.DISABLE_ALT_SCREEN: lambda: "\x1b[?1049l", + ControlType.SHOW_CURSOR: lambda: "\x1b[?25h", + ControlType.HIDE_CURSOR: lambda: "\x1b[?25l", + ControlType.CURSOR_UP: lambda param: f"\x1b[{param}A", + ControlType.CURSOR_DOWN: lambda param: f"\x1b[{param}B", + ControlType.CURSOR_FORWARD: lambda param: f"\x1b[{param}C", + ControlType.CURSOR_BACKWARD: lambda param: f"\x1b[{param}D", + ControlType.CURSOR_MOVE_TO_COLUMN: lambda param: f"\x1b[{param+1}G", + ControlType.ERASE_IN_LINE: lambda param: f"\x1b[{param}K", + ControlType.CURSOR_MOVE_TO: lambda x, y: f"\x1b[{y+1};{x+1}H", + ControlType.SET_WINDOW_TITLE: lambda title: f"\x1b]0;{title}\x07", +} + + +class Control: + """A renderable that inserts a control code (non printable but may move cursor). + + Args: + *codes (str): Positional arguments are either a :class:`~rich.segment.ControlType` enum or a + tuple of ControlType and an integer parameter + """ + + __slots__ = ["segment"] + + def __init__(self, *codes: Union[ControlType, ControlCode]) -> None: + control_codes: List[ControlCode] = [ + (code,) if isinstance(code, ControlType) else code for code in codes + ] + _format_map = CONTROL_CODES_FORMAT + rendered_codes = "".join( + _format_map[code](*parameters) for code, *parameters in control_codes + ) + self.segment = Segment(rendered_codes, None, control_codes) + + @classmethod + def bell(cls) -> "Control": + """Ring the 'bell'.""" + return cls(ControlType.BELL) + + @classmethod + def home(cls) -> "Control": + """Move cursor to 'home' position.""" + return cls(ControlType.HOME) + + @classmethod + def move(cls, x: int = 0, y: int = 0) -> "Control": + """Move cursor relative to current position. + + Args: + x (int): X offset. + y (int): Y offset. + + Returns: + ~Control: Control object. + + """ + + def get_codes() -> Iterable[ControlCode]: + control = ControlType + if x: + yield ( + control.CURSOR_FORWARD if x > 0 else control.CURSOR_BACKWARD, + abs(x), + ) + if y: + yield ( + control.CURSOR_DOWN if y > 0 else control.CURSOR_UP, + abs(y), + ) + + control = cls(*get_codes()) + return control + + @classmethod + def move_to_column(cls, x: int, y: int = 0) -> "Control": + """Move to the given column, optionally add offset to row. + + Returns: + x (int): absolute x (column) + y (int): optional y offset (row) + + Returns: + ~Control: Control object. + """ + + return ( + cls( + (ControlType.CURSOR_MOVE_TO_COLUMN, x), + ( + ControlType.CURSOR_DOWN if y > 0 else ControlType.CURSOR_UP, + abs(y), + ), + ) + if y + else cls((ControlType.CURSOR_MOVE_TO_COLUMN, x)) + ) + + @classmethod + def move_to(cls, x: int, y: int) -> "Control": + """Move cursor to absolute position. + + Args: + x (int): x offset (column) + y (int): y offset (row) + + Returns: + ~Control: Control object. + """ + return cls((ControlType.CURSOR_MOVE_TO, x, y)) + + @classmethod + def clear(cls) -> "Control": + """Clear the screen.""" + return cls(ControlType.CLEAR) + + @classmethod + def show_cursor(cls, show: bool) -> "Control": + """Show or hide the cursor.""" + return cls(ControlType.SHOW_CURSOR if show else ControlType.HIDE_CURSOR) + + @classmethod + def alt_screen(cls, enable: bool) -> "Control": + """Enable or disable alt screen.""" + if enable: + return cls(ControlType.ENABLE_ALT_SCREEN, ControlType.HOME) + else: + return cls(ControlType.DISABLE_ALT_SCREEN) + + @classmethod + def title(cls, title: str) -> "Control": + """Set the terminal window title + + Args: + title (str): The new terminal window title + """ + return cls((ControlType.SET_WINDOW_TITLE, title)) + + def __str__(self) -> str: + return self.segment.text + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if self.segment.text: + yield self.segment + + +def strip_control_codes( + text: str, _translate_table: Dict[int, None] = _CONTROL_STRIP_TRANSLATE +) -> str: + """Remove control codes from text. + + Args: + text (str): A string possibly contain control codes. + + Returns: + str: String with control codes removed. + """ + return text.translate(_translate_table) + + +def escape_control_codes( + text: str, + _translate_table: Dict[int, str] = CONTROL_ESCAPE, +) -> str: + """Replace control codes with their "escaped" equivalent in the given text. + (e.g. "\b" becomes "\\b") + + Args: + text (str): A string possibly containing control codes. + + Returns: + str: String with control codes replaced with their escaped version. + """ + return text.translate(_translate_table) + + +if __name__ == "__main__": # pragma: no cover + from pip._vendor.rich.console import Console + + console = Console() + console.print("Look at the title of your terminal window ^") + # console.print(Control((ControlType.SET_WINDOW_TITLE, "Hello, world!"))) + for i in range(10): + console.set_window_title("🚀 Loading" + "." * i) + time.sleep(0.5) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/default_styles.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/default_styles.py new file mode 100644 index 00000000..dca37193 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/default_styles.py @@ -0,0 +1,190 @@ +from typing import Dict + +from .style import Style + +DEFAULT_STYLES: Dict[str, Style] = { + "none": Style.null(), + "reset": Style( + color="default", + bgcolor="default", + dim=False, + bold=False, + italic=False, + underline=False, + blink=False, + blink2=False, + reverse=False, + conceal=False, + strike=False, + ), + "dim": Style(dim=True), + "bright": Style(dim=False), + "bold": Style(bold=True), + "strong": Style(bold=True), + "code": Style(reverse=True, bold=True), + "italic": Style(italic=True), + "emphasize": Style(italic=True), + "underline": Style(underline=True), + "blink": Style(blink=True), + "blink2": Style(blink2=True), + "reverse": Style(reverse=True), + "strike": Style(strike=True), + "black": Style(color="black"), + "red": Style(color="red"), + "green": Style(color="green"), + "yellow": Style(color="yellow"), + "magenta": Style(color="magenta"), + "cyan": Style(color="cyan"), + "white": Style(color="white"), + "inspect.attr": Style(color="yellow", italic=True), + "inspect.attr.dunder": Style(color="yellow", italic=True, dim=True), + "inspect.callable": Style(bold=True, color="red"), + "inspect.async_def": Style(italic=True, color="bright_cyan"), + "inspect.def": Style(italic=True, color="bright_cyan"), + "inspect.class": Style(italic=True, color="bright_cyan"), + "inspect.error": Style(bold=True, color="red"), + "inspect.equals": Style(), + "inspect.help": Style(color="cyan"), + "inspect.doc": Style(dim=True), + "inspect.value.border": Style(color="green"), + "live.ellipsis": Style(bold=True, color="red"), + "layout.tree.row": Style(dim=False, color="red"), + "layout.tree.column": Style(dim=False, color="blue"), + "logging.keyword": Style(bold=True, color="yellow"), + "logging.level.notset": Style(dim=True), + "logging.level.debug": Style(color="green"), + "logging.level.info": Style(color="blue"), + "logging.level.warning": Style(color="red"), + "logging.level.error": Style(color="red", bold=True), + "logging.level.critical": Style(color="red", bold=True, reverse=True), + "log.level": Style.null(), + "log.time": Style(color="cyan", dim=True), + "log.message": Style.null(), + "log.path": Style(dim=True), + "repr.ellipsis": Style(color="yellow"), + "repr.indent": Style(color="green", dim=True), + "repr.error": Style(color="red", bold=True), + "repr.str": Style(color="green", italic=False, bold=False), + "repr.brace": Style(bold=True), + "repr.comma": Style(bold=True), + "repr.ipv4": Style(bold=True, color="bright_green"), + "repr.ipv6": Style(bold=True, color="bright_green"), + "repr.eui48": Style(bold=True, color="bright_green"), + "repr.eui64": Style(bold=True, color="bright_green"), + "repr.tag_start": Style(bold=True), + "repr.tag_name": Style(color="bright_magenta", bold=True), + "repr.tag_contents": Style(color="default"), + "repr.tag_end": Style(bold=True), + "repr.attrib_name": Style(color="yellow", italic=False), + "repr.attrib_equal": Style(bold=True), + "repr.attrib_value": Style(color="magenta", italic=False), + "repr.number": Style(color="cyan", bold=True, italic=False), + "repr.number_complex": Style(color="cyan", bold=True, italic=False), # same + "repr.bool_true": Style(color="bright_green", italic=True), + "repr.bool_false": Style(color="bright_red", italic=True), + "repr.none": Style(color="magenta", italic=True), + "repr.url": Style(underline=True, color="bright_blue", italic=False, bold=False), + "repr.uuid": Style(color="bright_yellow", bold=False), + "repr.call": Style(color="magenta", bold=True), + "repr.path": Style(color="magenta"), + "repr.filename": Style(color="bright_magenta"), + "rule.line": Style(color="bright_green"), + "rule.text": Style.null(), + "json.brace": Style(bold=True), + "json.bool_true": Style(color="bright_green", italic=True), + "json.bool_false": Style(color="bright_red", italic=True), + "json.null": Style(color="magenta", italic=True), + "json.number": Style(color="cyan", bold=True, italic=False), + "json.str": Style(color="green", italic=False, bold=False), + "json.key": Style(color="blue", bold=True), + "prompt": Style.null(), + "prompt.choices": Style(color="magenta", bold=True), + "prompt.default": Style(color="cyan", bold=True), + "prompt.invalid": Style(color="red"), + "prompt.invalid.choice": Style(color="red"), + "pretty": Style.null(), + "scope.border": Style(color="blue"), + "scope.key": Style(color="yellow", italic=True), + "scope.key.special": Style(color="yellow", italic=True, dim=True), + "scope.equals": Style(color="red"), + "table.header": Style(bold=True), + "table.footer": Style(bold=True), + "table.cell": Style.null(), + "table.title": Style(italic=True), + "table.caption": Style(italic=True, dim=True), + "traceback.error": Style(color="red", italic=True), + "traceback.border.syntax_error": Style(color="bright_red"), + "traceback.border": Style(color="red"), + "traceback.text": Style.null(), + "traceback.title": Style(color="red", bold=True), + "traceback.exc_type": Style(color="bright_red", bold=True), + "traceback.exc_value": Style.null(), + "traceback.offset": Style(color="bright_red", bold=True), + "bar.back": Style(color="grey23"), + "bar.complete": Style(color="rgb(249,38,114)"), + "bar.finished": Style(color="rgb(114,156,31)"), + "bar.pulse": Style(color="rgb(249,38,114)"), + "progress.description": Style.null(), + "progress.filesize": Style(color="green"), + "progress.filesize.total": Style(color="green"), + "progress.download": Style(color="green"), + "progress.elapsed": Style(color="yellow"), + "progress.percentage": Style(color="magenta"), + "progress.remaining": Style(color="cyan"), + "progress.data.speed": Style(color="red"), + "progress.spinner": Style(color="green"), + "status.spinner": Style(color="green"), + "tree": Style(), + "tree.line": Style(), + "markdown.paragraph": Style(), + "markdown.text": Style(), + "markdown.em": Style(italic=True), + "markdown.emph": Style(italic=True), # For commonmark backwards compatibility + "markdown.strong": Style(bold=True), + "markdown.code": Style(bold=True, color="cyan", bgcolor="black"), + "markdown.code_block": Style(color="cyan", bgcolor="black"), + "markdown.block_quote": Style(color="magenta"), + "markdown.list": Style(color="cyan"), + "markdown.item": Style(), + "markdown.item.bullet": Style(color="yellow", bold=True), + "markdown.item.number": Style(color="yellow", bold=True), + "markdown.hr": Style(color="yellow"), + "markdown.h1.border": Style(), + "markdown.h1": Style(bold=True), + "markdown.h2": Style(bold=True, underline=True), + "markdown.h3": Style(bold=True), + "markdown.h4": Style(bold=True, dim=True), + "markdown.h5": Style(underline=True), + "markdown.h6": Style(italic=True), + "markdown.h7": Style(italic=True, dim=True), + "markdown.link": Style(color="bright_blue"), + "markdown.link_url": Style(color="blue", underline=True), + "markdown.s": Style(strike=True), + "iso8601.date": Style(color="blue"), + "iso8601.time": Style(color="magenta"), + "iso8601.timezone": Style(color="yellow"), +} + + +if __name__ == "__main__": # pragma: no cover + import argparse + import io + + from pip._vendor.rich.console import Console + from pip._vendor.rich.table import Table + from pip._vendor.rich.text import Text + + parser = argparse.ArgumentParser() + parser.add_argument("--html", action="store_true", help="Export as HTML table") + args = parser.parse_args() + html: bool = args.html + console = Console(record=True, width=70, file=io.StringIO()) if html else Console() + + table = Table("Name", "Styling") + + for style_name, style in DEFAULT_STYLES.items(): + table.add_row(Text(style_name, style=style), str(style)) + + console.print(table) + if html: + print(console.export_html(inline_styles=True)) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/diagnose.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/diagnose.py new file mode 100644 index 00000000..ad361838 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/diagnose.py @@ -0,0 +1,37 @@ +import os +import platform + +from pip._vendor.rich import inspect +from pip._vendor.rich.console import Console, get_windows_console_features +from pip._vendor.rich.panel import Panel +from pip._vendor.rich.pretty import Pretty + + +def report() -> None: # pragma: no cover + """Print a report to the terminal with debugging information""" + console = Console() + inspect(console) + features = get_windows_console_features() + inspect(features) + + env_names = ( + "TERM", + "COLORTERM", + "CLICOLOR", + "NO_COLOR", + "TERM_PROGRAM", + "COLUMNS", + "LINES", + "JUPYTER_COLUMNS", + "JUPYTER_LINES", + "JPY_PARENT_PID", + "VSCODE_VERBOSE_LOGGING", + ) + env = {name: os.getenv(name) for name in env_names} + console.print(Panel.fit((Pretty(env)), title="[b]Environment Variables")) + + console.print(f'platform="{platform.system()}"') + + +if __name__ == "__main__": # pragma: no cover + report() diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/emoji.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/emoji.py new file mode 100644 index 00000000..791f0465 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/emoji.py @@ -0,0 +1,96 @@ +import sys +from typing import TYPE_CHECKING, Optional, Union + +from .jupyter import JupyterMixin +from .segment import Segment +from .style import Style +from ._emoji_codes import EMOJI +from ._emoji_replace import _emoji_replace + +if sys.version_info >= (3, 8): + from typing import Literal +else: + from pip._vendor.typing_extensions import Literal # pragma: no cover + + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult + + +EmojiVariant = Literal["emoji", "text"] + + +class NoEmoji(Exception): + """No emoji by that name.""" + + +class Emoji(JupyterMixin): + __slots__ = ["name", "style", "_char", "variant"] + + VARIANTS = {"text": "\uFE0E", "emoji": "\uFE0F"} + + def __init__( + self, + name: str, + style: Union[str, Style] = "none", + variant: Optional[EmojiVariant] = None, + ) -> None: + """A single emoji character. + + Args: + name (str): Name of emoji. + style (Union[str, Style], optional): Optional style. Defaults to None. + + Raises: + NoEmoji: If the emoji doesn't exist. + """ + self.name = name + self.style = style + self.variant = variant + try: + self._char = EMOJI[name] + except KeyError: + raise NoEmoji(f"No emoji called {name!r}") + if variant is not None: + self._char += self.VARIANTS.get(variant, "") + + @classmethod + def replace(cls, text: str) -> str: + """Replace emoji markup with corresponding unicode characters. + + Args: + text (str): A string with emojis codes, e.g. "Hello :smiley:!" + + Returns: + str: A string with emoji codes replaces with actual emoji. + """ + return _emoji_replace(text) + + def __repr__(self) -> str: + return f"" + + def __str__(self) -> str: + return self._char + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + yield Segment(self._char, console.get_style(self.style)) + + +if __name__ == "__main__": # pragma: no cover + import sys + + from pip._vendor.rich.columns import Columns + from pip._vendor.rich.console import Console + + console = Console(record=True) + + columns = Columns( + (f":{name}: {name}" for name in sorted(EMOJI.keys()) if "\u200D" not in name), + column_first=True, + ) + + console.print(columns) + if len(sys.argv) > 1: + console.save_html(sys.argv[1]) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/errors.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/errors.py new file mode 100644 index 00000000..0bcbe53e --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/errors.py @@ -0,0 +1,34 @@ +class ConsoleError(Exception): + """An error in console operation.""" + + +class StyleError(Exception): + """An error in styles.""" + + +class StyleSyntaxError(ConsoleError): + """Style was badly formatted.""" + + +class MissingStyle(StyleError): + """No such style.""" + + +class StyleStackError(ConsoleError): + """Style stack is invalid.""" + + +class NotRenderableError(ConsoleError): + """Object is not renderable.""" + + +class MarkupError(ConsoleError): + """Markup was badly formatted.""" + + +class LiveError(ConsoleError): + """Error related to Live display.""" + + +class NoAltScreen(ConsoleError): + """Alt screen mode was required.""" diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/file_proxy.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/file_proxy.py new file mode 100644 index 00000000..4b0b0da6 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/file_proxy.py @@ -0,0 +1,57 @@ +import io +from typing import IO, TYPE_CHECKING, Any, List + +from .ansi import AnsiDecoder +from .text import Text + +if TYPE_CHECKING: + from .console import Console + + +class FileProxy(io.TextIOBase): + """Wraps a file (e.g. sys.stdout) and redirects writes to a console.""" + + def __init__(self, console: "Console", file: IO[str]) -> None: + self.__console = console + self.__file = file + self.__buffer: List[str] = [] + self.__ansi_decoder = AnsiDecoder() + + @property + def rich_proxied_file(self) -> IO[str]: + """Get proxied file.""" + return self.__file + + def __getattr__(self, name: str) -> Any: + return getattr(self.__file, name) + + def write(self, text: str) -> int: + if not isinstance(text, str): + raise TypeError(f"write() argument must be str, not {type(text).__name__}") + buffer = self.__buffer + lines: List[str] = [] + while text: + line, new_line, text = text.partition("\n") + if new_line: + lines.append("".join(buffer) + line) + buffer.clear() + else: + buffer.append(line) + break + if lines: + console = self.__console + with console: + output = Text("\n").join( + self.__ansi_decoder.decode_line(line) for line in lines + ) + console.print(output) + return len(text) + + def flush(self) -> None: + output = "".join(self.__buffer) + if output: + self.__console.print(output) + del self.__buffer[:] + + def fileno(self) -> int: + return self.__file.fileno() diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/filesize.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/filesize.py new file mode 100644 index 00000000..99f118e2 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/filesize.py @@ -0,0 +1,89 @@ +# coding: utf-8 +"""Functions for reporting filesizes. Borrowed from https://github.com/PyFilesystem/pyfilesystem2 + +The functions declared in this module should cover the different +use cases needed to generate a string representation of a file size +using several different units. Since there are many standards regarding +file size units, three different functions have been implemented. + +See Also: + * `Wikipedia: Binary prefix `_ + +""" + +__all__ = ["decimal"] + +from typing import Iterable, List, Optional, Tuple + + +def _to_str( + size: int, + suffixes: Iterable[str], + base: int, + *, + precision: Optional[int] = 1, + separator: Optional[str] = " ", +) -> str: + if size == 1: + return "1 byte" + elif size < base: + return "{:,} bytes".format(size) + + for i, suffix in enumerate(suffixes, 2): # noqa: B007 + unit = base**i + if size < unit: + break + return "{:,.{precision}f}{separator}{}".format( + (base * size / unit), + suffix, + precision=precision, + separator=separator, + ) + + +def pick_unit_and_suffix(size: int, suffixes: List[str], base: int) -> Tuple[int, str]: + """Pick a suffix and base for the given size.""" + for i, suffix in enumerate(suffixes): + unit = base**i + if size < unit * base: + break + return unit, suffix + + +def decimal( + size: int, + *, + precision: Optional[int] = 1, + separator: Optional[str] = " ", +) -> str: + """Convert a filesize in to a string (powers of 1000, SI prefixes). + + In this convention, ``1000 B = 1 kB``. + + This is typically the format used to advertise the storage + capacity of USB flash drives and the like (*256 MB* meaning + actually a storage capacity of more than *256 000 000 B*), + or used by **Mac OS X** since v10.6 to report file sizes. + + Arguments: + int (size): A file size. + int (precision): The number of decimal places to include (default = 1). + str (separator): The string to separate the value from the units (default = " "). + + Returns: + `str`: A string containing a abbreviated file size and units. + + Example: + >>> filesize.decimal(30000) + '30.0 kB' + >>> filesize.decimal(30000, precision=2, separator="") + '30.00kB' + + """ + return _to_str( + size, + ("kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"), + 1000, + precision=precision, + separator=separator, + ) diff --git a/myenv/lib/python3.12/site-packages/pip/_vendor/rich/highlighter.py b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/highlighter.py new file mode 100644 index 00000000..c2646794 --- /dev/null +++ b/myenv/lib/python3.12/site-packages/pip/_vendor/rich/highlighter.py @@ -0,0 +1,232 @@ +import re +from abc import ABC, abstractmethod +from typing import List, Union + +from .text import Span, Text + + +def _combine_regex(*regexes: str) -> str: + """Combine a number of regexes in to a single regex. + + Returns: + str: New regex with all regexes ORed together. + """ + return "|".join(regexes) + + +class Highlighter(ABC): + """Abstract base class for highlighters.""" + + def __call__(self, text: Union[str, Text]) -> Text: + """Highlight a str or Text instance. + + Args: + text (Union[str, ~Text]): Text to highlight. + + Raises: + TypeError: If not called with text or str. + + Returns: + Text: A test instance with highlighting applied. + """ + if isinstance(text, str): + highlight_text = Text(text) + elif isinstance(text, Text): + highlight_text = text.copy() + else: + raise TypeError(f"str or Text instance required, not {text!r}") + self.highlight(highlight_text) + return highlight_text + + @abstractmethod + def highlight(self, text: Text) -> None: + """Apply highlighting in place to text. + + Args: + text (~Text): A text object highlight. + """ + + +class NullHighlighter(Highlighter): + """A highlighter object that doesn't highlight. + + May be used to disable highlighting entirely. + + """ + + def highlight(self, text: Text) -> None: + """Nothing to do""" + + +class RegexHighlighter(Highlighter): + """Applies highlighting from a list of regular expressions.""" + + highlights: List[str] = [] + base_style: str = "" + + def highlight(self, text: Text) -> None: + """Highlight :class:`rich.text.Text` using regular expressions. + + Args: + text (~Text): Text to highlighted. + + """ + + highlight_regex = text.highlight_regex + for re_highlight in self.highlights: + highlight_regex(re_highlight, style_prefix=self.base_style) + + +class ReprHighlighter(RegexHighlighter): + """Highlights the text typically produced from ``__repr__`` methods.""" + + base_style = "repr." + highlights = [ + r"(?P<)(?P[-\w.:|]*)(?P[\w\W]*)(?P>)", + r'(?P[\w_]{1,50})=(?P"?[\w_]+"?)?', + r"(?P[][{}()])", + _combine_regex( + r"(?P[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})", + r"(?P([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4})", + r"(?P(?:[0-9A-Fa-f]{1,2}-){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){7}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){3}[0-9A-Fa-f]{4})", + r"(?P(?:[0-9A-Fa-f]{1,2}-){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{1,2}:){5}[0-9A-Fa-f]{1,2}|(?:[0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4})", + r"(?P[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})", + r"(?P[\w.]*?)\(", + r"\b(?PTrue)\b|\b(?PFalse)\b|\b(?PNone)\b", + r"(?P\.\.\.)", + r"(?P(?(?\B(/[-\w._+]+)*\/)(?P[-\w._+]*)?", + r"(?b?'''.*?(?(file|https|http|ws|wss)://[-0-9a-zA-Z$_+!`(),.?/;:&=%#]*)", + ), + ] + + +class JSONHighlighter(RegexHighlighter): + """Highlights JSON""" + + # Captures the start and end of JSON strings, handling escaped quotes + JSON_STR = r"(?b?\".*?(?[\{\[\(\)\]\}])", + r"\b(?Ptrue)\b|\b(?Pfalse)\b|\b(?Pnull)\b", + r"(?P(? None: + super().highlight(text) + + # Additional work to handle highlighting JSON keys + plain = text.plain + append = text.spans.append + whitespace = self.JSON_WHITESPACE + for match in re.finditer(self.JSON_STR, plain): + start, end = match.span() + cursor = end + while cursor < len(plain): + char = plain[cursor] + cursor += 1 + if char == ":": + append(Span(start, end, "json.key")) + elif char in whitespace: + continue + break + + +class ISO8601Highlighter(RegexHighlighter): + """Highlights the ISO8601 date time strings. + Regex reference: https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html + """ + + base_style = "iso8601." + highlights = [ + # + # Dates + # + # Calendar month (e.g. 2008-08). The hyphen is required + r"^(?P[0-9]{4})-(?P1[0-2]|0[1-9])$", + # Calendar date w/o hyphens (e.g. 20080830) + r"^(?P(?P[0-9]{4})(?P1[0-2]|0[1-9])(?P3[01]|0[1-9]|[12][0-9]))$", + # Ordinal date (e.g. 2008-243). The hyphen is optional + r"^(?P(?P[0-9]{4})-?(?P36[0-6]|3[0-5][0-9]|[12][0-9]{2}|0[1-9][0-9]|00[1-9]))$", + # + # Weeks + # + # Week of the year (e.g., 2008-W35). The hyphen is optional + r"^(?P(?P[0-9]{4})-?W(?P5[0-3]|[1-4][0-9]|0[1-9]))$", + # Week date (e.g., 2008-W35-6). The hyphens are optional + r"^(?P(?P[0-9]{4})-?W(?P5[0-3]|[1-4][0-9]|0[1-9])-?(?P[1-7]))$", + # + # Times + # + # Hours and minutes (e.g., 17:21). The colon is optional + r"^(?P