diff --git a/temp_venv/lib/python3.13/site-packages/IPython/__pycache__/__init__.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a3f2696c5eb0c4a539f5d69417b6f83373899e8 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/__pycache__/__init__.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/__pycache__/display.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/__pycache__/display.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d77ed9cb3243a3f639b1db8c11ad302962d239a6 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/__pycache__/display.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/__pycache__/paths.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/__pycache__/paths.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e186f0b624059850a8efdcff5579e286fed0a5f Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/__pycache__/paths.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/extensions/__init__.py b/temp_venv/lib/python3.13/site-packages/IPython/extensions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..db7f79fca6aba2547d08ed15bc4a67f9fbcaec01 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/extensions/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +"""This directory is meant for IPython extensions.""" diff --git a/temp_venv/lib/python3.13/site-packages/IPython/extensions/__pycache__/__init__.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/extensions/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62b26761ed1db351e3b7507a5c735c5296322e08 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/extensions/__pycache__/__init__.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/extensions/__pycache__/storemagic.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/extensions/__pycache__/storemagic.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..809466d4649bb1978ab8a85d63721caabed9176f Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/extensions/__pycache__/storemagic.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/extensions/autoreload.py b/temp_venv/lib/python3.13/site-packages/IPython/extensions/autoreload.py new file mode 100644 index 0000000000000000000000000000000000000000..39ac6d52d49b78dff5102b0a183bc145efa45f36 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/extensions/autoreload.py @@ -0,0 +1,752 @@ +"""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 +from IPython.extensions.deduperreload.deduperreload import DeduperReloader + +__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. + + # Deduper reloader + self.deduper_reloader = DeduperReloader() + + # 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) + # if not using autoload, check if deduperreload is viable for this module + elif self.deduper_reloader.maybe_reload_module(m): + pass + 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 + self.deduper_reloader.update_sources() + + +# ------------------------------------------------------------------------------ +# 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 adds any new + objects in the module. + + By default, a newer autoreload algorithm that diffs the module's source code + with the previous version and only reloads changed parts is applied for modes + 2 and below. To use the original algorithm, add the `-` suffix to the mode, + e.g. '%autoreload 2-', or pass in --full. + """, + ) + @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", + ) + @magic_arguments.argument( + "--full", + action="store_true", + default=False, + help="Don't ever use new diffing algorithm", + ) + 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() + + enable_deduperreload = not args.full + if mode.endswith("-"): + enable_deduperreload = False + mode = mode[:-1] + self._reloader.deduper_reloader.enabled = enable_deduperreload + + 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/temp_venv/lib/python3.13/site-packages/IPython/extensions/deduperreload/__init__.py b/temp_venv/lib/python3.13/site-packages/IPython/extensions/deduperreload/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/temp_venv/lib/python3.13/site-packages/IPython/extensions/deduperreload/deduperreload.py b/temp_venv/lib/python3.13/site-packages/IPython/extensions/deduperreload/deduperreload.py new file mode 100644 index 0000000000000000000000000000000000000000..670f7ae6ccff33b05f87d1a3a8624833c2db3b89 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/extensions/deduperreload/deduperreload.py @@ -0,0 +1,608 @@ +from __future__ import annotations +import ast +import builtins +import contextlib +import itertools +import os +import platform +import sys +import textwrap +from types import ModuleType +from typing import TYPE_CHECKING, Any, Generator, Iterable, NamedTuple, cast + +from IPython.extensions.deduperreload.deduperreload_patching import ( + DeduperReloaderPatchingMixin, +) + +if TYPE_CHECKING: + TDefinitionAst = ( + ast.FunctionDef + | ast.AsyncFunctionDef + | ast.Import + | ast.ImportFrom + | ast.Assign + | ast.AnnAssign + ) + + +def get_module_file_name(module: ModuleType | str) -> str: + """Returns the module's file path, or the empty string if it's inaccessible""" + if (mod := sys.modules.get(module) if isinstance(module, str) else module) is None: + return "" + return getattr(mod, "__file__", "") or "" + + +def compare_ast(node1: ast.AST | list[ast.AST], node2: ast.AST | list[ast.AST]) -> bool: + """Checks if node1 and node2 have identical AST structure/values, apart from some attributes""" + if type(node1) is not type(node2): + return False + + if isinstance(node1, ast.AST): + for k, v in node1.__dict__.items(): + if k in ( + "lineno", + "end_lineno", + "col_offset", + "end_col_offset", + "ctx", + "parent", + ): + continue + if not hasattr(node2, k) or not compare_ast(v, getattr(node2, k)): + return False + return True + + elif isinstance(node1, list) and isinstance( # type:ignore [redundant-expr] + node2, list + ): + return len(node1) == len(node2) and all( + compare_ast(n1, n2) for n1, n2 in zip(node1, node2) + ) + else: + return node1 == node2 + + +class DependencyNode(NamedTuple): + """ + Each node represents a function. + qualified_name: string which represents the namespace/name of the function + abstract_syntax_tree: subtree of the overall module which corresponds to this function + + qualified_name is of the structure: (namespace1, namespace2, ..., name) + + For example, foo() in the following would be represented as (A, B, foo): + + class A: + class B: + def foo(): + pass + """ + + qualified_name: tuple[str, ...] + abstract_syntax_tree: ast.AST + + +class GatherResult(NamedTuple): + import_defs: list[tuple[tuple[str, ...], ast.Import | ast.ImportFrom]] = [] + assign_defs: list[tuple[tuple[str, ...], ast.Assign | ast.AnnAssign]] = [] + function_defs: list[ + tuple[tuple[str, ...], ast.FunctionDef | ast.AsyncFunctionDef] + ] = [] + classes: dict[str, ast.ClassDef] = {} + unfixable: list[ast.AST] = [] + + @classmethod + def create(cls) -> GatherResult: + return cls([], [], [], {}, []) + + def all_defs(self) -> Iterable[tuple[tuple[str, ...], TDefinitionAst]]: + return itertools.chain(self.import_defs, self.assign_defs, self.function_defs) + + def inplace_merge(self, other: GatherResult) -> None: + self.import_defs.extend(other.import_defs) + self.assign_defs.extend(other.assign_defs) + self.function_defs.extend(other.function_defs) + self.classes.update(other.classes) + self.unfixable.extend(other.unfixable) + + +class ConstexprDetector(ast.NodeVisitor): + def __init__(self) -> None: + self.is_constexpr = True + self._allow_builtins_exceptions = True + + @contextlib.contextmanager + def disallow_builtins_exceptions(self) -> Generator[None, None, None]: + prev_allow = self._allow_builtins_exceptions + self._allow_builtins_exceptions = False + try: + yield + finally: + self._allow_builtins_exceptions = prev_allow + + def visit_Attribute(self, node: ast.Attribute) -> None: + with self.disallow_builtins_exceptions(): + self.visit(node.value) + + def visit_Name(self, node: ast.Name) -> None: + if self._allow_builtins_exceptions and hasattr(builtins, node.id): + return + self.is_constexpr = False + + def visit(self, node: ast.AST) -> None: + if not self.is_constexpr: + # can short-circuit if we've already detected that it's not a constexpr + return + super().visit(node) + + def __call__(self, node: ast.AST) -> bool: + self.is_constexpr = True + self.visit(node) + return self.is_constexpr + + +class AutoreloadTree: + """ + Recursive data structure to keep track of reloadable functions/methods. Each object corresponds to a specific scope level. + children: classes inside given scope, maps class name to autoreload tree for that class's scope + funcs_to_autoreload: list of function names that can be autoreloaded in given scope. + new_nested_classes: Classes getting added in new autoreload cycle + """ + + def __init__(self) -> None: + self.children: dict[str, AutoreloadTree] = {} + self.defs_to_reload: list[tuple[tuple[str, ...], ast.AST]] = [] + self.defs_to_delete: set[str] = set() + self.new_nested_classes: dict[str, ast.AST] = {} + + def traverse_prefixes(self, prefixes: list[str]) -> AutoreloadTree: + """ + Return ref to the AutoreloadTree at the namespace specified by prefixes + """ + cur = self + for prefix in prefixes: + if prefix not in cur.children: + cur.children[prefix] = AutoreloadTree() + cur = cur.children[prefix] + return cur + + +class DeduperReloader(DeduperReloaderPatchingMixin): + """ + This version of autoreload detects when we can leverage targeted recompilation of a subset of a module and patching + existing function/method objects to reflect these changes. + + Detects what functions/methods can be reloaded by recursively comparing the old/new AST of module-level classes, + module-level classes' methods, recursing through nested classes' methods. If other changes are made, original + autoreload algorithm is called directly. + """ + + def __init__(self) -> None: + self._to_autoreload: AutoreloadTree = AutoreloadTree() + self.source_by_modname: dict[str, str] = {} + self.dependency_graph: dict[tuple[str, ...], list[DependencyNode]] = {} + self._enabled = True + + @property + def enabled(self) -> bool: + return self._enabled and platform.python_implementation() == "CPython" + + @enabled.setter + def enabled(self, value: bool) -> None: + self._enabled = value + + def update_sources(self) -> None: + """ + Update dictionary source_by_modname with current modules' source codes. + """ + if not self.enabled: + return + for new_modname in sys.modules.keys() - self.source_by_modname.keys(): + new_module = sys.modules[new_modname] + if ( + (fname := get_module_file_name(new_module)) + is None # type:ignore [redundant-expr] + or "site-packages" in fname + or "dist-packages" in fname + or not os.access(fname, os.R_OK) + ): + self.source_by_modname[new_modname] = "" + continue + with open(fname, "r") as f: + try: + self.source_by_modname[new_modname] = f.read() + except Exception: + self.source_by_modname[new_modname] = "" + + constexpr_detector = ConstexprDetector() + + @staticmethod + def is_enum_subclass(node: ast.Module | ast.ClassDef) -> bool: + if isinstance(node, ast.Module): + return False + for base in node.bases: + if isinstance(base, ast.Name) and base.id == "Enum": + return True + elif ( + isinstance(base, ast.Attribute) + and base.attr == "Enum" + and isinstance(base.value, ast.Name) + and base.value.id == "enum" + ): + return True + return False + + @classmethod + def is_constexpr_assign( + cls, node: ast.AST, parent_node: ast.Module | ast.ClassDef + ) -> bool: + if not isinstance(node, (ast.Assign, ast.AnnAssign)) or node.value is None: + return False + if cls.is_enum_subclass(parent_node): + return False + for target in node.targets if isinstance(node, ast.Assign) else [node.target]: + if not isinstance(target, ast.Name): + return False + return cls.constexpr_detector(node.value) + + @classmethod + def _gather_children( + cls, body: list[ast.stmt], parent_node: ast.Module | ast.ClassDef + ) -> GatherResult: + """ + Given list of ast elements, return: + 1. dict mapping function names to their ASTs. + 2. dict mapping class names to their ASTs. + 3. list of any other ASTs. + """ + result = GatherResult.create() + for ast_node in body: + ast_elt: ast.expr | ast.stmt = ast_node + while isinstance(ast_elt, ast.Expr): + ast_elt = ast_elt.value + if isinstance(ast_elt, (ast.FunctionDef, ast.AsyncFunctionDef)): + result.function_defs.append(((ast_elt.name,), ast_elt)) + elif isinstance(ast_elt, (ast.Import, ast.ImportFrom)): + result.import_defs.append( + (tuple(name.asname or name.name for name in ast_elt.names), ast_elt) + ) + elif isinstance(ast_elt, ast.ClassDef): + result.classes[ast_elt.name] = ast_elt + elif isinstance(ast_elt, ast.If): + result.unfixable.append(ast_elt.test) + result.inplace_merge(cls._gather_children(ast_elt.body, parent_node)) + result.inplace_merge(cls._gather_children(ast_elt.orelse, parent_node)) + elif isinstance(ast_elt, (ast.AsyncWith, ast.With)): + result.unfixable.extend(ast_elt.items) + result.inplace_merge(cls._gather_children(ast_elt.body, parent_node)) + elif isinstance(ast_elt, ast.Try): + result.inplace_merge(cls._gather_children(ast_elt.body, parent_node)) + result.inplace_merge(cls._gather_children(ast_elt.orelse, parent_node)) + result.inplace_merge( + cls._gather_children(ast_elt.finalbody, parent_node) + ) + for handler in ast_elt.handlers: + if handler.type is not None: + result.unfixable.append(handler.type) + result.inplace_merge( + cls._gather_children(handler.body, parent_node) + ) + elif not isinstance(ast_elt, (ast.Ellipsis, ast.Pass)): + if cls.is_constexpr_assign(ast_elt, parent_node): + assert isinstance(ast_elt, (ast.Assign, ast.AnnAssign)) + targets = ( + ast_elt.targets + if isinstance(ast_elt, ast.Assign) + else [ast_elt.target] + ) + result.assign_defs.append( + ( + tuple(cast(ast.Name, target).id for target in targets), + ast_elt, + ) + ) + else: + result.unfixable.append(ast_elt) + return result + + def detect_autoreload( + self, + old_node: ast.Module | ast.ClassDef, + new_node: ast.Module | ast.ClassDef, + prefixes: list[str] | None = None, + ) -> bool: + """ + Returns + ------- + `True` if we can run our targeted autoreload algorithm safely. + `False` if we should instead use IPython's original autoreload implementation. + """ + if not self.enabled: + return False + prefixes = prefixes or [] + + old_result = self._gather_children(old_node.body, old_node) + new_result = self._gather_children(new_node.body, new_node) + old_defs_by_name: dict[str, ast.AST] = { + name: ast_def for names, ast_def in old_result.all_defs() for name in names + } + new_defs_by_name: dict[str, ast.AST] = { + name: ast_def for names, ast_def in new_result.all_defs() for name in names + } + + if not compare_ast(old_result.unfixable, new_result.unfixable): + return False + + cur = self._to_autoreload.traverse_prefixes(prefixes) + for names, new_ast_def in new_result.all_defs(): + names_to_reload = [] + for name in names: + if new_defs_by_name[name] is not new_ast_def: + continue + if name not in old_defs_by_name or not compare_ast( + new_ast_def, old_defs_by_name[name] + ): + names_to_reload.append(name) + if names_to_reload: + cur.defs_to_reload.append((tuple(names), new_ast_def)) + cur.defs_to_delete |= set(old_defs_by_name.keys()) - set( + new_defs_by_name.keys() + ) + for name, new_ast_def_class in new_result.classes.items(): + if name not in old_result.classes: + cur.new_nested_classes[name] = new_ast_def_class + elif not compare_ast( + new_ast_def_class, old_result.classes[name] + ) and not self.detect_autoreload( + old_result.classes[name], new_ast_def_class, prefixes + [name] + ): + return False + return True + + def _check_dependents(self) -> bool: + """ + If a decorator function is modified, we should similarly reload the functions which are decorated by this + decorator. Iterate through the Dependency Graph to find such cases in the given AutoreloadTree. + """ + for node in self._check_dependents_inner(): + self._add_node_to_autoreload_tree(node) + return True + + def _add_node_to_autoreload_tree(self, node: DependencyNode) -> None: + """ + Given a node of the dependency graph, add decorator dependencies to the autoreload tree. + """ + if len(node.qualified_name) == 0: + return + cur = self._to_autoreload.traverse_prefixes(list(node.qualified_name[:-1])) + if node.abstract_syntax_tree is not None: + cur.defs_to_reload.append( + ((node.qualified_name[-1],), node.abstract_syntax_tree) + ) + + def _check_dependents_inner( + self, prefixes: list[str] | None = None + ) -> list[DependencyNode]: + prefixes = prefixes or [] + cur = self._to_autoreload.traverse_prefixes(prefixes) + ans = [] + for (func_name, *_), _ in cur.defs_to_reload: + node = tuple(prefixes + [func_name]) + ans.extend(self._gen_dependents(node)) + for class_name in cur.new_nested_classes: + ans.extend(self._check_dependents_inner(prefixes + [class_name])) + return ans + + def _gen_dependents(self, qualname: tuple[str, ...]) -> list[DependencyNode]: + ans = [] + if qualname not in self.dependency_graph: + return [] + for elt in self.dependency_graph[qualname]: + ans.extend(self._gen_dependents(elt.qualified_name)) + ans.append(elt) + return ans + + def _patch_namespace_inner( + self, ns: ModuleType | type, prefixes: list[str] | None = None + ) -> bool: + """ + This function patches module functions and methods. Specifically, only objects with their name in + self.to_autoreload will be considered for patching. If an object has been marked to be autoreloaded, + new_source_code gets executed in the old version's global environment. Then, replace the old function's + attributes with the new function's attributes. + """ + prefixes = prefixes or [] + cur = self._to_autoreload.traverse_prefixes(prefixes) + namespace_to_check = ns + for prefix in prefixes: + namespace_to_check = namespace_to_check.__dict__[prefix] + for names, new_ast_def in cur.defs_to_reload: + local_env: dict[str, Any] = {} + if ( + isinstance(new_ast_def, (ast.FunctionDef, ast.AsyncFunctionDef)) + and (name := names[0]) in namespace_to_check.__dict__ + ): + assert len(names) == 1 + to_patch_to = namespace_to_check.__dict__[name] + if isinstance(to_patch_to, (staticmethod, classmethod)): + to_patch_to = to_patch_to.__func__ + # exec new source code using old function's (obj) globals environment. + func_code = textwrap.dedent(ast.unparse(new_ast_def)) + if is_method := (len(prefixes) > 0): + func_code = "class __autoreload_class__:\n" + textwrap.indent( + func_code, " " + ) + global_env = namespace_to_check.__dict__ + if hasattr(to_patch_to, "__globals__"): + global_env = to_patch_to.__globals__ + elif isinstance(to_patch_to, property): + if to_patch_to.fget is not None: + global_env = to_patch_to.fget.__globals__ + elif to_patch_to.fset is not None: + global_env = to_patch_to.fset.__globals__ + elif to_patch_to.fdel is not None: + global_env = to_patch_to.fdel.__globals__ + if not isinstance(global_env, dict): + global_env = dict(global_env) + exec(func_code, global_env, local_env) # type: ignore[arg-type] + # local_env contains the function exec'd from new version of function + if is_method: + to_patch_from = getattr(local_env["__autoreload_class__"], name) + else: + to_patch_from = local_env[name] + if isinstance(to_patch_from, (staticmethod, classmethod)): + to_patch_from = to_patch_from.__func__ + if isinstance(to_patch_to, property) and isinstance( + to_patch_from, property + ): + for attr in ("fget", "fset", "fdel"): + if ( + getattr(to_patch_to, attr) is None + or getattr(to_patch_from, attr) is None + ): + self.try_patch_attr(to_patch_to, to_patch_from, attr) + else: + self.patch_function( + getattr(to_patch_to, attr), + getattr(to_patch_from, attr), + is_method, + ) + elif not isinstance(to_patch_to, property) and not isinstance( + to_patch_from, property + ): + self.patch_function(to_patch_to, to_patch_from, is_method) + else: + raise ValueError( + "adding or removing property decorations not supported" + ) + else: + exec( + ast.unparse(new_ast_def), + ns.__dict__ | namespace_to_check.__dict__, + local_env, + ) + for name in names: + setattr(namespace_to_check, name, local_env[name]) + cur.defs_to_reload.clear() + for name in cur.defs_to_delete: + try: + delattr(namespace_to_check, name) + except (AttributeError, TypeError, ValueError): + # give up on deleting the attribute, let the stale one dangle + pass + cur.defs_to_delete.clear() + for class_name, class_ast_node in cur.new_nested_classes.items(): + local_env_class: dict[str, Any] = {} + exec( + ast.unparse(class_ast_node), + ns.__dict__ | namespace_to_check.__dict__, + local_env_class, + ) + setattr(namespace_to_check, class_name, local_env_class[class_name]) + cur.new_nested_classes.clear() + for class_name in cur.children.keys(): + if not self._patch_namespace(ns, prefixes + [class_name]): + return False + cur.children.clear() + return True + + def _patch_namespace( + self, ns: ModuleType | type, prefixes: list[str] | None = None + ) -> bool: + """ + Wrapper for patching all elements in a namespace as specified by the to_autoreload member variable. + Returns `true` if patching was successful, and `false` if unsuccessful. + """ + try: + return self._patch_namespace_inner(ns, prefixes=prefixes) + except Exception: + return False + + def maybe_reload_module(self, module: ModuleType) -> bool: + """ + Uses Deduperreload to try to update a module. + Returns `true` on success and `false` on failure. + """ + if not self.enabled: + return False + if not (modname := getattr(module, "__name__", None)): + return False + if (fname := get_module_file_name(module)) is None: + return False + with open(fname, "r") as f: + new_source_code = f.read() + patched_flag = False + if old_source_code := self.source_by_modname.get(modname): + # get old/new module ast + try: + old_module_ast = ast.parse(old_source_code) + new_module_ast = ast.parse(new_source_code) + except Exception: + return False + # detect if we are able to use our autoreload algorithm + ctx = contextlib.suppress() + with ctx: + self._build_dependency_graph(new_module_ast) + if ( + self.detect_autoreload(old_module_ast, new_module_ast) + and self._check_dependents() + and self._patch_namespace(module) + ): + patched_flag = True + + self.source_by_modname[modname] = new_source_code + self._to_autoreload = AutoreloadTree() + return patched_flag + + def _separate_name( + self, + decorator: ast.Attribute | ast.Name | ast.Call | ast.expr, + accept_calls: bool, + ) -> list[str] | None: + """ + Generates a qualified name for a given decorator by finding its relative namespace. + """ + if isinstance(decorator, ast.Name): + return [decorator.id] + elif isinstance(decorator, ast.Call): + if accept_calls: + return self._separate_name(decorator.func, False) + else: + return None + if not isinstance(decorator, ast.Attribute): + return None + if pref := self._separate_name(decorator.value, False): + return pref + [decorator.attr] + else: + return None + + def _gather_dependents( + self, body: list[ast.stmt], body_prefixes: list[str] | None = None + ) -> bool: + body_prefixes = body_prefixes or [] + for ast_node in body: + ast_elt: ast.expr | ast.stmt = ast_node + if isinstance(ast_elt, ast.ClassDef): + self._gather_dependents(ast_elt.body, body_prefixes + [ast_elt.name]) + continue + if not isinstance(ast_elt, (ast.FunctionDef, ast.AsyncFunctionDef)): + continue + qualified_name = tuple(body_prefixes + [ast_elt.name]) + cur_dependency_node = DependencyNode(qualified_name, ast_elt) + for decorator in ast_elt.decorator_list: + decorator_path = self._separate_name(decorator, True) + if not decorator_path: + continue + decorator_path_tuple = tuple(decorator_path) + self.dependency_graph.setdefault(decorator_path_tuple, []).append( + cur_dependency_node + ) + return True + + def _build_dependency_graph(self, new_ast: ast.Module | ast.ClassDef) -> bool: + """ + Wrapper function for generating dependency graph given some AST. + Returns `true` on success. Returns `false` on failure. + Currently, only returns `true` as we do not block on failure to build this graph. + """ + return self._gather_dependents(new_ast.body) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/extensions/deduperreload/deduperreload_patching.py b/temp_venv/lib/python3.13/site-packages/IPython/extensions/deduperreload/deduperreload_patching.py new file mode 100644 index 0000000000000000000000000000000000000000..f80d0936b6107e56afcb39bbd87f3856731f6196 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/extensions/deduperreload/deduperreload_patching.py @@ -0,0 +1,141 @@ +from __future__ import annotations +import ctypes +import sys +from typing import Any + +NOT_FOUND: object = object() +_MAX_FIELD_SEARCH_OFFSET = 50 + +if sys.maxsize > 2**32: + WORD_TYPE: type[ctypes.c_int32] | type[ctypes.c_int64] = ctypes.c_int64 + WORD_N_BYTES = 8 +else: + WORD_TYPE = ctypes.c_int32 + WORD_N_BYTES = 4 + + +class DeduperReloaderPatchingMixin: + @staticmethod + def infer_field_offset( + obj: object, + field: str, + ) -> int: + field_value = getattr(obj, field, NOT_FOUND) + if field_value is NOT_FOUND: + return -1 + obj_addr = ctypes.c_void_p.from_buffer(ctypes.py_object(obj)).value + field_addr = ctypes.c_void_p.from_buffer(ctypes.py_object(field_value)).value + if obj_addr is None or field_addr is None: + return -1 + ret = -1 + for offset in range(1, _MAX_FIELD_SEARCH_OFFSET): + if ( + ctypes.cast( + obj_addr + WORD_N_BYTES * offset, ctypes.POINTER(WORD_TYPE) + ).contents.value + == field_addr + ): + ret = offset + break + return ret + + @classmethod + def try_write_readonly_attr( + cls, + obj: object, + field: str, + new_value: object, + offset: int | None = None, + ) -> None: + prev_value = getattr(obj, field, NOT_FOUND) + if prev_value is NOT_FOUND: + return + if offset is None: + offset = cls.infer_field_offset(obj, field) + if offset == -1: + return + obj_addr = ctypes.c_void_p.from_buffer(ctypes.py_object(obj)).value + new_value_addr = ctypes.c_void_p.from_buffer(ctypes.py_object(new_value)).value + if obj_addr is None or new_value_addr is None: + return + if prev_value is not None: + ctypes.pythonapi.Py_DecRef(ctypes.py_object(prev_value)) + if new_value is not None: + ctypes.pythonapi.Py_IncRef(ctypes.py_object(new_value)) + ctypes.cast( + obj_addr + WORD_N_BYTES * offset, ctypes.POINTER(WORD_TYPE) + ).contents.value = new_value_addr + + @classmethod + def try_patch_readonly_attr( + cls, + old: object, + new: object, + field: str, + new_is_value: bool = False, + offset: int = -1, + ) -> None: + + old_value = getattr(old, field, NOT_FOUND) + new_value = new if new_is_value else getattr(new, field, NOT_FOUND) + if old_value is NOT_FOUND or new_value is NOT_FOUND: + return + elif old_value is new_value: + return + elif old_value is not None and offset < 0: + offset = cls.infer_field_offset(old, field) + elif offset < 0: + assert not new_is_value + assert new_value is not None + offset = cls.infer_field_offset(new, field) + cls.try_write_readonly_attr(old, field, new_value, offset=offset) + + @classmethod + def try_patch_attr( + cls, + old: object, + new: object, + field: str, + new_is_value: bool = False, + offset: int = -1, + ) -> None: + try: + setattr(old, field, new if new_is_value else getattr(new, field)) + except (AttributeError, TypeError, ValueError): + cls.try_patch_readonly_attr(old, new, field, new_is_value, offset) + + @classmethod + def patch_function( + cls, to_patch_to: Any, to_patch_from: Any, is_method: bool + ) -> None: + new_freevars = [] + new_closure = [] + for i, v in enumerate(to_patch_to.__code__.co_freevars): + if v not in to_patch_from.__code__.co_freevars or v == "__class__": + new_freevars.append(v) + new_closure.append(to_patch_to.__closure__[i]) + for i, v in enumerate(to_patch_from.__code__.co_freevars): + if v not in new_freevars: + new_freevars.append(v) + new_closure.append(to_patch_from.__closure__[i]) + code_with_new_freevars = to_patch_from.__code__.replace( + co_freevars=tuple(new_freevars) + ) + # lambdas may complain if there is more than one freevar + cls.try_patch_attr( + to_patch_to, code_with_new_freevars, "__code__", new_is_value=True + ) + offset = -1 + if to_patch_to.__closure__ is None and to_patch_from.__closure__ is not None: + offset = cls.infer_field_offset(to_patch_from, "__closure__") + cls.try_patch_readonly_attr( + to_patch_to, + tuple(new_closure) or None, + "__closure__", + new_is_value=True, + offset=offset, + ) + for attr in ("__defaults__", "__kwdefaults__", "__doc__", "__dict__"): + cls.try_patch_attr(to_patch_to, to_patch_from, attr) + if is_method: + cls.try_patch_readonly_attr(to_patch_to, to_patch_from, "__self__") diff --git a/temp_venv/lib/python3.13/site-packages/IPython/extensions/storemagic.py b/temp_venv/lib/python3.13/site-packages/IPython/extensions/storemagic.py new file mode 100644 index 0000000000000000000000000000000000000000..1ab56f72ea8cf571039a1bcd38541ca2a1b1cc79 --- /dev/null +++ b/temp_venv/lib/python3.13/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/temp_venv/lib/python3.13/site-packages/IPython/external/__init__.py b/temp_venv/lib/python3.13/site-packages/IPython/external/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..eedc338eb8471eb54c596d671d24e009afc9a134 --- /dev/null +++ b/temp_venv/lib/python3.13/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/temp_venv/lib/python3.13/site-packages/IPython/external/__pycache__/__init__.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/external/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb0f67c3680cc24f0eef0423c68b20eb1866ba59 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/external/__pycache__/__init__.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/external/__pycache__/pickleshare.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/external/__pycache__/pickleshare.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e902c23b5d42da5735efcfa6c7450de8fd2a9ec5 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/external/__pycache__/pickleshare.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/external/pickleshare.py b/temp_venv/lib/python3.13/site-packages/IPython/external/pickleshare.py new file mode 100644 index 0000000000000000000000000000000000000000..6f59945a9f004bf6e020f7dc78ba47d40af5a9f9 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/external/pickleshare.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python + +"""PickleShare - a small 'shelve' like datastore with concurrency support + +Like shelve, a PickleShareDB object acts like a normal dictionary. Unlike +shelve, many processes can access the database simultaneously. Changing a +value in database is immediately visible to other processes accessing the +same database. + +Concurrency is possible because the values are stored in separate files. Hence +the "database" is a directory where *all* files are governed by PickleShare. + +Example usage:: + + from pickleshare import * + db = PickleShareDB('~/testpickleshare') + db.clear() + print "Should be empty:",db.items() + db['hello'] = 15 + db['aku ankka'] = [1,2,313] + db['paths/are/ok/key'] = [1,(5,46)] + print db.keys() + del db['aku ankka'] + +This module is certainly not ZODB, but can be used for low-load +(non-mission-critical) situations where tiny code size trumps the +advanced features of a "real" object database. + +Installation guide: pip install pickleshare + +Author: Ville Vainio +License: MIT open source license. + +""" + +from __future__ import print_function + + +__version__ = "0.7.5" + +try: + from pathlib import Path +except ImportError: + # Python 2 backport + from pathlib2 import Path + +import os, stat, time + +try: + import collections.abc as collections_abc +except ImportError: + import collections as collections_abc +try: + import cPickle as pickle +except ImportError: + import pickle +import errno +import sys + +if sys.version_info[0] >= 3: + string_types = (str,) +else: + string_types = (str, unicode) + + +def gethashfile(key): + return ("%02x" % abs(hash(key) % 256))[-2:] + + +_sentinel = object() + + +class PickleShareDB(collections_abc.MutableMapping): + """The main 'connection' object for PickleShare database""" + + def __init__(self, root): + """Return a db object that will manage the specied directory""" + if not isinstance(root, string_types): + root = str(root) + root = os.path.abspath(os.path.expanduser(root)) + self.root = Path(root) + if not self.root.is_dir(): + # catching the exception is necessary if multiple processes are concurrently trying to create a folder + # exists_ok keyword argument of mkdir does the same but only from Python 3.5 + try: + self.root.mkdir(parents=True) + except OSError as e: + if e.errno != errno.EEXIST: + raise + # cache has { 'key' : (obj, orig_mod_time) } + self.cache = {} + + def __getitem__(self, key): + """db['key'] reading""" + fil = self.root / key + try: + mtime = fil.stat()[stat.ST_MTIME] + except OSError: + raise KeyError(key) + + if fil in self.cache and mtime == self.cache[fil][1]: + return self.cache[fil][0] + try: + # The cached item has expired, need to read + with fil.open("rb") as f: + obj = pickle.loads(f.read()) + except: + raise KeyError(key) + + self.cache[fil] = (obj, mtime) + return obj + + def __setitem__(self, key, value): + """db['key'] = 5""" + fil = self.root / key + parent = fil.parent + if parent and not parent.is_dir(): + parent.mkdir(parents=True) + # We specify protocol 2, so that we can mostly go between Python 2 + # and Python 3. We can upgrade to protocol 3 when Python 2 is obsolete. + with fil.open("wb") as f: + pickle.dump(value, f, protocol=2) + try: + self.cache[fil] = (value, fil.stat().st_mtime) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + def hset(self, hashroot, key, value): + """hashed set""" + hroot = self.root / hashroot + if not hroot.is_dir(): + hroot.mkdir() + hfile = hroot / gethashfile(key) + d = self.get(hfile, {}) + d.update({key: value}) + self[hfile] = d + + def hget(self, hashroot, key, default=_sentinel, fast_only=True): + """hashed get""" + hroot = self.root / hashroot + hfile = hroot / gethashfile(key) + + d = self.get(hfile, _sentinel) + # print "got dict",d,"from",hfile + if d is _sentinel: + if fast_only: + if default is _sentinel: + raise KeyError(key) + + return default + + # slow mode ok, works even after hcompress() + d = self.hdict(hashroot) + + return d.get(key, default) + + def hdict(self, hashroot): + """Get all data contained in hashed category 'hashroot' as dict""" + hfiles = self.keys(hashroot + "/*") + hfiles.sort() + last = len(hfiles) and hfiles[-1] or "" + if last.endswith("xx"): + # print "using xx" + hfiles = [last] + hfiles[:-1] + + all = {} + + for f in hfiles: + # print "using",f + try: + all.update(self[f]) + except KeyError: + print("Corrupt", f, "deleted - hset is not threadsafe!") + del self[f] + + self.uncache(f) + + return all + + def hcompress(self, hashroot): + """Compress category 'hashroot', so hset is fast again + + hget will fail if fast_only is True for compressed items (that were + hset before hcompress). + + """ + hfiles = self.keys(hashroot + "/*") + all = {} + for f in hfiles: + # print "using",f + all.update(self[f]) + self.uncache(f) + + self[hashroot + "/xx"] = all + for f in hfiles: + p = self.root / f + if p.name == "xx": + continue + p.unlink() + + def __delitem__(self, key): + """del db["key"]""" + fil = self.root / key + self.cache.pop(fil, None) + try: + fil.unlink() + except OSError: + # notfound and permission denied are ok - we + # lost, the other process wins the conflict + pass + + def _normalized(self, p): + """Make a key suitable for user's eyes""" + return str(p.relative_to(self.root)).replace("\\", "/") + + def keys(self, globpat=None): + """All keys in DB, or all keys matching a glob""" + + if globpat is None: + files = self.root.rglob("*") + else: + files = self.root.glob(globpat) + return [self._normalized(p) for p in files if p.is_file()] + + def __iter__(self): + return iter(self.keys()) + + def __len__(self): + return len(self.keys()) + + def uncache(self, *items): + """Removes all, or specified items from cache + + Use this after reading a large amount of large objects + to free up memory, when you won't be needing the objects + for a while. + + """ + if not items: + self.cache = {} + for it in items: + self.cache.pop(it, None) + + def waitget(self, key, maxwaittime=60): + """Wait (poll) for a key to get a value + + Will wait for `maxwaittime` seconds before raising a KeyError. + The call exits normally if the `key` field in db gets a value + within the timeout period. + + Use this for synchronizing different processes or for ensuring + that an unfortunately timed "db['key'] = newvalue" operation + in another process (which causes all 'get' operation to cause a + KeyError for the duration of pickling) won't screw up your program + logic. + """ + + wtimes = [0.2] * 3 + [0.5] * 2 + [1] + tries = 0 + waited = 0 + while 1: + try: + val = self[key] + return val + except KeyError: + pass + + if waited > maxwaittime: + raise KeyError(key) + + time.sleep(wtimes[tries]) + waited += wtimes[tries] + if tries < len(wtimes) - 1: + tries += 1 + + def getlink(self, folder): + """Get a convenient link for accessing items""" + return PickleShareLink(self, folder) + + def __repr__(self): + return "PickleShareDB('%s')" % self.root + + +class PickleShareLink: + """A shortdand for accessing nested PickleShare data conveniently. + + Created through PickleShareDB.getlink(), example:: + + lnk = db.getlink('myobjects/test') + lnk.foo = 2 + lnk.bar = lnk.foo + 5 + + """ + + def __init__(self, db, keydir): + self.__dict__.update(locals()) + + def __getattr__(self, key): + return self.__dict__["db"][self.__dict__["keydir"] + "/" + key] + + def __setattr__(self, key, val): + self.db[self.keydir + "/" + key] = val + + def __repr__(self): + db = self.__dict__["db"] + keys = db.keys(self.__dict__["keydir"] + "/*") + return "" % ( + self.__dict__["keydir"], + ";".join([Path(k).basename() for k in keys]), + ) + + +def main(): + import textwrap + + usage = textwrap.dedent( + """\ + pickleshare - manage PickleShare databases + + Usage: + + pickleshare dump /path/to/db > dump.txt + pickleshare load /path/to/db < dump.txt + pickleshare test /path/to/db + """ + ) + DB = PickleShareDB + import sys + + if len(sys.argv) < 2: + print(usage) + return + + cmd = sys.argv[1] + args = sys.argv[2:] + if cmd == "dump": + if not args: + args = ["."] + db = DB(args[0]) + import pprint + + pprint.pprint(db.items()) + elif cmd == "load": + cont = sys.stdin.read() + db = DB(args[0]) + data = eval(cont) + db.clear() + for k, v in db.items(): + db[k] = v + elif cmd == "testwait": + db = DB(args[0]) + db.clear() + print(db.waitget("250")) + elif cmd == "test": + test() + stress() + + +if __name__ == "__main__": + main() diff --git a/temp_venv/lib/python3.13/site-packages/IPython/external/qt_for_kernel.py b/temp_venv/lib/python3.13/site-packages/IPython/external/qt_for_kernel.py new file mode 100644 index 0000000000000000000000000000000000000000..125f4e6d2ae8157c7d019c2d5cc0bd7ca07f4cec --- /dev/null +++ b/temp_venv/lib/python3.13/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/temp_venv/lib/python3.13/site-packages/IPython/external/qt_loaders.py b/temp_venv/lib/python3.13/site-packages/IPython/external/qt_loaders.py new file mode 100644 index 0000000000000000000000000000000000000000..87b716582a14dc04892867c16cc579819c771ba4 --- /dev/null +++ b/temp_venv/lib/python3.13/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/temp_venv/lib/python3.13/site-packages/IPython/lib/__init__.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cf8ccb04151194985766e41ab7ef427e95228be9 --- /dev/null +++ b/temp_venv/lib/python3.13/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/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/__init__.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0bbe22d80035b624af7e9ae3c27593dd59e22cf6 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/__init__.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/clipboard.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/clipboard.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a943c9e1666c2f033b903e86a7fd463d8d265e3f Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/clipboard.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/display.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/display.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7253de5fc90760bec1f9f21bfa3a678488dd8b7 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/display.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/pretty.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/pretty.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f78c7c5fbcf388a847b7b2e10a521f25e8e9ae1a Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/lib/__pycache__/pretty.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/lib/backgroundjobs.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/backgroundjobs.py new file mode 100644 index 0000000000000000000000000000000000000000..29345ab87acda6ae0df2dbde1bbee9e60fdad613 --- /dev/null +++ b/temp_venv/lib/python3.13/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: + """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/temp_venv/lib/python3.13/site-packages/IPython/lib/clipboard.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/clipboard.py new file mode 100644 index 0000000000000000000000000000000000000000..e0bf80b075563d76939e9565981ca4426510c582 --- /dev/null +++ b/temp_venv/lib/python3.13/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/temp_venv/lib/python3.13/site-packages/IPython/lib/deepreload.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/deepreload.py new file mode 100644 index 0000000000000000000000000000000000000000..aaedab24255eed6b0213970be6e786d38e1cf900 --- /dev/null +++ b/temp_venv/lib/python3.13/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/temp_venv/lib/python3.13/site-packages/IPython/lib/demo.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/demo.py new file mode 100644 index 0000000000000000000000000000000000000000..c3908955fca7a76b30795d6d9cadd0cd12842757 --- /dev/null +++ b/temp_venv/lib/python3.13/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: + + 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: + """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/temp_venv/lib/python3.13/site-packages/IPython/lib/display.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/display.py new file mode 100644 index 0000000000000000000000000000000000000000..d2e942b0404a8b548065e22206be2b585545dd50 --- /dev/null +++ b/temp_venv/lib/python3.13/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: + """ + 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: + """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/temp_venv/lib/python3.13/site-packages/IPython/lib/editorhooks.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/editorhooks.py new file mode 100644 index 0000000000000000000000000000000000000000..d8bd6ac81bcfa42204ecd9415b8f1ffd1dd0be57 --- /dev/null +++ b/temp_venv/lib/python3.13/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/temp_venv/lib/python3.13/site-packages/IPython/lib/guisupport.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/guisupport.py new file mode 100644 index 0000000000000000000000000000000000000000..4d532d0f4d589efa103890a10fa41d047a223ead --- /dev/null +++ b/temp_venv/lib/python3.13/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/temp_venv/lib/python3.13/site-packages/IPython/lib/latextools.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/latextools.py new file mode 100644 index 0000000000000000000000000000000000000000..7e739f783d987f5b50e731ebf901823440749c6b --- /dev/null +++ b/temp_venv/lib/python3.13/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 +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/temp_venv/lib/python3.13/site-packages/IPython/lib/lexers.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/lexers.py new file mode 100644 index 0000000000000000000000000000000000000000..c13eeb3f0315c088e8ef56086f7a7ad5d8cf200c --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/lib/lexers.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +""" +The IPython lexers are now a separate package, ipython-pygments-lexers. + +Importing from here is deprecated and may break in the future. +""" +# ----------------------------------------------------------------------------- +# 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. +# ----------------------------------------------------------------------------- + +from ipython_pygments_lexers import ( + IPythonLexer, + IPython3Lexer, + IPythonPartialTracebackLexer, + IPythonTracebackLexer, + IPythonConsoleLexer, + IPyLexer, +) + + +__all__ = [ + "IPython3Lexer", + "IPythonLexer", + "IPythonPartialTracebackLexer", + "IPythonTracebackLexer", + "IPythonConsoleLexer", + "IPyLexer", +] diff --git a/temp_venv/lib/python3.13/site-packages/IPython/lib/pretty.py b/temp_venv/lib/python3.13/site-packages/IPython/lib/pretty.py new file mode 100644 index 0000000000000000000000000000000000000000..b2d7c7a94f9e81aa1cd92ed7b7450c37d3fa5cd7 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/lib/pretty.py @@ -0,0 +1,954 @@ +""" +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 + +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: + + @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: + + 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: + + 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: + 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/temp_venv/lib/python3.13/site-packages/IPython/testing/__init__.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8fcd65ea41a9406de5b11064524849b5bb4579f3 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/__init__.py @@ -0,0 +1,20 @@ +"""Testing support (tools to test IPython itself). +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2009-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. +#----------------------------------------------------------------------------- + + +import os + +#----------------------------------------------------------------------------- +# Constants +#----------------------------------------------------------------------------- + +# We scale all timeouts via this factor, slow machines can increase it +IPYTHON_TESTING_TIMEOUT_SCALE = float(os.getenv( + 'IPYTHON_TESTING_TIMEOUT_SCALE', 1)) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/__pycache__/__init__.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/testing/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c462b51829fb8d5ed219a2509cda966d488f1f86 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/testing/__pycache__/__init__.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/__pycache__/skipdoctest.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/testing/__pycache__/skipdoctest.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b91fdd7fc0c5cf55009fb2d4f3b0628703617696 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/testing/__pycache__/skipdoctest.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/decorators.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..9a10688302f7cf5ca67e0ba9a2fe91d6babac6de --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/decorators.py @@ -0,0 +1,148 @@ +import os +import shutil +import sys +import tempfile +from importlib import import_module + +import pytest + +# Expose the unittest-driven decorators +from .ipunittest import ipdoctest, ipdocstring + +def skipif(skip_condition, msg=None): + """Make function raise SkipTest exception if skip_condition is true + + Parameters + ---------- + + skip_condition : bool or callable + Flag to determine whether to skip test. If the condition is a + callable, it is used at runtime to dynamically make the decision. This + is useful for tests that may require costly imports, to delay the cost + until the test suite is actually executed. + msg : string + Message to give on raising a SkipTest exception. + + Returns + ------- + decorator : function + Decorator, which, when applied to a function, causes SkipTest + to be raised when the skip_condition was True, and the function + to be called normally otherwise. + """ + if msg is None: + msg = "Test skipped due to test condition." + + assert isinstance(skip_condition, bool) + return pytest.mark.skipif(skip_condition, reason=msg) + + +# A version with the condition set to true, common case just to attach a message +# to a skip decorator +def skip(msg=None): + """Decorator factory - mark a test function for skipping from test suite. + + Parameters + ---------- + msg : string + Optional message to be added. + + Returns + ------- + decorator : function + Decorator, which, when applied to a function, causes SkipTest + to be raised, with the optional message added. + """ + if msg and not isinstance(msg, str): + raise ValueError( + "invalid object passed to `@skip` decorator, did you " + "meant `@skip()` with brackets ?" + ) + return skipif(True, msg) + + +def onlyif(condition, msg): + """The reverse from skipif, see skipif for details.""" + + return skipif(not condition, msg) + + +# ----------------------------------------------------------------------------- +# Utility functions for decorators +def module_not_available(module): + """Can module be imported? Returns true if module does NOT import. + + This is used to make a decorator to skip tests that require module to be + available, but delay the 'import numpy' to test execution time. + """ + try: + mod = import_module(module) + mod_not_avail = False + except ImportError: + mod_not_avail = True + + return mod_not_avail + + +# ----------------------------------------------------------------------------- +# Decorators for public use + +# Decorators to skip certain tests on specific platforms. +skip_win32 = skipif(sys.platform == "win32", "This test does not run under Windows") + + +# Decorators to skip tests if not on specific platforms. +skip_if_not_win32 = skipif(sys.platform != "win32", "This test only runs under Windows") +skip_if_not_osx = skipif( + not sys.platform.startswith("darwin"), "This test only runs under macOS" +) + +_x11_skip_cond = ( + sys.platform not in ("darwin", "win32") and os.environ.get("DISPLAY", "") == "" +) +_x11_skip_msg = "Skipped under *nix when X11/XOrg not available" + +skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg) + +# Other skip decorators + +# generic skip without module +skip_without = lambda mod: skipif( + module_not_available(mod), "This test requires %s" % mod +) + +skipif_not_numpy = skip_without("numpy") + +skipif_not_matplotlib = skip_without("matplotlib") + +# A null 'decorator', useful to make more readable code that needs to pick +# between different decorators based on OS or other conditions +null_deco = lambda f: f + +# Some tests only run where we can use unicode paths. Note that we can't just +# check os.path.supports_unicode_filenames, which is always False on Linux. +try: + f = tempfile.NamedTemporaryFile(prefix="tmp€") +except UnicodeEncodeError: + unicode_paths = False +# TODO: should this be finnally ? +else: + unicode_paths = True + f.close() + +onlyif_unicode_paths = onlyif( + unicode_paths, + ("This test is only applicable where we can use unicode in filenames."), +) + + +def onlyif_cmds_exist(*commands): + """ + Decorator to skip test when at least one of `commands` is not found. + """ + for cmd in commands: + reason = f"This test runs only if command '{cmd}' is installed" + if not shutil.which(cmd): + + return pytest.mark.skip(reason=reason) + return null_deco diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/globalipapp.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/globalipapp.py new file mode 100644 index 0000000000000000000000000000000000000000..3a699e07d619fe22fbdaba89d31e6563e36b25f9 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/globalipapp.py @@ -0,0 +1,114 @@ +"""Global IPython app to support test running. + +We must start our own ipython object and heavily muck with it so that all the +modifications IPython makes to system behavior don't send the doctest machinery +into a fit. This code should be considered a gross hack, but it gets the job +done. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import builtins as builtin_mod +import sys +import types + +from pathlib import Path + +from . import tools + +from IPython.core import page +from IPython.utils import io +from IPython.terminal.interactiveshell import TerminalInteractiveShell + + +def get_ipython(): + # This will get replaced by the real thing once we start IPython below + return start_ipython() + + +# A couple of methods to override those in the running IPython to interact +# better with doctest (doctest captures on raw stdout, so we need to direct +# various types of output there otherwise it will miss them). + +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() + + +def _showtraceback(self, etype, evalue, stb): + """Print the traceback purely on stdout for doctest to capture it. + """ + print(self.InteractiveTB.stb2text(stb), file=sys.stdout) + + +def start_ipython(): + """Start a global IPython shell, which we need for IPython-specific syntax. + """ + global get_ipython + + # This function should only ever run once! + if hasattr(start_ipython, 'already_called'): + return + start_ipython.already_called = True + + # Store certain global objects that IPython modifies + _displayhook = sys.displayhook + _excepthook = sys.excepthook + _main = sys.modules.get('__main__') + + # Create custom argv and namespaces for our IPython to be test-friendly + config = tools.default_config() + config.TerminalInteractiveShell.simple_prompt = True + + # Create and initialize our test-friendly IPython instance. + shell = TerminalInteractiveShell.instance(config=config, + ) + + # A few more tweaks needed for playing nicely with doctests... + + # remove history file + shell.tempfiles.append(Path(config.HistoryManager.hist_file)) + + # These traps are normally only active for interactive use, set them + # permanently since we'll be mocking interactive sessions. + shell.builtin_trap.activate() + + # Modify the IPython system call with one that uses getoutput, so that we + # can capture subcommands and print them to Python's stdout, otherwise the + # doctest machinery would miss them. + shell.system = types.MethodType(xsys, shell) + + shell._showtraceback = types.MethodType(_showtraceback, shell) + + # IPython is ready, now clean up some global state... + + # Deactivate the various python system hooks added by ipython for + # interactive convenience so we don't confuse the doctest system + sys.modules['__main__'] = _main + sys.displayhook = _displayhook + sys.excepthook = _excepthook + + # So that ipython magics and aliases can be doctested (they work by making + # a call into a global _ip object). Also make the top-level get_ipython + # now return this without recursively calling here again. + _ip = shell + get_ipython = _ip.get_ipython + builtin_mod._ip = _ip + builtin_mod.ip = _ip + builtin_mod.get_ipython = get_ipython + + # Override paging, so we don't require user interaction during the tests. + def nopage(strng, start=0, screen_lines=0, pager_cmd=None): + if isinstance(strng, dict): + strng = strng.get('text/plain', '') + print(strng) + + page.orig_page = page.pager_page + page.pager_page = nopage + + return _ip diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/ipunittest.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/ipunittest.py new file mode 100644 index 0000000000000000000000000000000000000000..2ce77a44edf5cef1f83e73eb48fd78770f184614 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/ipunittest.py @@ -0,0 +1,187 @@ +"""Experimental code for cleaner support of IPython syntax with unittest. + +In IPython up until 0.10, we've used very hacked up nose machinery for running +tests with IPython special syntax, and this has proved to be extremely slow. +This module provides decorators to try a different approach, stemming from a +conversation Brian and I (FP) had about this problem Sept/09. + +The goal is to be able to easily write simple functions that can be seen by +unittest as tests, and ultimately for these to support doctests with full +IPython syntax. Nose already offers this based on naming conventions and our +hackish plugins, but we are seeking to move away from nose dependencies if +possible. + +This module follows a different approach, based on decorators. + +- A decorator called @ipdoctest can mark any function as having a docstring + that should be viewed as a doctest, but after syntax conversion. + +Authors +------- + +- Fernando Perez +""" + + +#----------------------------------------------------------------------------- +# Copyright (C) 2009-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 +#----------------------------------------------------------------------------- + +# Stdlib +import re +import sys +import unittest +import builtins +from doctest import DocTestFinder, DocTestRunner, TestResults +from IPython.terminal.interactiveshell import InteractiveShell + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + +def count_failures(runner): + """Count number of failures in a doctest runner. + + Code modeled after the summarize() method in doctest. + """ + if sys.version_info < (3, 13): + return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0] + else: + return [ + TestResults(failure, try_) + for failure, try_, skip in runner._stats.values() + if failure > 0 + ] + + +class IPython2PythonConverter: + """Convert IPython 'syntax' to valid Python. + + Eventually this code may grow to be the full IPython syntax conversion + implementation, but for now it only does prompt conversion.""" + + def __init__(self): + self.rps1 = re.compile(r'In\ \[\d+\]: ') + self.rps2 = re.compile(r'\ \ \ \.\.\.+: ') + self.rout = re.compile(r'Out\[\d+\]: \s*?\n?') + self.pyps1 = '>>> ' + self.pyps2 = '... ' + self.rpyps1 = re.compile (r'(\s*%s)(.*)$' % self.pyps1) + self.rpyps2 = re.compile (r'(\s*%s)(.*)$' % self.pyps2) + + def __call__(self, ds): + """Convert IPython prompts to python ones in a string.""" + from . import globalipapp + + pyps1 = '>>> ' + pyps2 = '... ' + pyout = '' + + dnew = ds + dnew = self.rps1.sub(pyps1, dnew) + dnew = self.rps2.sub(pyps2, dnew) + dnew = self.rout.sub(pyout, dnew) + ip = InteractiveShell.instance() + + # Convert input IPython source into valid Python. + out = [] + newline = out.append + for line in dnew.splitlines(): + + mps1 = self.rpyps1.match(line) + if mps1 is not None: + prompt, text = mps1.groups() + newline(prompt+ip.prefilter(text, False)) + continue + + mps2 = self.rpyps2.match(line) + if mps2 is not None: + prompt, text = mps2.groups() + newline(prompt+ip.prefilter(text, True)) + continue + + newline(line) + newline('') # ensure a closing newline, needed by doctest + # print("PYSRC:", '\n'.join(out)) # dbg + return '\n'.join(out) + + #return dnew + + +class Doc2UnitTester: + """Class whose instances act as a decorator for docstring testing. + + In practice we're only likely to need one instance ever, made below (though + no attempt is made at turning it into a singleton, there is no need for + that). + """ + def __init__(self, verbose=False): + """New decorator. + + Parameters + ---------- + + verbose : boolean, optional (False) + Passed to the doctest finder and runner to control verbosity. + """ + self.verbose = verbose + # We can reuse the same finder for all instances + self.finder = DocTestFinder(verbose=verbose, recurse=False) + + def __call__(self, func): + """Use as a decorator: doctest a function's docstring as a unittest. + + This version runs normal doctests, but the idea is to make it later run + ipython syntax instead.""" + + # Capture the enclosing instance with a different name, so the new + # class below can see it without confusion regarding its own 'self' + # that will point to the test instance at runtime + d2u = self + + # Rewrite the function's docstring to have python syntax + if func.__doc__ is not None: + func.__doc__ = ip2py(func.__doc__) + + # Now, create a tester object that is a real unittest instance, so + # normal unittest machinery (or Nose, or Trial) can find it. + class Tester(unittest.TestCase): + def test(self): + # Make a new runner per function to be tested + runner = DocTestRunner(verbose=d2u.verbose) + for the_test in d2u.finder.find(func, func.__name__): + runner.run(the_test) + failed = count_failures(runner) + if failed: + # Since we only looked at a single function's docstring, + # failed should contain at most one item. More than that + # is a case we can't handle and should error out on + if len(failed) > 1: + err = "Invalid number of test results: %s" % failed + raise ValueError(err) + # Report a normal failure. + self.fail('failed doctests: %s' % str(failed[0])) + + # Rename it so test reports have the original signature. + Tester.__name__ = func.__name__ + return Tester + + +def ipdocstring(func): + """Change the function docstring via ip2py. + """ + if func.__doc__ is not None: + func.__doc__ = ip2py(func.__doc__) + return func + + +# Make an instance of the classes for public use +ipdoctest = Doc2UnitTester() +ip2py = IPython2PythonConverter() diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/__init__.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/dtexample.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/dtexample.py new file mode 100644 index 0000000000000000000000000000000000000000..68f7016e34dc57b9ae7aadfd028c36889fe36088 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/dtexample.py @@ -0,0 +1,167 @@ +"""Simple example using doctests. + +This file just contains doctests both using plain python and IPython prompts. +All tests should be loaded by nose. +""" + +import os + + +def pyfunc(): + """Some pure python tests... + + >>> pyfunc() + 'pyfunc' + + >>> import os + + >>> 2+3 + 5 + + >>> for i in range(3): + ... print(i, end=' ') + ... print(i+1, end=' ') + ... + 0 1 1 2 2 3 + """ + return 'pyfunc' + +def ipfunc(): + """Some ipython tests... + + In [1]: import os + + In [3]: 2+3 + Out[3]: 5 + + In [26]: for i in range(3): + ....: print(i, end=' ') + ....: print(i+1, end=' ') + ....: + 0 1 1 2 2 3 + + + It's OK to use '_' for the last result, but do NOT try to use IPython's + numbered history of _NN outputs, since those won't exist under the + doctest environment: + + In [7]: 'hi' + Out[7]: 'hi' + + In [8]: print(repr(_)) + 'hi' + + In [7]: 3+4 + Out[7]: 7 + + In [8]: _+3 + Out[8]: 10 + + In [9]: ipfunc() + Out[9]: 'ipfunc' + """ + return "ipfunc" + + +def ipos(): + """Examples that access the operating system work: + + In [1]: !echo hello + hello + + In [2]: !echo hello > /tmp/foo_iptest + + In [3]: !cat /tmp/foo_iptest + hello + + In [4]: rm -f /tmp/foo_iptest + """ + pass + + +ipos.__skip_doctest__ = os.name == "nt" + + +def ranfunc(): + """A function with some random output. + + Normal examples are verified as usual: + >>> 1+3 + 4 + + But if you put '# random' in the output, it is ignored: + >>> 1+3 + junk goes here... # random + + >>> 1+2 + again, anything goes #random + if multiline, the random mark is only needed once. + + >>> 1+2 + You can also put the random marker at the end: + # random + + >>> 1+2 + # random + .. or at the beginning. + + More correct input is properly verified: + >>> ranfunc() + 'ranfunc' + """ + return 'ranfunc' + + +def random_all(): + """A function where we ignore the output of ALL examples. + + Examples: + + # all-random + + This mark tells the testing machinery that all subsequent examples should + be treated as random (ignoring their output). They are still executed, + so if a they raise an error, it will be detected as such, but their + output is completely ignored. + + >>> 1+3 + junk goes here... + + >>> 1+3 + klasdfj; + + >>> 1+2 + again, anything goes + blah... + """ + pass + +def iprand(): + """Some ipython tests with random output. + + In [7]: 3+4 + Out[7]: 7 + + In [8]: print('hello') + world # random + + In [9]: iprand() + Out[9]: 'iprand' + """ + return 'iprand' + +def iprand_all(): + """Some ipython tests with fully random output. + + # all-random + + In [7]: 1 + Out[7]: 99 + + In [8]: print('hello') + world + + In [9]: iprand_all() + Out[9]: 'junk' + """ + return 'iprand_all' diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/ipdoctest.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/ipdoctest.py new file mode 100644 index 0000000000000000000000000000000000000000..5c23373fba2a9bfa7fe08e8bb03303d57c8587a3 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/ipdoctest.py @@ -0,0 +1,299 @@ +"""Nose Plugin that supports IPython doctests. + +Limitations: + +- When generating examples for use as doctests, make sure that you have + pretty-printing OFF. This can be done either by setting the + ``PlainTextFormatter.pprint`` option in your configuration file to False, or + by interactively disabling it with %Pprint. This is required so that IPython + output matches that of normal Python, which is used by doctest for internal + execution. + +- Do not rely on specific prompt numbers for results (such as using + '_34==True', for example). For IPython tests run via an external process the + prompt numbers may be different, and IPython tests run as normal python code + won't even have these special _NN variables set at all. +""" + +#----------------------------------------------------------------------------- +# Module imports + +# From the standard library +import doctest +import logging +import re + +from testpath import modified_env + +#----------------------------------------------------------------------------- +# Module globals and other constants +#----------------------------------------------------------------------------- + +log = logging.getLogger(__name__) + + +#----------------------------------------------------------------------------- +# Classes and functions +#----------------------------------------------------------------------------- + + +class DocTestFinder(doctest.DocTestFinder): + def _get_test(self, obj, name, module, globs, source_lines): + test = super()._get_test(obj, name, module, globs, source_lines) + + if bool(getattr(obj, "__skip_doctest__", False)) and test is not None: + for example in test.examples: + example.options[doctest.SKIP] = True + + return test + + +class IPDoctestOutputChecker(doctest.OutputChecker): + """Second-chance checker with support for random tests. + + If the default comparison doesn't pass, this checker looks in the expected + output string for flags that tell us to ignore the output. + """ + + random_re = re.compile(r'#\s*random\s+') + + def check_output(self, want, got, optionflags): + """Check output, accepting special markers embedded in the output. + + If the output didn't pass the default validation but the special string + '#random' is included, we accept it.""" + + # Let the original tester verify first, in case people have valid tests + # that happen to have a comment saying '#random' embedded in. + ret = doctest.OutputChecker.check_output(self, want, got, + optionflags) + if not ret and self.random_re.search(want): + # print('RANDOM OK:',want, file=sys.stderr) # dbg + return True + + return ret + + +# A simple subclassing of the original with a different class name, so we can +# distinguish and treat differently IPython examples from pure python ones. +class IPExample(doctest.Example): pass + + +class IPDocTestParser(doctest.DocTestParser): + """ + A class used to parse strings containing doctest examples. + + Note: This is a version modified to properly recognize IPython input and + convert any IPython examples into valid Python ones. + """ + # This regular expression is used to find doctest examples in a + # string. It defines three groups: `source` is the source code + # (including leading indentation and prompts); `indent` is the + # indentation of the first (PS1) line of the source code; and + # `want` is the expected output (including leading indentation). + + # Classic Python prompts or default IPython ones + _PS1_PY = r'>>>' + _PS2_PY = r'\.\.\.' + + _PS1_IP = r'In\ \[\d+\]:' + _PS2_IP = r'\ \ \ \.\.\.+:' + + _RE_TPL = r''' + # Source consists of a PS1 line followed by zero or more PS2 lines. + (?P + (?:^(?P [ ]*) (?P %s) .*) # PS1 line + (?:\n [ ]* (?P %s) .*)*) # PS2 lines + \n? # a newline + # Want consists of any non-blank lines that do not start with PS1. + (?P (?:(?![ ]*$) # Not a blank line + (?![ ]*%s) # Not a line starting with PS1 + (?![ ]*%s) # Not a line starting with PS2 + .*$\n? # But any other line + )*) + ''' + + _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY), + re.MULTILINE | re.VERBOSE) + + _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP), + re.MULTILINE | re.VERBOSE) + + # Mark a test as being fully random. In this case, we simply append the + # random marker ('#random') to each individual example's output. This way + # we don't need to modify any other code. + _RANDOM_TEST = re.compile(r'#\s*all-random\s+') + + def ip2py(self,source): + """Convert input IPython source into valid Python.""" + block = _ip.input_transformer_manager.transform_cell(source) + if len(block.splitlines()) == 1: + return _ip.prefilter(block) + else: + return block + + def parse(self, string, name=''): + """ + Divide the given string into examples and intervening text, + and return them as a list of alternating Examples and strings. + Line numbers for the Examples are 0-based. The optional + argument `name` is a name identifying this string, and is only + used for error messages. + """ + + # print('Parse string:\n',string) # dbg + + string = string.expandtabs() + # If all lines begin with the same indentation, then strip it. + min_indent = self._min_indent(string) + if min_indent > 0: + string = '\n'.join([l[min_indent:] for l in string.split('\n')]) + + output = [] + charno, lineno = 0, 0 + + # We make 'all random' tests by adding the '# random' mark to every + # block of output in the test. + if self._RANDOM_TEST.search(string): + random_marker = '\n# random' + else: + random_marker = '' + + # Whether to convert the input from ipython to python syntax + ip2py = False + # Find all doctest examples in the string. First, try them as Python + # examples, then as IPython ones + terms = list(self._EXAMPLE_RE_PY.finditer(string)) + if terms: + # Normal Python example + Example = doctest.Example + else: + # It's an ipython example. + terms = list(self._EXAMPLE_RE_IP.finditer(string)) + Example = IPExample + ip2py = True + + for m in terms: + # Add the pre-example text to `output`. + output.append(string[charno:m.start()]) + # Update lineno (lines before this example) + lineno += string.count('\n', charno, m.start()) + # Extract info from the regexp match. + (source, options, want, exc_msg) = \ + self._parse_example(m, name, lineno,ip2py) + + # Append the random-output marker (it defaults to empty in most + # cases, it's only non-empty for 'all-random' tests): + want += random_marker + + # Create an Example, and add it to the list. + if not self._IS_BLANK_OR_COMMENT(source): + output.append(Example(source, want, exc_msg, + lineno=lineno, + indent=min_indent+len(m.group('indent')), + options=options)) + # Update lineno (lines inside this example) + lineno += string.count('\n', m.start(), m.end()) + # Update charno. + charno = m.end() + # Add any remaining post-example text to `output`. + output.append(string[charno:]) + return output + + def _parse_example(self, m, name, lineno,ip2py=False): + """ + Given a regular expression match from `_EXAMPLE_RE` (`m`), + return a pair `(source, want)`, where `source` is the matched + example's source code (with prompts and indentation stripped); + and `want` is the example's expected output (with indentation + stripped). + + `name` is the string's name, and `lineno` is the line number + where the example starts; both are used for error messages. + + Optional: + `ip2py`: if true, filter the input via IPython to convert the syntax + into valid python. + """ + + # Get the example's indentation level. + indent = len(m.group('indent')) + + # Divide source into lines; check that they're properly + # indented; and then strip their indentation & prompts. + source_lines = m.group('source').split('\n') + + # We're using variable-length input prompts + ps1 = m.group('ps1') + ps2 = m.group('ps2') + ps1_len = len(ps1) + + self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len) + if ps2: + self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno) + + source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines]) + + if ip2py: + # Convert source input from IPython into valid Python syntax + source = self.ip2py(source) + + # Divide want into lines; check that it's properly indented; and + # then strip the indentation. Spaces before the last newline should + # be preserved, so plain rstrip() isn't good enough. + want = m.group('want') + want_lines = want.split('\n') + if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): + del want_lines[-1] # forget final newline & spaces after it + self._check_prefix(want_lines, ' '*indent, name, + lineno + len(source_lines)) + + # Remove ipython output prompt that might be present in the first line + want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0]) + + want = '\n'.join([wl[indent:] for wl in want_lines]) + + # If `want` contains a traceback message, then extract it. + m = self._EXCEPTION_RE.match(want) + if m: + exc_msg = m.group('msg') + else: + exc_msg = None + + # Extract options from the source. + options = self._find_options(source, name, lineno) + + return source, options, want, exc_msg + + def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len): + """ + Given the lines of a source string (including prompts and + leading indentation), check to make sure that every prompt is + followed by a space character. If any line is not followed by + a space character, then raise ValueError. + + Note: IPython-modified version which takes the input prompt length as a + parameter, so that prompts of variable length can be dealt with. + """ + space_idx = indent+ps1_len + min_len = space_idx+1 + for i, line in enumerate(lines): + if len(line) >= min_len and line[space_idx] != ' ': + raise ValueError('line %r of the docstring for %s ' + 'lacks blank after %s: %r' % + (lineno+i+1, name, + line[indent:space_idx], line)) + + +SKIP = doctest.register_optionflag('SKIP') + + +class IPDocTestRunner(doctest.DocTestRunner): + """Test runner that synchronizes the IPython namespace with test globals. + """ + + def run(self, test, compileflags=None, out=None, clear_globs=True): + # Override terminal size to standardise traceback format + with modified_env({'COLUMNS': '80', 'LINES': '24'}): + return super(IPDocTestRunner,self).run(test, + compileflags,out,clear_globs) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/pytest_ipdoctest.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/pytest_ipdoctest.py new file mode 100644 index 0000000000000000000000000000000000000000..df1f72e6426e3bddab6bdda15692b034467e6d9b --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/pytest_ipdoctest.py @@ -0,0 +1,880 @@ +# Based on Pytest doctest.py +# Original license: +# The MIT License (MIT) +# +# Copyright (c) 2004-2021 Holger Krekel and others +"""Discover and run ipdoctests in modules and test files.""" + +import bdb +import builtins +import inspect +import os +import platform +import sys +import traceback +import types +import warnings +from contextlib import contextmanager +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Generator, + Iterable, + List, + Optional, + Pattern, + Sequence, + Tuple, + Type, + Union, +) + +import pytest +from _pytest import outcomes +from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr +from _pytest._io import TerminalWriter +from _pytest.compat import safe_getattr +from _pytest.config import Config +from _pytest.config.argparsing import Parser + +try: + from _pytest.fixtures import TopRequest as FixtureRequest +except ImportError: + from _pytest.fixtures import FixtureRequest +from _pytest.nodes import Collector +from _pytest.outcomes import OutcomeException +from _pytest.pathlib import fnmatch_ex, import_path +from _pytest.python_api import approx +from _pytest.warning_types import PytestWarning + +if TYPE_CHECKING: + import doctest + + from .ipdoctest import IPDoctestOutputChecker + +DOCTEST_REPORT_CHOICE_NONE = "none" +DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" +DOCTEST_REPORT_CHOICE_NDIFF = "ndiff" +DOCTEST_REPORT_CHOICE_UDIFF = "udiff" +DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure" + +DOCTEST_REPORT_CHOICES = ( + DOCTEST_REPORT_CHOICE_NONE, + DOCTEST_REPORT_CHOICE_CDIFF, + DOCTEST_REPORT_CHOICE_NDIFF, + DOCTEST_REPORT_CHOICE_UDIFF, + DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE, +) + +# Lazy definition of runner class +RUNNER_CLASS = None +# Lazy definition of output checker class +CHECKER_CLASS: Optional[Type["IPDoctestOutputChecker"]] = None + +pytest_version = tuple([int(part) for part in pytest.__version__.split(".")]) + + +def pytest_addoption(parser: Parser) -> None: + parser.addini( + "ipdoctest_optionflags", + "option flags for ipdoctests", + type="args", + default=["ELLIPSIS"], + ) + parser.addini( + "ipdoctest_encoding", "encoding used for ipdoctest files", default="utf-8" + ) + group = parser.getgroup("collect") + group.addoption( + "--ipdoctest-modules", + action="store_true", + default=False, + help="run ipdoctests in all .py modules", + dest="ipdoctestmodules", + ) + group.addoption( + "--ipdoctest-report", + type=str.lower, + default="udiff", + help="choose another output format for diffs on ipdoctest failure", + choices=DOCTEST_REPORT_CHOICES, + dest="ipdoctestreport", + ) + group.addoption( + "--ipdoctest-glob", + action="append", + default=[], + metavar="pat", + help="ipdoctests file matching pattern, default: test*.txt", + dest="ipdoctestglob", + ) + group.addoption( + "--ipdoctest-ignore-import-errors", + action="store_true", + default=False, + help="ignore ipdoctest ImportErrors", + dest="ipdoctest_ignore_import_errors", + ) + group.addoption( + "--ipdoctest-continue-on-failure", + action="store_true", + default=False, + help="for a given ipdoctest, continue to run after the first failure", + dest="ipdoctest_continue_on_failure", + ) + + +def pytest_unconfigure() -> None: + global RUNNER_CLASS + + RUNNER_CLASS = None + + +def pytest_collect_file( + file_path: Path, + parent: Collector, +) -> Optional[Union["IPDoctestModule", "IPDoctestTextfile"]]: + config = parent.config + if file_path.suffix == ".py": + if config.option.ipdoctestmodules and not any( + (_is_setup_py(file_path), _is_main_py(file_path)) + ): + mod: IPDoctestModule = IPDoctestModule.from_parent(parent, path=file_path) + return mod + elif _is_ipdoctest(config, file_path, parent): + txt: IPDoctestTextfile = IPDoctestTextfile.from_parent(parent, path=file_path) + return txt + return None + + +if pytest_version[0] < 7: + _collect_file = pytest_collect_file + + def pytest_collect_file( + path, + parent: Collector, + ) -> Optional[Union["IPDoctestModule", "IPDoctestTextfile"]]: + return _collect_file(Path(path), parent) + + _import_path = import_path + + def import_path(path, root): + import py.path + + return _import_path(py.path.local(path)) + + +def _is_setup_py(path: Path) -> bool: + if path.name != "setup.py": + return False + contents = path.read_bytes() + return b"setuptools" in contents or b"distutils" in contents + + +def _is_ipdoctest(config: Config, path: Path, parent: Collector) -> bool: + if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path): + return True + globs = config.getoption("ipdoctestglob") or ["test*.txt"] + return any(fnmatch_ex(glob, path) for glob in globs) + + +def _is_main_py(path: Path) -> bool: + return path.name == "__main__.py" + + +class ReprFailDoctest(TerminalRepr): + def __init__( + self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] + ) -> None: + self.reprlocation_lines = reprlocation_lines + + def toterminal(self, tw: TerminalWriter) -> None: + for reprlocation, lines in self.reprlocation_lines: + for line in lines: + tw.line(line) + reprlocation.toterminal(tw) + + +class MultipleDoctestFailures(Exception): + def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None: + super().__init__() + self.failures = failures + + +def _init_runner_class() -> Type["IPDocTestRunner"]: + import doctest + from .ipdoctest import IPDocTestRunner + + class PytestDoctestRunner(IPDocTestRunner): + """Runner to collect failures. + + Note that the out variable in this case is a list instead of a + stdout-like object. + """ + + def __init__( + self, + checker: Optional["IPDoctestOutputChecker"] = None, + verbose: Optional[bool] = None, + optionflags: int = 0, + continue_on_failure: bool = True, + ) -> None: + super().__init__(checker=checker, verbose=verbose, optionflags=optionflags) + self.continue_on_failure = continue_on_failure + + def report_failure( + self, + out, + test: "doctest.DocTest", + example: "doctest.Example", + got: str, + ) -> None: + failure = doctest.DocTestFailure(test, example, got) + if self.continue_on_failure: + out.append(failure) + else: + raise failure + + def report_unexpected_exception( + self, + out, + test: "doctest.DocTest", + example: "doctest.Example", + exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType], + ) -> None: + if isinstance(exc_info[1], OutcomeException): + raise exc_info[1] + if isinstance(exc_info[1], bdb.BdbQuit): + outcomes.exit("Quitting debugger") + failure = doctest.UnexpectedException(test, example, exc_info) + if self.continue_on_failure: + out.append(failure) + else: + raise failure + + return PytestDoctestRunner + + +def _get_runner( + checker: Optional["IPDoctestOutputChecker"] = None, + verbose: Optional[bool] = None, + optionflags: int = 0, + continue_on_failure: bool = True, +) -> "IPDocTestRunner": + # We need this in order to do a lazy import on doctest + global RUNNER_CLASS + if RUNNER_CLASS is None: + RUNNER_CLASS = _init_runner_class() + # Type ignored because the continue_on_failure argument is only defined on + # PytestDoctestRunner, which is lazily defined so can't be used as a type. + return RUNNER_CLASS( # type: ignore + checker=checker, + verbose=verbose, + optionflags=optionflags, + continue_on_failure=continue_on_failure, + ) + + +class IPDoctestItem(pytest.Item): + _user_ns_orig: Dict[str, Any] + + def __init__( + self, + name: str, + parent: "Union[IPDoctestTextfile, IPDoctestModule]", + runner: Optional["IPDocTestRunner"] = None, + dtest: Optional["doctest.DocTest"] = None, + ) -> None: + super().__init__(name, parent) + self.runner = runner + self.dtest = dtest + self.obj = None + self.fixture_request: Optional[FixtureRequest] = None + self._user_ns_orig = {} + + @classmethod + def from_parent( # type: ignore + cls, + parent: "Union[IPDoctestTextfile, IPDoctestModule]", + *, + name: str, + runner: "IPDocTestRunner", + dtest: "doctest.DocTest", + ): + # incompatible signature due to imposed limits on subclass + """The public named constructor.""" + return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) + + def setup(self) -> None: + if self.dtest is not None: + self.fixture_request = _setup_fixtures(self) + globs = dict(getfixture=self.fixture_request.getfixturevalue) + for name, value in self.fixture_request.getfixturevalue( + "ipdoctest_namespace" + ).items(): + globs[name] = value + self.dtest.globs.update(globs) + + from .ipdoctest import IPExample + + if isinstance(self.dtest.examples[0], IPExample): + # for IPython examples *only*, we swap the globals with the ipython + # namespace, after updating it with the globals (which doctest + # fills with the necessary info from the module being tested). + self._user_ns_orig = {} + self._user_ns_orig.update(_ip.user_ns) + _ip.user_ns.update(self.dtest.globs) + # We must remove the _ key in the namespace, so that Python's + # doctest code sets it naturally + _ip.user_ns.pop("_", None) + _ip.user_ns["__builtins__"] = builtins + self.dtest.globs = _ip.user_ns + + def teardown(self) -> None: + from .ipdoctest import IPExample + + # Undo the test.globs reassignment we made + if isinstance(self.dtest.examples[0], IPExample): + self.dtest.globs = {} + _ip.user_ns.clear() + _ip.user_ns.update(self._user_ns_orig) + del self._user_ns_orig + + self.dtest.globs.clear() + + def runtest(self) -> None: + assert self.dtest is not None + assert self.runner is not None + _check_all_skipped(self.dtest) + self._disable_output_capturing_for_darwin() + failures: List[doctest.DocTestFailure] = [] + + # exec(compile(..., "single", ...), ...) puts result in builtins._ + had_underscore_value = hasattr(builtins, "_") + underscore_original_value = getattr(builtins, "_", None) + + # Save our current directory and switch out to the one where the + # test was originally created, in case another doctest did a + # directory change. We'll restore this in the finally clause. + curdir = os.getcwd() + os.chdir(self.fspath.dirname) + try: + # Type ignored because we change the type of `out` from what + # ipdoctest expects. + self.runner.run(self.dtest, out=failures, clear_globs=False) # type: ignore[arg-type] + finally: + os.chdir(curdir) + if had_underscore_value: + setattr(builtins, "_", underscore_original_value) + elif hasattr(builtins, "_"): + delattr(builtins, "_") + + if failures: + raise MultipleDoctestFailures(failures) + + def _disable_output_capturing_for_darwin(self) -> None: + """Disable output capturing. Otherwise, stdout is lost to ipdoctest (pytest#985).""" + if platform.system() != "Darwin": + return + capman = self.config.pluginmanager.getplugin("capturemanager") + if capman: + capman.suspend_global_capture(in_=True) + out, err = capman.read_global_capture() + sys.stdout.write(out) + sys.stderr.write(err) + + # TODO: Type ignored -- breaks Liskov Substitution. + def repr_failure( # type: ignore[override] + self, + excinfo: ExceptionInfo[BaseException], + ) -> Union[str, TerminalRepr]: + import doctest + + failures: Optional[ + Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]] + ] = None + if isinstance( + excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) + ): + failures = [excinfo.value] + elif isinstance(excinfo.value, MultipleDoctestFailures): + failures = excinfo.value.failures + + if failures is None: + return super().repr_failure(excinfo) + + reprlocation_lines = [] + for failure in failures: + example = failure.example + test = failure.test + filename = test.filename + if test.lineno is None: + lineno = None + else: + lineno = test.lineno + example.lineno + 1 + message = type(failure).__name__ + # TODO: ReprFileLocation doesn't expect a None lineno. + reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] + checker = _get_checker() + report_choice = _get_report_choice(self.config.getoption("ipdoctestreport")) + if lineno is not None: + assert failure.test.docstring is not None + lines = failure.test.docstring.splitlines(False) + # add line numbers to the left of the error message + assert test.lineno is not None + lines = [ + "%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines) + ] + # trim docstring error lines to 10 + lines = lines[max(example.lineno - 9, 0) : example.lineno + 1] + else: + lines = [ + "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example" + ] + indent = ">>>" + for line in example.source.splitlines(): + lines.append(f"??? {indent} {line}") + indent = "..." + if isinstance(failure, doctest.DocTestFailure): + lines += checker.output_difference( + example, failure.got, report_choice + ).split("\n") + else: + inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info) + lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)] + lines += [ + x.strip("\n") for x in traceback.format_exception(*failure.exc_info) + ] + reprlocation_lines.append((reprlocation, lines)) + return ReprFailDoctest(reprlocation_lines) + + def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + assert self.dtest is not None + return self.path, self.dtest.lineno, "[ipdoctest] %s" % self.name + + if pytest_version[0] < 7: + + @property + def path(self) -> Path: + return Path(self.fspath) + + +def _get_flag_lookup() -> Dict[str, int]: + import doctest + + return dict( + DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1, + DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE, + NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE, + ELLIPSIS=doctest.ELLIPSIS, + IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL, + COMPARISON_FLAGS=doctest.COMPARISON_FLAGS, + ALLOW_UNICODE=_get_allow_unicode_flag(), + ALLOW_BYTES=_get_allow_bytes_flag(), + NUMBER=_get_number_flag(), + ) + + +def get_optionflags(parent): + optionflags_str = parent.config.getini("ipdoctest_optionflags") + flag_lookup_table = _get_flag_lookup() + flag_acc = 0 + for flag in optionflags_str: + flag_acc |= flag_lookup_table[flag] + return flag_acc + + +def _get_continue_on_failure(config): + continue_on_failure = config.getvalue("ipdoctest_continue_on_failure") + if continue_on_failure: + # We need to turn off this if we use pdb since we should stop at + # the first failure. + if config.getvalue("usepdb"): + continue_on_failure = False + return continue_on_failure + + +class IPDoctestTextfile(pytest.Module): + obj = None + + def collect(self) -> Iterable[IPDoctestItem]: + import doctest + from .ipdoctest import IPDocTestParser + + # Inspired by doctest.testfile; ideally we would use it directly, + # but it doesn't support passing a custom checker. + encoding = self.config.getini("ipdoctest_encoding") + text = self.path.read_text(encoding) + filename = str(self.path) + name = self.path.name + globs = {"__name__": "__main__"} + + optionflags = get_optionflags(self) + + runner = _get_runner( + verbose=False, + optionflags=optionflags, + checker=_get_checker(), + continue_on_failure=_get_continue_on_failure(self.config), + ) + + parser = IPDocTestParser() + test = parser.get_doctest(text, globs, name, filename, 0) + if test.examples: + yield IPDoctestItem.from_parent( + self, name=test.name, runner=runner, dtest=test + ) + + if pytest_version[0] < 7: + + @property + def path(self) -> Path: + return Path(self.fspath) + + @classmethod + def from_parent( + cls, + parent, + *, + fspath=None, + path: Optional[Path] = None, + **kw, + ): + if path is not None: + import py.path + + fspath = py.path.local(path) + return super().from_parent(parent=parent, fspath=fspath, **kw) + + +def _check_all_skipped(test: "doctest.DocTest") -> None: + """Raise pytest.skip() if all examples in the given DocTest have the SKIP + option set.""" + import doctest + + all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples) + if all_skipped: + pytest.skip("all docstests skipped by +SKIP option") + + +def _is_mocked(obj: object) -> bool: + """Return if an object is possibly a mock object by checking the + existence of a highly improbable attribute.""" + return ( + safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None) + is not None + ) + + +@contextmanager +def _patch_unwrap_mock_aware() -> Generator[None, None, None]: + """Context manager which replaces ``inspect.unwrap`` with a version + that's aware of mock objects and doesn't recurse into them.""" + real_unwrap = inspect.unwrap + + def _mock_aware_unwrap( + func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None + ) -> Any: + try: + if stop is None or stop is _is_mocked: + return real_unwrap(func, stop=_is_mocked) + _stop = stop + return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func)) + except Exception as e: + warnings.warn( + "Got %r when unwrapping %r. This is usually caused " + "by a violation of Python's object protocol; see e.g. " + "https://github.com/pytest-dev/pytest/issues/5080" % (e, func), + PytestWarning, + ) + raise + + inspect.unwrap = _mock_aware_unwrap + try: + yield + finally: + inspect.unwrap = real_unwrap + + +class IPDoctestModule(pytest.Module): + def collect(self) -> Iterable[IPDoctestItem]: + import doctest + from .ipdoctest import DocTestFinder, IPDocTestParser + + class MockAwareDocTestFinder(DocTestFinder): + """A hackish ipdoctest finder that overrides stdlib internals to fix a stdlib bug. + + https://github.com/pytest-dev/pytest/issues/3456 + https://bugs.python.org/issue25532 + """ + + def _find_lineno(self, obj, source_lines): + """Doctest code does not take into account `@property`, this + is a hackish way to fix it. https://bugs.python.org/issue17446 + + Wrapped Doctests will need to be unwrapped so the correct + line number is returned. This will be reported upstream. #8796 + """ + if isinstance(obj, property): + obj = getattr(obj, "fget", obj) + + if hasattr(obj, "__wrapped__"): + # Get the main obj in case of it being wrapped + obj = inspect.unwrap(obj) + + # Type ignored because this is a private function. + return super()._find_lineno( # type:ignore[misc] + obj, + source_lines, + ) + + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> None: + if _is_mocked(obj): + return + with _patch_unwrap_mock_aware(): + # Type ignored because this is a private function. + super()._find( # type:ignore[misc] + tests, obj, name, module, source_lines, globs, seen + ) + + if self.path.name == "conftest.py": + if pytest_version[0] < 7: + module = self.config.pluginmanager._importconftest( + self.path, + self.config.getoption("importmode"), + ) + else: + kwargs = {"rootpath": self.config.rootpath} + if pytest_version >= (8, 1): + kwargs["consider_namespace_packages"] = False + module = self.config.pluginmanager._importconftest( + self.path, + self.config.getoption("importmode"), + **kwargs, + ) + else: + try: + kwargs = {"root": self.config.rootpath} + if pytest_version >= (8, 1): + kwargs["consider_namespace_packages"] = False + module = import_path(self.path, **kwargs) + except ImportError: + if self.config.getvalue("ipdoctest_ignore_import_errors"): + pytest.skip("unable to import module %r" % self.path) + else: + raise + # Uses internal doctest module parsing mechanism. + finder = MockAwareDocTestFinder(parser=IPDocTestParser()) + optionflags = get_optionflags(self) + runner = _get_runner( + verbose=False, + optionflags=optionflags, + checker=_get_checker(), + continue_on_failure=_get_continue_on_failure(self.config), + ) + + for test in finder.find(module, module.__name__): + if test.examples: # skip empty ipdoctests + yield IPDoctestItem.from_parent( + self, name=test.name, runner=runner, dtest=test + ) + + if pytest_version[0] < 7: + + @property + def path(self) -> Path: + return Path(self.fspath) + + @classmethod + def from_parent( + cls, + parent, + *, + fspath=None, + path: Optional[Path] = None, + **kw, + ): + if path is not None: + import py.path + + fspath = py.path.local(path) + return super().from_parent(parent=parent, fspath=fspath, **kw) + + +def _setup_fixtures(doctest_item: IPDoctestItem) -> FixtureRequest: + """Used by IPDoctestTextfile and IPDoctestItem to setup fixture information.""" + + def func() -> None: + pass + + doctest_item.funcargs = {} # type: ignore[attr-defined] + fm = doctest_item.session._fixturemanager + kwargs = {"node": doctest_item, "func": func, "cls": None} + if pytest_version <= (8, 0): + kwargs["funcargs"] = False + doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] + **kwargs + ) + fixture_request = FixtureRequest(doctest_item, _ispytest=True) + if pytest_version <= (8, 0): + fixture_request._fillfixtures() + return fixture_request + + +def _init_checker_class() -> Type["IPDoctestOutputChecker"]: + import doctest + import re + from .ipdoctest import IPDoctestOutputChecker + + class LiteralsOutputChecker(IPDoctestOutputChecker): + # Based on doctest_nose_plugin.py from the nltk project + # (https://github.com/nltk/nltk) and on the "numtest" doctest extension + # by Sebastien Boisgerault (https://github.com/boisgera/numtest). + + _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) + _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE) + _number_re = re.compile( + r""" + (?P + (?P + (?P [+-]?\d*)\.(?P\d+) + | + (?P [+-]?\d+)\. + ) + (?: + [Ee] + (?P [+-]?\d+) + )? + | + (?P [+-]?\d+) + (?: + [Ee] + (?P [+-]?\d+) + ) + ) + """, + re.VERBOSE, + ) + + def check_output(self, want: str, got: str, optionflags: int) -> bool: + if super().check_output(want, got, optionflags): + return True + + allow_unicode = optionflags & _get_allow_unicode_flag() + allow_bytes = optionflags & _get_allow_bytes_flag() + allow_number = optionflags & _get_number_flag() + + if not allow_unicode and not allow_bytes and not allow_number: + return False + + def remove_prefixes(regex: Pattern[str], txt: str) -> str: + return re.sub(regex, r"\1\2", txt) + + if allow_unicode: + want = remove_prefixes(self._unicode_literal_re, want) + got = remove_prefixes(self._unicode_literal_re, got) + + if allow_bytes: + want = remove_prefixes(self._bytes_literal_re, want) + got = remove_prefixes(self._bytes_literal_re, got) + + if allow_number: + got = self._remove_unwanted_precision(want, got) + + return super().check_output(want, got, optionflags) + + def _remove_unwanted_precision(self, want: str, got: str) -> str: + wants = list(self._number_re.finditer(want)) + gots = list(self._number_re.finditer(got)) + if len(wants) != len(gots): + return got + offset = 0 + for w, g in zip(wants, gots): + fraction: Optional[str] = w.group("fraction") + exponent: Optional[str] = w.group("exponent1") + if exponent is None: + exponent = w.group("exponent2") + precision = 0 if fraction is None else len(fraction) + if exponent is not None: + precision -= int(exponent) + if float(w.group()) == approx(float(g.group()), abs=10**-precision): + # They're close enough. Replace the text we actually + # got with the text we want, so that it will match when we + # check the string literally. + got = ( + got[: g.start() + offset] + w.group() + got[g.end() + offset :] + ) + offset += w.end() - w.start() - (g.end() - g.start()) + return got + + return LiteralsOutputChecker + + +def _get_checker() -> "IPDoctestOutputChecker": + """Return a IPDoctestOutputChecker subclass that supports some + additional options: + + * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b'' + prefixes (respectively) in string literals. Useful when the same + ipdoctest should run in Python 2 and Python 3. + + * NUMBER to ignore floating-point differences smaller than the + precision of the literal number in the ipdoctest. + + An inner class is used to avoid importing "ipdoctest" at the module + level. + """ + global CHECKER_CLASS + if CHECKER_CLASS is None: + CHECKER_CLASS = _init_checker_class() + return CHECKER_CLASS() + + +def _get_allow_unicode_flag() -> int: + """Register and return the ALLOW_UNICODE flag.""" + import doctest + + return doctest.register_optionflag("ALLOW_UNICODE") + + +def _get_allow_bytes_flag() -> int: + """Register and return the ALLOW_BYTES flag.""" + import doctest + + return doctest.register_optionflag("ALLOW_BYTES") + + +def _get_number_flag() -> int: + """Register and return the NUMBER flag.""" + import doctest + + return doctest.register_optionflag("NUMBER") + + +def _get_report_choice(key: str) -> int: + """Return the actual `ipdoctest` module flag value. + + We want to do it as late as possible to avoid importing `ipdoctest` and all + its dependencies when parsing options, as it adds overhead and breaks tests. + """ + import doctest + + return { + DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF, + DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF, + DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF, + DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE, + DOCTEST_REPORT_CHOICE_NONE: 0, + }[key] + + +@pytest.fixture(scope="session") +def ipdoctest_namespace() -> Dict[str, Any]: + """Fixture that returns a :py:class:`dict` that will be injected into the + namespace of ipdoctests.""" + return dict() diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/setup.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..a3281d30c8da509f46859b56ee66658e66156e84 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/setup.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +"""A Nose plugin to support IPython doctests. +""" + +from setuptools import setup + +setup(name='IPython doctest plugin', + version='0.1', + author='The IPython Team', + description = 'Nose plugin to load IPython-extended doctests', + license = 'LGPL', + py_modules = ['ipdoctest'], + entry_points = { + 'nose.plugins.0.10': ['ipdoctest = ipdoctest:IPythonDoctest', + 'extdoctest = ipdoctest:ExtensionDoctest', + ], + }, + ) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/simple.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/simple.py new file mode 100644 index 0000000000000000000000000000000000000000..79c9e0d21cc5dc446fe0197145d4a5347d34f499 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/simple.py @@ -0,0 +1,45 @@ +"""Simple example using doctests. + +This file just contains doctests both using plain python and IPython prompts. +All tests should be loaded by Pytest. +""" + + +def pyfunc(): + """Some pure python tests... + + >>> pyfunc() + 'pyfunc' + + >>> import os + + >>> 2+3 + 5 + + >>> for i in range(3): + ... print(i, end=' ') + ... print(i+1, end=' ') + ... + 0 1 1 2 2 3 + """ + return "pyfunc" + + +def ipyfunc(): + """Some IPython tests... + + In [1]: ipyfunc() + Out[1]: 'ipyfunc' + + In [2]: import os + + In [3]: 2+3 + Out[3]: 5 + + In [4]: for i in range(3): + ...: print(i, end=' ') + ...: print(i+1, end=' ') + ...: + Out[4]: 0 1 1 2 2 3 + """ + return "ipyfunc" diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/simplevars.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/simplevars.py new file mode 100644 index 0000000000000000000000000000000000000000..82a5edb028dada24662962811ff975d74d1c3a10 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/simplevars.py @@ -0,0 +1,2 @@ +x = 1 +print("x is:", x) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_combo.txt b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_combo.txt new file mode 100644 index 0000000000000000000000000000000000000000..6c8759f3e72ae3f1aea989826f58ca6863337435 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_combo.txt @@ -0,0 +1,36 @@ +======================= + Combo testing example +======================= + +This is a simple example that mixes ipython doctests:: + + In [1]: import code + + In [2]: 2**12 + Out[2]: 4096 + +with command-line example information that does *not* get executed:: + + $ mpirun -n 4 ipengine --controller-port=10000 --controller-ip=host0 + +and with literal examples of Python source code:: + + controller = dict(host='myhost', + engine_port=None, # default is 10105 + control_port=None, + ) + + # keys are hostnames, values are the number of engine on that host + engines = dict(node1=2, + node2=2, + node3=2, + node3=2, + ) + + # Force failure to detect that this test is being run. + 1/0 + +These source code examples are executed but no output is compared at all. An +error or failure is reported only if an exception is raised. + +NOTE: the execution of pure python blocks is not yet working! diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_example.txt b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_example.txt new file mode 100644 index 0000000000000000000000000000000000000000..f8b681eb4ff4410bc9d66a66ba77ee2c6dea6d12 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_example.txt @@ -0,0 +1,24 @@ +===================================== + Tests in example form - pure python +===================================== + +This file contains doctest examples embedded as code blocks, using normal +Python prompts. See the accompanying file for similar examples using IPython +prompts (you can't mix both types within one file). The following will be run +as a test:: + + >>> 1+1 + 2 + >>> print ("hello") + hello + +More than one example works:: + + >>> s="Hello World" + + >>> s.upper() + 'HELLO WORLD' + +but you should note that the *entire* test file is considered to be a single +test. Individual code blocks that fail are printed separately as ``example +failures``, but the whole file is still counted and reported as one test. diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_exampleip.txt b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_exampleip.txt new file mode 100644 index 0000000000000000000000000000000000000000..96b1eae19f09fb62702f5ee58851f4cad87aa9d5 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_exampleip.txt @@ -0,0 +1,30 @@ +================================= + Tests in example form - IPython +================================= + +You can write text files with examples that use IPython prompts (as long as you +use the nose ipython doctest plugin), but you can not mix and match prompt +styles in a single file. That is, you either use all ``>>>`` prompts or all +IPython-style prompts. Your test suite *can* have both types, you just need to +put each type of example in a separate. Using IPython prompts, you can paste +directly from your session:: + + In [5]: s="Hello World" + + In [6]: s.upper() + Out[6]: 'HELLO WORLD' + +Another example:: + + In [8]: 1+3 + Out[8]: 4 + +Just like in IPython docstrings, you can use all IPython syntax and features:: + + In [9]: !echo hello + hello + + In [10]: a='hi' + + In [11]: !echo $a + hi diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_ipdoctest.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_ipdoctest.py new file mode 100644 index 0000000000000000000000000000000000000000..2686172bb29e50c9fe824ef9f31b1d3badb1c7b4 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_ipdoctest.py @@ -0,0 +1,92 @@ +"""Tests for the ipdoctest machinery itself. + +Note: in a file named test_X, functions whose only test is their docstring (as +a doctest) and which have no test functionality of their own, should be called +'doctest_foo' instead of 'test_foo', otherwise they get double-counted (the +empty function call is counted as a test, which just inflates tests numbers +artificially). +""" + +def doctest_simple(): + """ipdoctest must handle simple inputs + + In [1]: 1 + Out[1]: 1 + + In [2]: print(1) + 1 + """ + +def doctest_multiline1(): + """The ipdoctest machinery must handle multiline examples gracefully. + + In [2]: for i in range(4): + ...: print(i) + ...: + 0 + 1 + 2 + 3 + """ + +def doctest_multiline2(): + """Multiline examples that define functions and print output. + + In [7]: def f(x): + ...: return x+1 + ...: + + In [8]: f(1) + Out[8]: 2 + + In [9]: def g(x): + ...: print('x is:',x) + ...: + + In [10]: g(1) + x is: 1 + + In [11]: g('hello') + x is: hello + """ + + +def doctest_multiline3(): + """Multiline examples with blank lines. + + In [12]: def h(x): + ....: if x>1: + ....: return x**2 + ....: # To leave a blank line in the input, you must mark it + ....: # with a comment character: + ....: # + ....: # otherwise the doctest parser gets confused. + ....: else: + ....: return -1 + ....: + + In [13]: h(5) + Out[13]: 25 + + In [14]: h(1) + Out[14]: -1 + + In [15]: h(0) + Out[15]: -1 + """ + + +def doctest_builtin_underscore(): + """Defining builtins._ should not break anything outside the doctest + while also should be working as expected inside the doctest. + + In [1]: import builtins + + In [2]: builtins._ = 42 + + In [3]: builtins._ + Out[3]: 42 + + In [4]: _ + Out[4]: 42 + """ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_refs.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_refs.py new file mode 100644 index 0000000000000000000000000000000000000000..b92448be074180e8311fa9820b52bc6fa70d696e --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/plugin/test_refs.py @@ -0,0 +1,39 @@ +"""Some simple tests for the plugin while running scripts. +""" +# Module imports +# Std lib +import inspect + +# Our own + +#----------------------------------------------------------------------------- +# Testing functions + +def test_trivial(): + """A trivial passing test.""" + pass + +def doctest_run(): + """Test running a trivial script. + + In [13]: run simplevars.py + x is: 1 + """ + +def doctest_runvars(): + """Test that variables defined in scripts get loaded correctly via %run. + + In [13]: run simplevars.py + x is: 1 + + In [14]: x + Out[14]: 1 + """ + +def doctest_ivars(): + """Test that variables defined interactively are picked up. + In [5]: zz=1 + + In [6]: zz + Out[6]: 1 + """ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/skipdoctest.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/skipdoctest.py new file mode 100644 index 0000000000000000000000000000000000000000..f440ea14b274de05919ce9e77bada084d8725218 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/skipdoctest.py @@ -0,0 +1,19 @@ +"""Decorators marks that a doctest should be skipped. + +The IPython.testing.decorators module triggers various extra imports, including +numpy and sympy if they're present. Since this decorator is used in core parts +of IPython, it's in a separate module so that running IPython doesn't trigger +those imports.""" + +# Copyright (C) IPython Development Team +# Distributed under the terms of the Modified BSD License. + + +def skip_doctest(f): + """Decorator - mark a function or method for skipping its doctest. + + This decorator allows you to mark a function whose docstring you wish to + omit from testing, while preserving the docstring for introspection, help, + etc.""" + f.__skip_doctest__ = True + return f diff --git a/temp_venv/lib/python3.13/site-packages/IPython/testing/tools.py b/temp_venv/lib/python3.13/site-packages/IPython/testing/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..d596a000f8f812bf6328b15fa640c394224db7fc --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/testing/tools.py @@ -0,0 +1,434 @@ +"""Generic testing tools. + +Authors +------- +- Fernando Perez +""" + + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import os +from pathlib import Path +import re +import sys +import tempfile +import unittest + +from contextlib import contextmanager +from io import StringIO +from subprocess import Popen, PIPE +from unittest.mock import patch + +from traitlets.config.loader import Config +from IPython.utils.process import get_output_error_code +from IPython.utils.text import list_strings +from IPython.utils.io import temp_pyfile, Tee +from IPython.utils import py3compat + +from . import decorators as dec +from . import skipdoctest + + +# The docstring for full_path doctests differently on win32 (different path +# separator) so just skip the doctest there. The example remains informative. +doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco + +@doctest_deco +def full_path(startPath: str, files: list[str]) -> list[str]: + """Make full paths for all the listed files, based on startPath. + + Only the base part of startPath is kept, since this routine is typically + used with a script's ``__file__`` variable as startPath. The base of startPath + is then prepended to all the listed files, forming the output list. + + Parameters + ---------- + startPath : string + Initial path to use as the base for the results. This path is split + using os.path.split() and only its first component is kept. + + files : list + One or more files. + + Examples + -------- + + >>> full_path('/foo/bar.py',['a.txt','b.txt']) + ['/foo/a.txt', '/foo/b.txt'] + + >>> full_path('/foo',['a.txt','b.txt']) + ['/a.txt', '/b.txt'] + + """ + assert isinstance(files, list) + base = os.path.split(startPath)[0] + return [ os.path.join(base,f) for f in files ] + + +def parse_test_output(txt): + """Parse the output of a test run and return errors, failures. + + Parameters + ---------- + txt : str + Text output of a test run, assumed to contain a line of one of the + following forms:: + + 'FAILED (errors=1)' + 'FAILED (failures=1)' + 'FAILED (errors=1, failures=1)' + + Returns + ------- + nerr, nfail + number of errors and failures. + """ + + err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE) + if err_m: + nerr = int(err_m.group(1)) + nfail = 0 + return nerr, nfail + + fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE) + if fail_m: + nerr = 0 + nfail = int(fail_m.group(1)) + return nerr, nfail + + both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt, + re.MULTILINE) + if both_m: + nerr = int(both_m.group(1)) + nfail = int(both_m.group(2)) + return nerr, nfail + + # If the input didn't match any of these forms, assume no error/failures + return 0, 0 + + +# So nose doesn't think this is a test +parse_test_output.__test__ = False + + +def default_argv(): + """Return a valid default argv for creating testing instances of ipython""" + + return [ + "--quick", # so no config file is loaded + # Other defaults to minimize side effects on stdout + "--colors=nocolor", + "--no-term-title", + "--no-banner", + "--autocall=0", + ] + + +def default_config(): + """Return a config object with good defaults for testing.""" + config = Config() + config.TerminalInteractiveShell.colors = "nocolor" + config.TerminalTerminalInteractiveShell.term_title = (False,) + config.TerminalInteractiveShell.autocall = 0 + f = tempfile.NamedTemporaryFile(suffix="test_hist.sqlite", delete=False) + config.HistoryManager.hist_file = Path(f.name) + f.close() + config.HistoryManager.db_cache_size = 10000 + return config + + +def get_ipython_cmd(as_string=False): + """ + Return appropriate IPython command line name. By default, this will return + a list that can be used with subprocess.Popen, for example, but passing + `as_string=True` allows for returning the IPython command as a string. + + Parameters + ---------- + as_string: bool + Flag to allow to return the command as a string. + """ + ipython_cmd = [sys.executable, "-m", "IPython"] + + if as_string: + ipython_cmd = " ".join(ipython_cmd) + + return ipython_cmd + +def ipexec(fname, options=None, commands=()): + """Utility to call 'ipython filename'. + + Starts IPython with a minimal and safe configuration to make startup as fast + as possible. + + Note that this starts IPython in a subprocess! + + Parameters + ---------- + fname : str, Path + Name of file to be executed (should have .py or .ipy extension). + + options : optional, list + Extra command-line flags to be passed to IPython. + + commands : optional, list + Commands to send in on stdin + + Returns + ------- + ``(stdout, stderr)`` of ipython subprocess. + """ + __tracebackhide__ = True + + if options is None: + options = [] + + cmdargs = default_argv() + options + + test_dir = os.path.dirname(__file__) + + ipython_cmd = get_ipython_cmd() + # Absolute path for filename + full_fname = os.path.join(test_dir, fname) + full_cmd = ipython_cmd + cmdargs + ['--', full_fname] + env = os.environ.copy() + # FIXME: ignore all warnings in ipexec while we have shims + # should we keep suppressing warnings here, even after removing shims? + env['PYTHONWARNINGS'] = 'ignore' + # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr + # Prevent coloring under PyCharm ("\x1b[0m" at the end of the stdout) + env.pop("PYCHARM_HOSTED", None) + for k, v in env.items(): + # Debug a bizarre failure we've seen on Windows: + # TypeError: environment can only contain strings + if not isinstance(v, str): + print(k, v) + p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env) + out, err = p.communicate(input=py3compat.encode('\n'.join(commands)) or None) + out, err = py3compat.decode(out), py3compat.decode(err) + # `import readline` causes 'ESC[?1034h' to be output sometimes, + # so strip that out before doing comparisons + if out: + out = re.sub(r'\x1b\[[^h]+h', '', out) + return out, err + + +def ipexec_validate(fname, expected_out, expected_err='', + options=None, commands=()): + """Utility to call 'ipython filename' and validate output/error. + + This function raises an AssertionError if the validation fails. + + Note that this starts IPython in a subprocess! + + Parameters + ---------- + fname : str, Path + Name of the file to be executed (should have .py or .ipy extension). + + expected_out : str + Expected stdout of the process. + + expected_err : optional, str + Expected stderr of the process. + + options : optional, list + Extra command-line flags to be passed to IPython. + + Returns + ------- + None + """ + __tracebackhide__ = True + + out, err = ipexec(fname, options, commands) + # print('OUT', out) # dbg + # print('ERR', err) # dbg + # If there are any errors, we must check those before stdout, as they may be + # more informative than simply having an empty stdout. + if err: + if expected_err: + assert "\n".join(err.strip().splitlines()) == "\n".join( + expected_err.strip().splitlines() + ) + else: + raise ValueError('Running file %r produced error: %r' % + (fname, err)) + # If no errors or output on stderr was expected, match stdout + assert "\n".join(out.strip().splitlines()) == "\n".join( + expected_out.strip().splitlines() + ) + + +class TempFileMixin(unittest.TestCase): + """Utility class to create temporary Python/IPython files. + + Meant as a mixin class for test cases.""" + + def mktmp(self, src, ext='.py'): + """Make a valid python temp file.""" + fname = temp_pyfile(src, ext) + if not hasattr(self, 'tmps'): + self.tmps=[] + self.tmps.append(fname) + self.fname = fname + + def tearDown(self): + # If the tmpfile wasn't made because of skipped tests, like in + # win32, there's nothing to cleanup. + if hasattr(self, 'tmps'): + for fname in self.tmps: + # If the tmpfile wasn't made because of skipped tests, like in + # win32, there's nothing to cleanup. + try: + os.unlink(fname) + except: + # On Windows, even though we close the file, we still can't + # delete it. I have no clue why + pass + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.tearDown() + + +MyStringIO = StringIO + +_re_type = type(re.compile(r'')) + +notprinted_msg = """Did not find {0!r} in printed output (on {1}): +------- +{2!s} +------- +""" + +class AssertPrints: + """Context manager for testing that code prints certain text. + + Examples + -------- + >>> with AssertPrints("abc", suppress=False): + ... print("abcd") + ... print("def") + ... + abcd + def + """ + def __init__(self, s, channel='stdout', suppress=True): + self.s = s + if isinstance(self.s, (str, _re_type)): + self.s = [self.s] + self.channel = channel + self.suppress = suppress + + def __enter__(self): + self.orig_stream = getattr(sys, self.channel) + self.buffer = MyStringIO() + self.tee = Tee(self.buffer, channel=self.channel) + setattr(sys, self.channel, self.buffer if self.suppress else self.tee) + + def __exit__(self, etype, value, traceback): + __tracebackhide__ = True + + try: + if value is not None: + # If an error was raised, don't check anything else + return False + self.tee.flush() + setattr(sys, self.channel, self.orig_stream) + printed = self.buffer.getvalue() + for s in self.s: + if isinstance(s, _re_type): + assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed) + else: + assert s in printed, notprinted_msg.format(s, self.channel, printed) + return False + finally: + self.tee.close() + +printed_msg = """Found {0!r} in printed output (on {1}): +------- +{2!s} +------- +""" + +class AssertNotPrints(AssertPrints): + """Context manager for checking that certain output *isn't* produced. + + Counterpart of AssertPrints""" + def __exit__(self, etype, value, traceback): + __tracebackhide__ = True + + try: + if value is not None: + # If an error was raised, don't check anything else + self.tee.close() + return False + self.tee.flush() + setattr(sys, self.channel, self.orig_stream) + printed = self.buffer.getvalue() + for s in self.s: + if isinstance(s, _re_type): + assert not s.search(printed),printed_msg.format( + s.pattern, self.channel, printed) + else: + assert s not in printed, printed_msg.format( + s, self.channel, printed) + return False + finally: + self.tee.close() + +@contextmanager +def make_tempfile(name): + """Create an empty, named, temporary file for the duration of the context.""" + open(name, "w", encoding="utf-8").close() + try: + yield + finally: + os.unlink(name) + +def fake_input(inputs): + """Temporarily replace the input() function to return the given values + + Use as a context manager: + + with fake_input(['result1', 'result2']): + ... + + Values are returned in order. If input() is called again after the last value + was used, EOFError is raised. + """ + it = iter(inputs) + def mock_input(prompt=''): + try: + return next(it) + except StopIteration as e: + raise EOFError('No more inputs given') from e + + return patch('builtins.input', mock_input) + +def help_output_test(subcommand=''): + """test that `ipython [subcommand] -h` works""" + cmd = get_ipython_cmd() + [subcommand, '-h'] + out, err, rc = get_output_error_code(cmd) + assert rc == 0, err + assert "Traceback" not in err + assert "Options" in out + assert "--help-all" in out + return out, err + + +def help_all_output_test(subcommand=''): + """test that `ipython [subcommand] --help-all` works""" + cmd = get_ipython_cmd() + [subcommand, '--help-all'] + out, err, rc = get_output_error_code(cmd) + assert rc == 0, err + assert "Traceback" not in err + assert "Options" in out + assert "Class" in out + return out, err + diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/PyColorize.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/PyColorize.py new file mode 100644 index 0000000000000000000000000000000000000000..9948fc1ea59c351dbb891ad92057b3ea151f2b58 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/PyColorize.py @@ -0,0 +1,522 @@ +import keyword +import os +import sys +import token +import tokenize +import warnings +from io import StringIO +from typing import TypeAlias + +import pygments +from pygments.formatters.terminal256 import Terminal256Formatter +from pygments.style import Style +from pygments.styles import get_style_by_name +from pygments.token import Token, _TokenType +from functools import cache + +from typing import TypedDict + + +TokenStream: TypeAlias = list[tuple[_TokenType, str]] + + +__all__ = ["Parser", "Theme"] + + +class Symbols(TypedDict): + top_line: str + arrow_body: str + arrow_head: str + + +_default_symbols: Symbols = { + "top_line": "-", + "arrow_body": "-", + "arrow_head": ">", +} + + +class Theme: + name: str + base: str | None + extra_style: dict[_TokenType, str] + symbols: Symbols + + def __init__(self, name, base, extra_style, *, symbols={}): + self.name = name + self.base = base + self.extra_style = extra_style + self.symbols = {**_default_symbols, **symbols} + self._formatter = Terminal256Formatter(style=self.as_pygments_style()) + + @cache + def as_pygments_style(self): + if self.base is not None: + base_styles = get_style_by_name(self.base).styles + else: + base_styles = {} + + class MyStyle(Style): + styles = {**base_styles, **self.extra_style} + + return MyStyle + + def format(self, stream: TokenStream) -> str: + + return pygments.format(stream, self._formatter) + + def make_arrow(self, width: int): + """generate the leading arrow in front of traceback or debugger""" + if width >= 2: + return ( + self.symbols["arrow_body"] * (width - 2) + + self.symbols["arrow_head"] + + " " + ) + elif width == 1: + return self.symbols["arrow_head"] + return "" + + +generate_tokens = tokenize.generate_tokens + + +############################################################################# +### Python Source Parser (does Highlighting) +############################################################################# + +_KEYWORD = token.NT_OFFSET + 1 +_TEXT = token.NT_OFFSET + 2 + +# **************************************************************************** + +_pygment_token_mapping: dict[int, _TokenType] = { + token.NUMBER: Token.Literal.Number, + token.OP: Token.Operator, + token.STRING: Token.Literal.String, + token.COMMENT: Token.Comment, + token.NAME: Token.Name, + token.ERRORTOKEN: Token.Error, + _KEYWORD: Token.Keyword, + _TEXT: Token.Text, +} + +# technically BW is not nocolor, we should have a no-style, style +nocolors_theme = Theme("nocolor", None, {}) + + +linux_theme = Theme( + "linux", + "monokai", + { + Token.Header: "ansibrightred", + Token.LinenoEm: "ansibrightgreen", + Token.Lineno: "ansigreen", + Token.ValEm: "ansibrightblue", + Token.VName: "ansicyan", + Token.Caret: "", + Token.Filename: "ansibrightgreen", + Token.ExcName: "ansibrightred", + Token.Topline: "ansibrightred", + Token.FilenameEm: "ansigreen", + Token.Normal: "", + Token.NormalEm: "ansibrightcyan", + Token.Line: "ansiyellow", + Token.TB.Name: "ansimagenta", + Token.TB.NameEm: "ansibrightmagenta", + Token.Breakpoint: "", + Token.Breakpoint.Enabled: "ansibrightred", + Token.Breakpoint.Disabled: "ansired", + Token.Prompt: "ansibrightgreen", + Token.PromptNum: "ansigreen bold", + Token.OutPrompt: "ansibrightred", + Token.OutPromptNum: "ansired bold", + }, +) + +neutral_pygments_equiv = { + Token.Header: "ansired", + Token.LinenoEm: "ansigreen", + Token.Lineno: "ansibrightgreen", + Token.ValEm: "ansiblue", + Token.VName: "ansicyan", + Token.Caret: "", + Token.Filename: "ansibrightgreen", + Token.FilenameEm: "ansigreen", + Token.ExcName: "ansired", + Token.Topline: "ansired", + Token.Normal: "", + Token.NormalEm: "ansicyan", + Token.Line: "ansired", + Token.TB.Name: "ansibrightmagenta", + Token.TB.NameEm: "ansimagenta", + Token.Breakpoint: "", + Token.Breakpoint.Enabled: "ansibrightred", + Token.Breakpoint.Disabled: "ansired", + ## specific override of pygments defaults for visibility + Token.Number: "ansigreen", + Token.Operator: "noinherit", + Token.String: "ansiyellow", + Token.Name.Function: "ansiblue", + Token.Name.Class: "bold ansiblue", + Token.Name.Namespace: "bold ansiblue", + Token.Name.Variable.Magic: "ansiblue", + Token.Prompt: "ansigreen", + Token.OutPrompt: "ansired", +} + + +neutral_pygments_nt = { + **neutral_pygments_equiv, + Token.PromptNum: "ansigreen bold", + Token.OutPromptNum: "ansired bold", +} +neutral_pygments_posix = { + **neutral_pygments_equiv, + Token.PromptNum: "ansibrightgreen bold", + Token.OutPromptNum: "ansibrightred bold", +} + + +neutral_nt = Theme("neutral:nt", "default", neutral_pygments_nt) +neutral_posix = Theme("neutral:posix", "default", neutral_pygments_posix) + + +# Hack: the 'neutral' colours are not very visible on a dark background on +# Windows. Since Windows command prompts have a dark background by default, and +# relatively few users are likely to alter that, we will use the 'Linux' colours, +# designed for a dark background, as the default on Windows. Changing it here +# avoids affecting the prompt colours rendered by prompt_toolkit, where the +# neutral defaults do work OK. +if os.name == "nt": + neutral_theme = neutral_nt +else: + neutral_theme = neutral_posix + + +lightbg_theme = Theme( + "lightbg", + "pastie", + { + Token.Header: "ansired", + Token.LinenoEm: "ansigreen", + Token.Lineno: "ansibrightgreen", + Token.ValEm: "ansiblue", + Token.VName: "ansicyan", + Token.Caret: "", + Token.Filename: "ansigreen", + Token.FilenameEm: "ansibrightgreen", + Token.ExcName: "ansired", + Token.Topline: "ansired", + Token.Normal: "", + Token.NormalEm: "ansicyan", + Token.Line: "ansired", + Token.TB.Name: "ansibrightmagenta", + Token.TB.NameEm: "ansimagenta", + Token.Breakpoint: "", + Token.Breakpoint.Enabled: "ansibrightred", + Token.Breakpoint.Disabled: "ansired", + Token.Prompt: "ansibrightblue", + Token.PromptNum: "ansiblue bold", + Token.OutPrompt: "ansibrightred", + Token.OutPromptNum: "ansired bold", + }, +) + +PRIDE_RED = "#E40303" +PRIDE_ORANGE = "#FF8C00" +PRIDE_YELLOW = "#FFED00" +PRIDE_GREEN = "#008026" +PRIDE_INDIGO = "#004CFF" +PRIDE_VIOLET = "#732982" +pride_theme = Theme( + "pride", + "pastie", + { + Token.Header: PRIDE_INDIGO, + Token.LinenoEm: f"{PRIDE_GREEN} italic", + Token.Lineno: f"{PRIDE_GREEN} bold", + Token.ValEm: f"{PRIDE_INDIGO} italic", + Token.VName: "ansicyan", + Token.Caret: "", + Token.Filename: f"{PRIDE_YELLOW}", + Token.FilenameEm: f"bg:{PRIDE_VIOLET}", + Token.ExcName: f"{PRIDE_ORANGE}", + Token.Topline: f"{PRIDE_RED}", + Token.Normal: "", + Token.NormalEm: "bold", + Token.Line: "ansired", + Token.TB.Name: "ansibrightmagenta", + Token.TB.NameEm: "ansimagenta", + Token.Breakpoint: "", + Token.Breakpoint.Enabled: "ansibrightred", + Token.Breakpoint.Disabled: "ansired", + Token.Prompt: "ansibrightblue", + Token.Prompt.Continuation.L1: f"ansiwhite bg:{PRIDE_RED}", + Token.Prompt.Continuation.L2: f"ansiwhite bg:{PRIDE_ORANGE}", + Token.Prompt.Continuation.L3: f"ansiblack bg:{PRIDE_YELLOW}", + Token.Prompt.Continuation.L4: f"ansiwhite bg:{PRIDE_GREEN}", + Token.Prompt.Continuation.L5: f"ansiwhite bg:{PRIDE_INDIGO}", + Token.Prompt.Continuation.L6: f"ansiwhite bg:{PRIDE_VIOLET}", + Token.PromptNum: "ansiblue bold", + Token.OutPrompt: "ansibrightred", + Token.OutPromptNum: "ansired bold", + }, + symbols={"arrow_body": "\u2500", "arrow_head": "\u25b6", "top_line": "\u2500"}, +) + + +C1 = "#D52D00" +C2 = "#EF7627" +C3 = "#FF9A56" +White = "#FFFFFF" +C5 = "#D162A4" +C6 = "#B55690" +C7 = "#A30262" + +pl = { + # Token.Whitespace: "#bbbbbb", + Token.Comment: "#888888", + Token.String: C5, + Token.String.Escape: C1, + Token.Keyword: f"italic {C2}", + Token.Name.Class: C2, + Token.Name.Exception: C1, + Token.Name.Builtin: C3, + Token.Name.Variable: C6, + Token.Name.Constant: C7, + Token.Name.Decorator: C2, + Token.Number: C7, + Token.Generic.Deleted: f"bg:{C1} #000000", + Token.Generic.Emph: "italic", + Token.Generic.Strong: "bold", + Token.Generic.EmphStrong: "bold italic", +} + +pridel_theme = Theme( + "pride:l", + None, + { + Token.Header: C3, + Token.LinenoEm: C3, + Token.Lineno: C2, + Token.ValEm: C2, + Token.VName: C2, + Token.Caret: "", + Token.Filename: C2, + Token.FilenameEm: C3, + Token.ExcName: C1, + Token.Topline: C1, + Token.Normal: "", + Token.NormalEm: "bold", + Token.Line: C2, + Token.TB.Name: C6, + Token.TB.NameEm: C7, + Token.Breakpoint: "", + Token.Breakpoint.Enabled: C1, + Token.Breakpoint.Disabled: C7, + Token.Prompt: C1, + Token.PromptNum: C2, + Token.Prompt.Continuation: C7, + Token.Prompt.Continuation.L1: C2, + Token.Prompt.Continuation.L2: C3, + Token.Prompt.Continuation.L3: White, + Token.Prompt.Continuation.L4: C5, + Token.Prompt.Continuation.L5: C6, + Token.Prompt.Continuation.L6: C7, + Token.OutPrompt: C6, + Token.OutPromptNum: C5, + **pl, + }, + symbols={"arrow_body": "\u2500", "arrow_head": "\u25b6", "top_line": "\u2500"}, +) + +theme_table: dict[str, Theme] = { + "nocolor": nocolors_theme, + "linux": linux_theme, + "neutral": neutral_theme, + "neutral:nt": neutral_nt, + "neutral:posix": neutral_posix, + "lightbg": lightbg_theme, + "pride": pride_theme, + "pride:l": pridel_theme, +} + + +class Parser: + """Format colored Python source.""" + + _theme_name: str + + def __init__(self, out=sys.stdout, *, theme_name: str = None): + """Create a parser with a specified color table and output channel. + + Call format() to process code. + """ + + assert theme_name is not None + + self.out = out + self.pos = None + self.lines = None + self.raw = None + if theme_name is not None: + if theme_name in ["Linux", "LightBG", "Neutral", "NoColor"]: + warnings.warn( + f"Theme names and color schemes are lowercase in IPython 9.0 use {theme_name.lower()} instead", + DeprecationWarning, + stacklevel=2, + ) + theme_name = theme_name.lower() + if not theme_name: + self.theme_name = "nocolor" + else: + self.theme_name = theme_name + + @property + def theme_name(self): + return self._theme_name + + @theme_name.setter + def theme_name(self, value): + assert value == value.lower() + self._theme_name = value + + @property + def style(self): + assert False + return self._theme_name + + @style.setter + def set(self, val): + assert False + assert val == val.lower() + self._theme_name = val + + def format(self, raw, out=None): + return self.format2(raw, out)[0] + + def format2(self, raw, out=None): + """Parse and send the colored source. + + If out is not specified, the defaults (given to constructor) are used. + + out should be a file-type object. Optionally, out can be given as the + string 'str' and the parser will automatically return the output in a + string.""" + + string_output = 0 + if out == "str" or self.out == "str" or isinstance(self.out, StringIO): + # XXX - I don't really like this state handling logic, but at this + # point I don't want to make major changes, so adding the + # isinstance() check is the simplest I can do to ensure correct + # behavior. + out_old = self.out + self.out = StringIO() + string_output = 1 + elif out is not None: + self.out = out + else: + raise ValueError( + '`out` or `self.out` should be file-like or the value `"str"`' + ) + + # Fast return of the unmodified input for nocolor scheme + # TODO: + if self.theme_name == "nocolor": + error = False + self.out.write(raw) + if string_output: + return raw, error + return None, error + + # local shorthands + + # Remove trailing whitespace and normalize tabs + self.raw = raw.expandtabs().rstrip() + + # store line offsets in self.lines + self.lines = [0, 0] + pos = 0 + raw_find = self.raw.find + lines_append = self.lines.append + while True: + pos = raw_find("\n", pos) + 1 + if not pos: + break + lines_append(pos) + lines_append(len(self.raw)) + + # parse the source and write it + self.pos = 0 + text = StringIO(self.raw) + + error = False + try: + for atoken in generate_tokens(text.readline): + self(*atoken) + except tokenize.TokenError as ex: + msg = ex.args[0] + line = ex.args[1][0] + self.out.write( + theme_table[self.theme_name].format( + [ + (Token, "\n\n"), + ( + Token.Error, + f"*** ERROR: {msg}{self.raw[self.lines[line] :]}", + ), + (Token, "\n"), + ] + ) + ) + error = True + self.out.write( + theme_table[self.theme_name].format( + [ + (Token, "\n"), + ] + ) + ) + + if string_output: + output = self.out.getvalue() + self.out = out_old + return (output, error) + return (None, error) + + def _inner_call_(self, toktype, toktext, start_pos): + """like call but write to a temporary buffer""" + srow, scol = start_pos + + # calculate new positions + oldpos = self.pos + newpos = self.lines[srow] + scol + self.pos = newpos + len(toktext) + + # send the original whitespace, if needed + if newpos > oldpos: + acc = self.raw[oldpos:newpos] + else: + acc = "" + + # skip indenting tokens + if toktype in [token.INDENT, token.DEDENT]: + self.pos = newpos + return acc + + # map token type to a color group + if token.LPAR <= toktype <= token.OP: + toktype = token.OP + elif toktype == token.NAME and keyword.iskeyword(toktext): + toktype = _KEYWORD + pyg_tok_type = _pygment_token_mapping.get(toktype, Token.Text) + + # send text, pygments should take care of splitting on newline and resending + # the correct self.colors after the new line, which is necessary for pagers + acc += theme_table[self.theme_name].format([(pyg_tok_type, toktext)]) + return acc + + def __call__(self, toktype, toktext, start_pos, end_pos, line): + """Token handler, with syntax highlighting.""" + self.out.write(self._inner_call_(toktype, toktext, start_pos)) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/capture.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/capture.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62f41f569131c8c9d4c88b3dadc085c3107c7718 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/capture.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/data.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/data.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..424cbf7c417faf49c4c9c30d6b3ff75c28106bee Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/data.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/dir2.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/dir2.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f723ef410dc5b925397c35d04537faec5dde225e Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/dir2.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/encoding.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/encoding.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1dd3755c802ac63069987aa1b877b7c66cb8d036 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/encoding.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/generics.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/generics.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84f87d3581e53dc1afd653a17acd5655db811418 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/generics.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/io.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/io.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af5ea6f1b94bd0b84dc2be51400399fa701af4c3 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/io.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/py3compat.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/py3compat.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d097f75fede2d93e74a1a118580fb919a81dfc36 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/py3compat.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/sysinfo.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/sysinfo.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb565d46c926f00c6346b45f9e0b405ca0553f21 Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/sysinfo.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/tokenutil.cpython-313.pyc b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/tokenutil.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6dfbc0c4c542b693e08e8b4d3f5cfabb3ff14b5e Binary files /dev/null and b/temp_venv/lib/python3.13/site-packages/IPython/utils/__pycache__/tokenutil.cpython-313.pyc differ diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/_process_common.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/_process_common.py new file mode 100644 index 0000000000000000000000000000000000000000..1014c7c2d738ed17d9da3161580fb737513ecda1 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/_process_common.py @@ -0,0 +1,219 @@ +"""Common utilities for the various process_* implementations. + +This file is only meant to be imported by the platform-specific implementations +of subprocess utilities, and it contains tools that are common to all of them. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2010-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 os +import shlex +import subprocess +import sys +from typing import IO, Any, Callable, List, Union + +from IPython.utils import py3compat + +#----------------------------------------------------------------------------- +# Function definitions +#----------------------------------------------------------------------------- + +def read_no_interrupt(stream: IO[Any]) -> bytes: + """Read from a pipe ignoring EINTR errors. + + This is necessary because when reading from pipes with GUI event loops + running in the background, often interrupts are raised that stop the + command from completing.""" + import errno + + try: + return stream.read() + except IOError as err: + if err.errno != errno.EINTR: + raise + + +def process_handler( + cmd: Union[str, List[str]], + callback: Callable[[subprocess.Popen], int | str | bytes], + stderr=subprocess.PIPE, +) -> int | str | bytes: + """Open a command in a shell subprocess and execute a callback. + + This function provides common scaffolding for creating subprocess.Popen() + calls. It creates a Popen object and then calls the callback with it. + + Parameters + ---------- + cmd : str or list + A command to be executed by the system, using :class:`subprocess.Popen`. + If a string is passed, it will be run in the system shell. If a list is + passed, it will be used directly as arguments. + callback : callable + A one-argument function that will be called with the Popen object. + stderr : file descriptor number, optional + By default this is set to ``subprocess.PIPE``, but you can also pass the + value ``subprocess.STDOUT`` to force the subprocess' stderr to go into + the same file descriptor as its stdout. This is useful to read stdout + and stderr combined in the order they are generated. + + Returns + ------- + The return value of the provided callback is returned. + """ + sys.stdout.flush() + sys.stderr.flush() + # On win32, close_fds can't be true when using pipes for stdin/out/err + if sys.platform == "win32" and stderr != subprocess.PIPE: + close_fds = False + else: + close_fds = True + # Determine if cmd should be run with system shell. + shell = isinstance(cmd, str) + # On POSIX systems run shell commands with user-preferred shell. + executable = None + if shell and os.name == 'posix' and 'SHELL' in os.environ: + executable = os.environ['SHELL'] + p = subprocess.Popen(cmd, shell=shell, + executable=executable, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=stderr, + close_fds=close_fds) + + try: + out = callback(p) + except KeyboardInterrupt: + print('^C') + sys.stdout.flush() + sys.stderr.flush() + out = None + finally: + # Make really sure that we don't leave processes behind, in case the + # call above raises an exception + # We start by assuming the subprocess finished (to avoid NameErrors + # later depending on the path taken) + if p.returncode is None: + try: + p.terminate() + p.poll() + except OSError: + pass + # One last try on our way out + if p.returncode is None: + try: + p.kill() + except OSError: + pass + + return out + + +def getoutput(cmd): + """Run a command and return its stdout/stderr as a string. + + Parameters + ---------- + cmd : str or list + A command to be executed in the system shell. + + Returns + ------- + output : str + A string containing the combination of stdout and stderr from the + subprocess, in whatever order the subprocess originally wrote to its + file descriptors (so the order of the information in this string is the + correct order as would be seen if running the command in a terminal). + """ + out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT) + if out is None: + return '' + assert isinstance(out, bytes) + return py3compat.decode(out) + + +def getoutputerror(cmd): + """Return (standard output, standard error) of executing cmd in a shell. + + Accepts the same arguments as os.system(). + + Parameters + ---------- + cmd : str or list + A command to be executed in the system shell. + + Returns + ------- + stdout : str + stderr : str + """ + return get_output_error_code(cmd)[:2] + +def get_output_error_code(cmd): + """Return (standard output, standard error, return code) of executing cmd + in a shell. + + Accepts the same arguments as os.system(). + + Parameters + ---------- + cmd : str or list + A command to be executed in the system shell. + + Returns + ------- + stdout : str + stderr : str + returncode: int + """ + + out_err, p = process_handler(cmd, lambda p: (p.communicate(), p)) + if out_err is None: + return '', '', p.returncode + out, err = out_err + return py3compat.decode(out), py3compat.decode(err), p.returncode + +def arg_split(s, posix=False, strict=True): + """Split a command line's arguments in a shell-like manner. + + This is a modified version of the standard library's shlex.split() + function, but with a default of posix=False for splitting, so that quotes + in inputs are respected. + + if strict=False, then any errors shlex.split would raise will result in the + unparsed remainder being the last element of the list, rather than raising. + This is because we sometimes use arg_split to parse things other than + command-line args. + """ + + lex = shlex.shlex(s, posix=posix) + lex.whitespace_split = True + # Extract tokens, ensuring that things like leaving open quotes + # does not cause this to raise. This is important, because we + # sometimes pass Python source through this (e.g. %timeit f(" ")), + # and it shouldn't raise an exception. + # It may be a bad idea to parse things that are not command-line args + # through this function, but we do, so let's be safe about it. + lex.commenters='' #fix for GH-1269 + tokens = [] + while True: + try: + tokens.append(next(lex)) + except StopIteration: + break + except ValueError: + if strict: + raise + # couldn't parse, get remaining blob as last token + tokens.append(lex.token) + break + + return tokens diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/_process_emscripten.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/_process_emscripten.py new file mode 100644 index 0000000000000000000000000000000000000000..bfc2518462393c53363f39fc42676637f8191e99 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/_process_emscripten.py @@ -0,0 +1,22 @@ +"""Emscripten-specific implementation of process utilities. + +This file is only meant to be imported by process.py, not by end-users. +""" + +from ._process_common import arg_split + + +def system(cmd): + raise OSError("Not available") + + +def getoutput(cmd): + raise OSError("Not available") + + +def check_pid(cmd): + raise OSError("Not available") + + +# `arg_split` is still used by magics regardless of whether we are on a posix/windows/emscipten +__all__ = ["system", "getoutput", "check_pid", "arg_split"] diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/_process_win32.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/_process_win32.py new file mode 100644 index 0000000000000000000000000000000000000000..0600b1ee14c1edfccea4b8a1c79f3b4d159e559b --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/_process_win32.py @@ -0,0 +1,216 @@ +"""Windows-specific implementation of process utilities. + +This file is only meant to be imported by process.py, not by end-users. +""" + +import ctypes +import os +import subprocess +import sys +import time +from ctypes import POINTER, c_int +from ctypes.wintypes import HLOCAL, LPCWSTR +from subprocess import STDOUT +from threading import Thread +from types import TracebackType +from typing import List, Optional + +from . import py3compat +from ._process_common import arg_split as py_arg_split + +from ._process_common import process_handler, read_no_interrupt +from .encoding import DEFAULT_ENCODING + + +class AvoidUNCPath: + """A context manager to protect command execution from UNC paths. + + In the Win32 API, commands can't be invoked with the cwd being a UNC path. + This context manager temporarily changes directory to the 'C:' drive on + entering, and restores the original working directory on exit. + + The context manager returns the starting working directory *if* it made a + change and None otherwise, so that users can apply the necessary adjustment + to their system calls in the event of a change. + + Examples + -------- + :: + cmd = 'dir' + with AvoidUNCPath() as path: + if path is not None: + cmd = '"pushd %s &&"%s' % (path, cmd) + os.system(cmd) + """ + + def __enter__(self) -> Optional[str]: + self.path = os.getcwd() + self.is_unc_path = self.path.startswith(r"\\") + if self.is_unc_path: + # change to c drive (as cmd.exe cannot handle UNC addresses) + os.chdir("C:") + return self.path + else: + # We return None to signal that there was no change in the working + # directory + return None + + def __exit__( + self, + exc_type: Optional[type[BaseException]], + exc_value: Optional[BaseException], + traceback: TracebackType, + ) -> None: + if self.is_unc_path: + os.chdir(self.path) + + +def _system_body(p: subprocess.Popen) -> int: + """Callback for _system.""" + enc = DEFAULT_ENCODING + + # Dec 2024: in both of these functions, I'm not sure why we .splitlines() + # the bytes and then decode each line individually instead of just decoding + # the whole thing at once. + def stdout_read() -> None: + try: + assert p.stdout is not None + for byte_line in read_no_interrupt(p.stdout).splitlines(): + line = byte_line.decode(enc, "replace") + print(line, file=sys.stdout) + except Exception as e: + print(f"Error reading stdout: {e}", file=sys.stderr) + + def stderr_read() -> None: + try: + assert p.stderr is not None + for byte_line in read_no_interrupt(p.stderr).splitlines(): + line = byte_line.decode(enc, "replace") + print(line, file=sys.stderr) + except Exception as e: + print(f"Error reading stderr: {e}", file=sys.stderr) + + stdout_thread = Thread(target=stdout_read) + stderr_thread = Thread(target=stderr_read) + + stdout_thread.start() + stderr_thread.start() + + # Wait to finish for returncode. Unfortunately, Python has a bug where + # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in + # a loop instead of just doing `return p.wait()` + while True: + result = p.poll() + if result is None: + time.sleep(0.01) + else: + break + + # Join the threads to ensure they complete before returning + stdout_thread.join() + stderr_thread.join() + + return result + + +def system(cmd: str) -> Optional[int]: + """Win32 version of os.system() that works with network shares. + + Note that this implementation returns None, as meant for use in IPython. + + Parameters + ---------- + cmd : str or list + A command to be executed in the system shell. + + Returns + ------- + int : child process' exit code. + """ + # The controller provides interactivity with both + # stdin and stdout + # import _process_win32_controller + # _process_win32_controller.system(cmd) + + with AvoidUNCPath() as path: + if path is not None: + cmd = '"pushd %s &&"%s' % (path, cmd) + res = process_handler(cmd, _system_body) + assert isinstance(res, int | type(None)) + return res + return None + + +def getoutput(cmd: str) -> str: + """Return standard output of executing cmd in a shell. + + Accepts the same arguments as os.system(). + + Parameters + ---------- + cmd : str or list + A command to be executed in the system shell. + + Returns + ------- + stdout : str + """ + + with AvoidUNCPath() as path: + if path is not None: + cmd = '"pushd %s &&"%s' % (path, cmd) + out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT) + + if out is None: + out = b"" + return py3compat.decode(out) + + +try: + windll = ctypes.windll # type: ignore [attr-defined] + CommandLineToArgvW = windll.shell32.CommandLineToArgvW + CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)] + CommandLineToArgvW.restype = POINTER(LPCWSTR) + LocalFree = windll.kernel32.LocalFree + LocalFree.res_type = HLOCAL + LocalFree.arg_types = [HLOCAL] + + def arg_split( + commandline: str, posix: bool = False, strict: bool = True + ) -> List[str]: + """Split a command line's arguments in a shell-like manner. + + This is a special version for windows that use a ctypes call to CommandLineToArgvW + to do the argv splitting. The posix parameter is ignored. + + If strict=False, process_common.arg_split(...strict=False) is used instead. + """ + # CommandLineToArgvW returns path to executable if called with empty string. + if commandline.strip() == "": + return [] + if not strict: + # not really a cl-arg, fallback on _process_common + return py_arg_split(commandline, posix=posix, strict=strict) + argvn = c_int() + result_pointer = CommandLineToArgvW(commandline.lstrip(), ctypes.byref(argvn)) + try: + result_array_type = LPCWSTR * argvn.value + result = [ + arg + for arg in result_array_type.from_address( + ctypes.addressof(result_pointer.contents) + ) + if arg is not None + ] + finally: + # for side effects + _ = LocalFree(result_pointer) + return result +except AttributeError: + arg_split = py_arg_split + + +def check_pid(pid: int) -> bool: + # OpenProcess returns 0 if no such process (of ours) exists + # positive int otherwise + return bool(windll.kernel32.OpenProcess(1, 0, pid)) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/contexts.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/contexts.py new file mode 100644 index 0000000000000000000000000000000000000000..24e54b3e934d25846b4c1a45b3e67d2944e0e045 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/contexts.py @@ -0,0 +1,60 @@ +# encoding: utf-8 +"""Miscellaneous context managers.""" + +import warnings + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +class preserve_keys: + """Preserve a set of keys in a dictionary. + + Upon entering the context manager the current values of the keys + will be saved. Upon exiting, the dictionary will be updated to + restore the original value of the preserved keys. Preserved keys + which did not exist when entering the context manager will be + deleted. + + Examples + -------- + + >>> d = {'a': 1, 'b': 2, 'c': 3} + >>> with preserve_keys(d, 'b', 'c', 'd'): + ... del d['a'] + ... del d['b'] # will be reset to 2 + ... d['c'] = None # will be reset to 3 + ... d['d'] = 4 # will be deleted + ... d['e'] = 5 + ... print(sorted(d.items())) + ... + [('c', None), ('d', 4), ('e', 5)] + >>> print(sorted(d.items())) + [('b', 2), ('c', 3), ('e', 5)] + """ + + def __init__(self, dictionary, *keys): + self.dictionary = dictionary + self.keys = keys + + def __enter__(self): + # Actions to perform upon exiting. + to_delete = [] + to_update = {} + + d = self.dictionary + for k in self.keys: + if k in d: + to_update[k] = d[k] + else: + to_delete.append(k) + + self.to_delete = to_delete + self.to_update = to_update + + def __exit__(self, *exc_info): + d = self.dictionary + + for k in self.to_delete: + d.pop(k, None) + d.update(self.to_update) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/data.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/data.py new file mode 100644 index 0000000000000000000000000000000000000000..433c90916c229a34faf030c01b0f349fdc24b155 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/data.py @@ -0,0 +1,30 @@ +# encoding: utf-8 +"""Utilities for working with data structures like lists, dicts and tuples. +""" + +#----------------------------------------------------------------------------- +# 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. +#----------------------------------------------------------------------------- + + +def uniq_stable(elems): + """uniq_stable(elems) -> list + + Return from an iterable, a list of all the unique elements in the input, + but maintaining the order in which they first appear. + + Note: All elements in the input must be hashable for this routine + to work, as it internally uses a set for efficiency reasons. + """ + seen = set() + return [x for x in elems if x not in seen and not seen.add(x)] + + +def chop(seq, size): + """Chop a sequence into chunks of the given size.""" + return [seq[i:i+size] for i in range(0,len(seq),size)] + + diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/decorators.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..bc7589cd35ceb47c41c9347cc75d9675af9d4286 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/decorators.py @@ -0,0 +1,83 @@ +# encoding: utf-8 +"""Decorators that don't go anywhere else. + +This module contains misc. decorators that don't really go with another module +in :mod:`IPython.utils`. Before putting something here please see if it should +go into another topical module in :mod:`IPython.utils`. +""" + +#----------------------------------------------------------------------------- +# 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 +#----------------------------------------------------------------------------- +from typing import Sequence + +from IPython.utils.docs import GENERATING_DOCUMENTATION + + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +def flag_calls(func): + """Wrap a function to detect and flag when it gets called. + + This is a decorator which takes a function and wraps it in a function with + a 'called' attribute. wrapper.called is initialized to False. + + The wrapper.called attribute is set to False right before each call to the + wrapped function, so if the call fails it remains False. After the call + completes, wrapper.called is set to True and the output is returned. + + Testing for truth in wrapper.called allows you to determine if a call to + func() was attempted and succeeded.""" + + # don't wrap twice + if hasattr(func, 'called'): + return func + + def wrapper(*args,**kw): + wrapper.called = False + out = func(*args,**kw) + wrapper.called = True + return out + + wrapper.called = False + wrapper.__doc__ = func.__doc__ + return wrapper + + +def undoc(func): + """Mark a function or class as undocumented. + + This is found by inspecting the AST, so for now it must be used directly + as @undoc, not as e.g. @decorators.undoc + """ + return func + + +def sphinx_options( + show_inheritance: bool = True, + show_inherited_members: bool = False, + exclude_inherited_from: Sequence[str] = tuple(), +): + """Set sphinx options""" + + def wrapper(func): + if not GENERATING_DOCUMENTATION: + return func + + func._sphinx_options = dict( + show_inheritance=show_inheritance, + show_inherited_members=show_inherited_members, + exclude_inherited_from=exclude_inherited_from, + ) + return func + + return wrapper diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/generics.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/generics.py new file mode 100644 index 0000000000000000000000000000000000000000..17a905576d5d569090d98d0f9dce1caa885a0b16 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/generics.py @@ -0,0 +1,28 @@ +# encoding: utf-8 +"""Generic functions for extending IPython.""" + +from IPython.core.error import TryNext +from functools import singledispatch + + +@singledispatch +def inspect_object(obj): + """Called when you do obj?""" + raise TryNext + + +@singledispatch +def complete_object(obj, prev_completions): + """Custom completer dispatching for python objects. + + Parameters + ---------- + obj : object + The object to complete. + prev_completions : list + List of attributes discovered so far. + This should return the list of attributes in obj. If you only wish to + add to the attributes already discovered normally, return + own_attrs + prev_completions. + """ + raise TryNext diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/importstring.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/importstring.py new file mode 100644 index 0000000000000000000000000000000000000000..614607bce3cfa263b2e0537455e4dc00918a8dbd --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/importstring.py @@ -0,0 +1,39 @@ +# encoding: utf-8 +""" +A simple utility to import something by its string name. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +def import_item(name): + """Import and return ``bar`` given the string ``foo.bar``. + + Calling ``bar = import_item("foo.bar")`` is the functional equivalent of + executing the code ``from foo import bar``. + + Parameters + ---------- + name : string + The fully qualified name of the module/package being imported. + + Returns + ------- + mod : module object + The module that was imported. + """ + + parts = name.rsplit(".", 1) + if len(parts) == 2: + # called with 'foo.bar....' + package, obj = parts + module = __import__(package, fromlist=[obj]) + try: + pak = getattr(module, obj) + except AttributeError as e: + raise ImportError("No module named %s" % obj) from e + return pak + else: + # called with un-dotted string + return __import__(parts[0]) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/path.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/path.py new file mode 100644 index 0000000000000000000000000000000000000000..2e93d6976f65ff753f11bdf1a411950bdc275a81 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/path.py @@ -0,0 +1,354 @@ +# encoding: utf-8 +""" +Utilities for path handling. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import os +import sys +import errno +import shutil +import random +import glob +import warnings + +from IPython.utils.process import system + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- +fs_encoding = sys.getfilesystemencoding() + +def _writable_dir(path): + """Whether `path` is a directory, to which the user has write access.""" + return os.path.isdir(path) and os.access(path, os.W_OK) + +if sys.platform == 'win32': + def _get_long_path_name(path): + """Get a long path name (expand ~) on Windows using ctypes. + + Examples + -------- + + >>> get_long_path_name('c:\\\\docume~1') + 'c:\\\\Documents and Settings' + + """ + try: + import ctypes + except ImportError as e: + raise ImportError('you need to have ctypes installed for this to work') from e + _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW + _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, + ctypes.c_uint ] + + buf = ctypes.create_unicode_buffer(260) + rv = _GetLongPathName(path, buf, 260) + if rv == 0 or rv > 260: + return path + else: + return buf.value +else: + def _get_long_path_name(path): + """Dummy no-op.""" + return path + + + +def get_long_path_name(path): + """Expand a path into its long form. + + On Windows this expands any ~ in the paths. On other platforms, it is + a null operation. + """ + return _get_long_path_name(path) + + +def compress_user(path: str) -> str: + """Reverse of :func:`os.path.expanduser`""" + home = os.path.expanduser("~") + if path.startswith(home): + path = "~" + path[len(home):] + return path + +def get_py_filename(name): + """Return a valid python filename in the current directory. + + If the given name is not a file, it adds '.py' and searches again. + Raises IOError with an informative message if the file isn't found. + """ + + name = os.path.expanduser(name) + if os.path.isfile(name): + return name + if not name.endswith(".py"): + py_name = name + ".py" + if os.path.isfile(py_name): + return py_name + raise IOError("File `%r` not found." % name) + + +def filefind(filename: str, path_dirs=None) -> str: + """Find a file by looking through a sequence of paths. + + This iterates through a sequence of paths looking for a file and returns + the full, absolute path of the first occurrence of the file. If no set of + path dirs is given, the filename is tested as is, after running through + :func:`expandvars` and :func:`expanduser`. Thus a simple call:: + + filefind('myfile.txt') + + will find the file in the current working dir, but:: + + filefind('~/myfile.txt') + + Will find the file in the users home directory. This function does not + automatically try any paths, such as the cwd or the user's home directory. + + Parameters + ---------- + filename : str + The filename to look for. + path_dirs : str, None or sequence of str + The sequence of paths to look for the file in. If None, the filename + need to be absolute or be in the cwd. If a string, the string is + put into a sequence and the searched. If a sequence, walk through + each element and join with ``filename``, calling :func:`expandvars` + and :func:`expanduser` before testing for existence. + + Returns + ------- + path : str + returns absolute path to file. + + Raises + ------ + IOError + """ + + # If paths are quoted, abspath gets confused, strip them... + filename = filename.strip('"').strip("'") + # If the input is an absolute path, just check it exists + if os.path.isabs(filename) and os.path.isfile(filename): + return filename + + if path_dirs is None: + path_dirs = ("",) + elif isinstance(path_dirs, str): + path_dirs = (path_dirs,) + + for path in path_dirs: + if path == '.': path = os.getcwd() + testname = expand_path(os.path.join(path, filename)) + if os.path.isfile(testname): + return os.path.abspath(testname) + + raise IOError("File %r does not exist in any of the search paths: %r" % + (filename, path_dirs) ) + + +class HomeDirError(Exception): + pass + + +def get_home_dir(require_writable=False) -> str: + """Return the 'home' directory, as a unicode string. + + Uses os.path.expanduser('~'), and checks for writability. + + See stdlib docs for how this is determined. + For Python <3.8, $HOME is first priority on *ALL* platforms. + For Python >=3.8 on Windows, %HOME% is no longer considered. + + Parameters + ---------- + require_writable : bool [default: False] + if True: + guarantees the return value is a writable directory, otherwise + raises HomeDirError + if False: + The path is resolved, but it is not guaranteed to exist or be writable. + """ + + homedir = os.path.expanduser('~') + # Next line will make things work even when /home/ is a symlink to + # /usr/home as it is on FreeBSD, for example + homedir = os.path.realpath(homedir) + + if not _writable_dir(homedir) and os.name == 'nt': + # expanduser failed, use the registry to get the 'My Documents' folder. + try: + import winreg as wreg + with wreg.OpenKey( + wreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) as key: + homedir = wreg.QueryValueEx(key,'Personal')[0] + except: + pass + + if (not require_writable) or _writable_dir(homedir): + assert isinstance(homedir, str), "Homedir should be unicode not bytes" + return homedir + else: + raise HomeDirError('%s is not a writable dir, ' + 'set $HOME environment variable to override' % homedir) + +def get_xdg_dir(): + """Return the XDG_CONFIG_HOME, if it is defined and exists, else None. + + This is only for non-OS X posix (Linux,Unix,etc.) systems. + """ + + env = os.environ + + if os.name == "posix": + # Linux, Unix, AIX, etc. + # use ~/.config if empty OR not set + xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config') + if xdg and _writable_dir(xdg): + assert isinstance(xdg, str) + return xdg + + return None + + +def get_xdg_cache_dir(): + """Return the XDG_CACHE_HOME, if it is defined and exists, else None. + + This is only for non-OS X posix (Linux,Unix,etc.) systems. + """ + + env = os.environ + + if os.name == "posix": + # Linux, Unix, AIX, etc. + # use ~/.cache if empty OR not set + xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache') + if xdg and _writable_dir(xdg): + assert isinstance(xdg, str) + return xdg + + return None + + +def expand_path(s): + """Expand $VARS and ~names in a string, like a shell + + :Examples: + + In [2]: os.environ['FOO']='test' + + In [3]: expand_path('variable FOO is $FOO') + Out[3]: 'variable FOO is test' + """ + # This is a pretty subtle hack. When expand user is given a UNC path + # on Windows (\\server\share$\%username%), os.path.expandvars, removes + # the $ to get (\\server\share\%username%). I think it considered $ + # alone an empty var. But, we need the $ to remains there (it indicates + # a hidden share). + if os.name=='nt': + s = s.replace('$\\', 'IPYTHON_TEMP') + s = os.path.expandvars(os.path.expanduser(s)) + if os.name=='nt': + s = s.replace('IPYTHON_TEMP', '$\\') + return s + + +def unescape_glob(string): + """Unescape glob pattern in `string`.""" + def unescape(s): + for pattern in '*[]!?': + s = s.replace(r'\{0}'.format(pattern), pattern) + return s + return '\\'.join(map(unescape, string.split('\\\\'))) + + +def shellglob(args): + """ + Do glob expansion for each element in `args` and return a flattened list. + + Unmatched glob pattern will remain as-is in the returned list. + + """ + expanded = [] + # Do not unescape backslash in Windows as it is interpreted as + # path separator: + unescape = unescape_glob if sys.platform != 'win32' else lambda x: x + for a in args: + expanded.extend(glob.glob(a) or [unescape(a)]) + return expanded + +ENOLINK = 1998 + +def link(src, dst): + """Hard links ``src`` to ``dst``, returning 0 or errno. + + Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't + supported by the operating system. + """ + + if not hasattr(os, "link"): + return ENOLINK + link_errno = 0 + try: + os.link(src, dst) + except OSError as e: + link_errno = e.errno + return link_errno + + +def link_or_copy(src, dst): + """Attempts to hardlink ``src`` to ``dst``, copying if the link fails. + + Attempts to maintain the semantics of ``shutil.copy``. + + Because ``os.link`` does not overwrite files, a unique temporary file + will be used if the target already exists, then that file will be moved + into place. + """ + + if os.path.isdir(dst): + dst = os.path.join(dst, os.path.basename(src)) + + link_errno = link(src, dst) + if link_errno == errno.EEXIST: + if os.stat(src).st_ino == os.stat(dst).st_ino: + # dst is already a hard link to the correct file, so we don't need + # to do anything else. If we try to link and rename the file + # anyway, we get duplicate files - see http://bugs.python.org/issue21876 + return + + new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), ) + try: + link_or_copy(src, new_dst) + except: + try: + os.remove(new_dst) + except OSError: + pass + raise + os.rename(new_dst, dst) + elif link_errno != 0: + # Either link isn't supported, or the filesystem doesn't support + # linking, or 'src' and 'dst' are on different filesystems. + shutil.copy(src, dst) + +def ensure_dir_exists(path, mode=0o755): + """ensure that a directory exists + + If it doesn't exist, try to create it and protect against a race condition + if another process is doing the same. + + The default permissions are 755, which differ from os.makedirs default of 777. + """ + if not os.path.exists(path): + try: + os.makedirs(path, mode=mode) + except OSError as e: + if e.errno != errno.EEXIST: + raise + elif not os.path.isdir(path): + raise IOError("%r exists but is not a directory" % path) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/process.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/process.py new file mode 100644 index 0000000000000000000000000000000000000000..f50cf9ba223a57205a872a1cbb24dc125ad815f0 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/process.py @@ -0,0 +1,71 @@ +# encoding: utf-8 +""" +Utilities for working with external processes. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +import os +import shutil +import sys + +if sys.platform == 'win32': + from ._process_win32 import system, getoutput, arg_split, check_pid +elif sys.platform == 'cli': + from ._process_cli import system, getoutput, arg_split, check_pid +elif sys.platform == "emscripten": + from ._process_emscripten import system, getoutput, arg_split, check_pid +else: + from ._process_posix import system, getoutput, arg_split, check_pid + +from ._process_common import getoutputerror, get_output_error_code, process_handler + + +class FindCmdError(Exception): + pass + + +def find_cmd(cmd): + """Find absolute path to executable cmd in a cross platform manner. + + This function tries to determine the full path to a command line program + using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the + time it will use the version that is first on the users `PATH`. + + Warning, don't use this to find IPython command line programs as there + is a risk you will find the wrong one. Instead find those using the + following code and looking for the application itself:: + + import sys + argv = [sys.executable, '-m', 'IPython'] + + Parameters + ---------- + cmd : str + The command line program to look for. + """ + path = shutil.which(cmd) + if path is None: + raise FindCmdError('command could not be found: %s' % cmd) + return path + + +def abbrev_cwd(): + """ Return abbreviated version of cwd, e.g. d:mydir """ + cwd = os.getcwd().replace('\\','/') + drivepart = '' + tail = cwd + if sys.platform == 'win32': + if len(cwd) < 4: + return cwd + drivepart,tail = os.path.splitdrive(cwd) + + + parts = tail.split('/') + if len(parts) > 2: + tail = '/'.join(parts[-2:]) + + return (drivepart + ( + cwd == '/' and '/' or tail)) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/sentinel.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/sentinel.py new file mode 100644 index 0000000000000000000000000000000000000000..2ab06e4a5df8d8f47c363d73426dc41f88fdee40 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/sentinel.py @@ -0,0 +1,15 @@ +"""Sentinel class for constants with useful reprs""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +class Sentinel: + def __init__(self, name, module, docstring=None): + self.name = name + self.module = module + if docstring: + self.__doc__ = docstring + + def __repr__(self): + return str(self.module) + "." + self.name diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/sysinfo.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/sysinfo.py new file mode 100644 index 0000000000000000000000000000000000000000..8240ea54e3a77a8f889a99a20c1160f4c522ca9c --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/sysinfo.py @@ -0,0 +1,120 @@ +# encoding: utf-8 +""" +Utilities for getting information about IPython and the system it's running in. +""" + +#----------------------------------------------------------------------------- +# 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 os +import platform +import pprint +import sys +import subprocess + +from pathlib import Path + +from IPython.core import release +from IPython.utils import _sysinfo, encoding + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +def pkg_commit_hash(pkg_path: str) -> tuple[str, str]: + """Get short form of commit hash given directory `pkg_path` + + We get the commit hash from (in order of preference): + + * IPython.utils._sysinfo.commit + * git output, if we are in a git repository + + If these fail, we return a not-found placeholder tuple + + Parameters + ---------- + pkg_path : str + directory containing package + only used for getting commit from active repo + + Returns + ------- + hash_from : str + Where we got the hash from - description + hash_str : str + short form of hash + """ + # Try and get commit from written commit text file + if _sysinfo.commit: + return "installation", _sysinfo.commit + + # maybe we are in a repository + proc = subprocess.Popen('git rev-parse --short HEAD'.split(' '), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=pkg_path) + repo_commit, _ = proc.communicate() + if repo_commit: + return 'repository', repo_commit.strip().decode('ascii') + return '(none found)', '' + + +def pkg_info(pkg_path: str) -> dict: + """Return dict describing the context of this package + + Parameters + ---------- + pkg_path : str + path containing __init__.py for package + + Returns + ------- + context : dict + with named parameters of interest + """ + src, hsh = pkg_commit_hash(pkg_path) + return dict( + ipython_version=release.version, + ipython_path=pkg_path, + commit_source=src, + commit_hash=hsh, + sys_version=sys.version, + sys_executable=sys.executable, + sys_platform=sys.platform, + platform=platform.platform(), + os_name=os.name, + default_encoding=encoding.DEFAULT_ENCODING, + ) + +def get_sys_info() -> dict: + """Return useful information about IPython and the system, as a dict.""" + path = Path(__file__, "..").resolve().parent + return pkg_info(str(path)) + +def sys_info() -> str: + """Return useful information about IPython and the system, as a string. + + Examples + -------- + :: + + In [2]: print(sys_info()) + {'commit_hash': '144fdae', # random + 'commit_source': 'repository', + 'ipython_path': '/home/fperez/usr/lib/python2.6/site-packages/IPython', + 'ipython_version': '0.11.dev', + 'os_name': 'posix', + 'platform': 'Linux-2.6.35-22-generic-i686-with-Ubuntu-10.10-maverick', + 'sys_executable': '/usr/bin/python', + 'sys_platform': 'linux2', + 'sys_version': '2.6.6 (r266:84292, Sep 15 2010, 15:52:39) \\n[GCC 4.4.5]'} + """ + return pprint.pformat(get_sys_info()) diff --git a/temp_venv/lib/python3.13/site-packages/IPython/utils/syspathcontext.py b/temp_venv/lib/python3.13/site-packages/IPython/utils/syspathcontext.py new file mode 100644 index 0000000000000000000000000000000000000000..79484016392710f94084e0fca9ef1c8ea815bdc0 --- /dev/null +++ b/temp_venv/lib/python3.13/site-packages/IPython/utils/syspathcontext.py @@ -0,0 +1,25 @@ +import sys +import warnings + + +class prepended_to_syspath: + """A context for prepending a directory to sys.path for a second.""" + + def __init__(self, dir): + self.dir = dir + + def __enter__(self): + if self.dir not in sys.path: + sys.path.insert(0, self.dir) + self.added = True + else: + self.added = False + + def __exit__(self, type, value, traceback): + if self.added: + try: + sys.path.remove(self.dir) + except ValueError: + pass + # Returning False causes any exceptions to be re-raised. + return False