ZhengyangZhang commited on
Commit
a6087d6
·
verified ·
1 Parent(s): a239196

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/IPython/__init__.py +144 -0
  2. lib/python3.12/site-packages/IPython/__main__.py +14 -0
  3. lib/python3.12/site-packages/IPython/core/compilerop.py +192 -0
  4. lib/python3.12/site-packages/IPython/core/crashhandler.py +244 -0
  5. lib/python3.12/site-packages/IPython/core/displaypub.py +205 -0
  6. lib/python3.12/site-packages/IPython/core/getipython.py +23 -0
  7. lib/python3.12/site-packages/IPython/core/guarded_eval.py +1656 -0
  8. lib/python3.12/site-packages/IPython/core/magic.py +786 -0
  9. lib/python3.12/site-packages/IPython/core/release.py +45 -0
  10. lib/python3.12/site-packages/IPython/display.py +41 -0
  11. lib/python3.12/site-packages/IPython/paths.py +122 -0
  12. lib/python3.12/site-packages/IPython/py.typed +0 -0
  13. lib/python3.12/site-packages/IPython/sphinxext/__init__.py +0 -0
  14. lib/python3.12/site-packages/IPython/sphinxext/__pycache__/__init__.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/IPython/sphinxext/__pycache__/custom_doctests.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/IPython/sphinxext/__pycache__/ipython_console_highlighting.cpython-312.pyc +0 -0
  17. lib/python3.12/site-packages/IPython/sphinxext/__pycache__/ipython_directive.cpython-312.pyc +0 -0
  18. lib/python3.12/site-packages/IPython/sphinxext/custom_doctests.py +155 -0
  19. lib/python3.12/site-packages/IPython/sphinxext/ipython_console_highlighting.py +28 -0
  20. lib/python3.12/site-packages/IPython/sphinxext/ipython_directive.py +1278 -0
  21. lib/python3.12/site-packages/IPython/testing/__init__.py +20 -0
  22. lib/python3.12/site-packages/IPython/testing/__pycache__/__init__.cpython-312.pyc +0 -0
  23. lib/python3.12/site-packages/IPython/testing/__pycache__/decorators.cpython-312.pyc +0 -0
  24. lib/python3.12/site-packages/IPython/testing/__pycache__/globalipapp.cpython-312.pyc +0 -0
  25. lib/python3.12/site-packages/IPython/testing/__pycache__/ipunittest.cpython-312.pyc +0 -0
  26. lib/python3.12/site-packages/IPython/testing/__pycache__/skipdoctest.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/IPython/testing/__pycache__/tools.cpython-312.pyc +0 -0
  28. lib/python3.12/site-packages/IPython/testing/decorators.py +147 -0
  29. lib/python3.12/site-packages/IPython/testing/globalipapp.py +114 -0
  30. lib/python3.12/site-packages/IPython/testing/ipunittest.py +187 -0
  31. lib/python3.12/site-packages/IPython/testing/plugin/__init__.py +0 -0
  32. lib/python3.12/site-packages/IPython/testing/plugin/dtexample.py +167 -0
  33. lib/python3.12/site-packages/IPython/testing/plugin/ipdoctest.py +299 -0
  34. lib/python3.12/site-packages/IPython/testing/plugin/pytest_ipdoctest.py +877 -0
  35. lib/python3.12/site-packages/IPython/testing/plugin/setup.py +18 -0
  36. lib/python3.12/site-packages/IPython/testing/plugin/simple.py +45 -0
  37. lib/python3.12/site-packages/IPython/testing/plugin/test_combo.txt +36 -0
  38. lib/python3.12/site-packages/IPython/testing/plugin/test_exampleip.txt +30 -0
  39. lib/python3.12/site-packages/IPython/testing/plugin/test_ipdoctest.py +92 -0
  40. lib/python3.12/site-packages/IPython/testing/plugin/test_refs.py +39 -0
  41. lib/python3.12/site-packages/IPython/testing/skipdoctest.py +19 -0
  42. lib/python3.12/site-packages/IPython/testing/tools.py +434 -0
  43. lib/python3.12/site-packages/IPython/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  44. lib/python3.12/site-packages/IPython/utils/__pycache__/capture.cpython-312.pyc +0 -0
  45. lib/python3.12/site-packages/IPython/utils/__pycache__/decorators.cpython-312.pyc +0 -0
  46. lib/python3.12/site-packages/IPython/utils/__pycache__/dir2.cpython-312.pyc +0 -0
  47. lib/python3.12/site-packages/IPython/utils/__pycache__/encoding.cpython-312.pyc +0 -0
  48. lib/python3.12/site-packages/IPython/utils/__pycache__/frame.cpython-312.pyc +0 -0
  49. lib/python3.12/site-packages/IPython/utils/__pycache__/generics.cpython-312.pyc +0 -0
  50. lib/python3.12/site-packages/IPython/utils/__pycache__/ipstruct.cpython-312.pyc +0 -0
lib/python3.12/site-packages/IPython/__init__.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PYTHON_ARGCOMPLETE_OK
2
+ """
3
+ IPython: tools for interactive and parallel computing in Python.
4
+
5
+ https://ipython.org
6
+ """
7
+ #-----------------------------------------------------------------------------
8
+ # Copyright (c) 2008-2011, IPython Development Team.
9
+ # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
10
+ # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
11
+ # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
12
+ #
13
+ # Distributed under the terms of the Modified BSD License.
14
+ #
15
+ # The full license is in the file COPYING.txt, distributed with this software.
16
+ #-----------------------------------------------------------------------------
17
+
18
+ #-----------------------------------------------------------------------------
19
+ # Imports
20
+ #-----------------------------------------------------------------------------
21
+
22
+ import sys
23
+ import warnings
24
+
25
+ #-----------------------------------------------------------------------------
26
+ # Setup everything
27
+ #-----------------------------------------------------------------------------
28
+
29
+ # Don't forget to also update setup.py when this changes!
30
+ if sys.version_info < (3, 11):
31
+ raise ImportError(
32
+ """
33
+ IPython 8.31+ supports Python 3.11 and above, following SPEC0
34
+ IPython 8.19+ supports Python 3.10 and above, following SPEC0.
35
+ IPython 8.13+ supports Python 3.9 and above, following NEP 29.
36
+ IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29.
37
+ When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
38
+ Python 3.3 and 3.4 were supported up to IPython 6.x.
39
+ Python 3.5 was supported with IPython 7.0 to 7.9.
40
+ Python 3.6 was supported with IPython up to 7.16.
41
+ Python 3.7 was still supported with the 7.x branch.
42
+
43
+ See IPython `README.rst` file for more information:
44
+
45
+ https://github.com/ipython/ipython/blob/main/README.rst
46
+
47
+ """
48
+ )
49
+
50
+ #-----------------------------------------------------------------------------
51
+ # Setup the top level names
52
+ #-----------------------------------------------------------------------------
53
+
54
+ from .core.getipython import get_ipython
55
+ from .core import release
56
+ from .core.application import Application
57
+ from .terminal.embed import embed
58
+
59
+ from .core.interactiveshell import InteractiveShell
60
+ from .utils.sysinfo import sys_info
61
+ from .utils.frame import extract_module_locals
62
+
63
+ __all__ = ["start_ipython", "embed", "embed_kernel"]
64
+
65
+ # Release data
66
+ __author__ = '%s <%s>' % (release.author, release.author_email)
67
+ __license__ = release.license
68
+ __version__ = release.version
69
+ version_info = release.version_info
70
+ # list of CVEs that should have been patched in this release.
71
+ # this is informational and should not be relied upon.
72
+ __patched_cves__ = {"CVE-2022-21699", "CVE-2023-24816"}
73
+
74
+
75
+ def embed_kernel(module=None, local_ns=None, **kwargs):
76
+ """Embed and start an IPython kernel in a given scope.
77
+
78
+ If you don't want the kernel to initialize the namespace
79
+ from the scope of the surrounding function,
80
+ and/or you want to load full IPython configuration,
81
+ you probably want `IPython.start_kernel()` instead.
82
+
83
+ This is a deprecated alias for `ipykernel.embed.embed_kernel()`,
84
+ to be removed in the future.
85
+ You should import directly from `ipykernel.embed`; this wrapper
86
+ fails anyway if you don't have `ipykernel` package installed.
87
+
88
+ Parameters
89
+ ----------
90
+ module : types.ModuleType, optional
91
+ The module to load into IPython globals (default: caller)
92
+ local_ns : dict, optional
93
+ The namespace to load into IPython user namespace (default: caller)
94
+ **kwargs : various, optional
95
+ Further keyword args are relayed to the IPKernelApp constructor,
96
+ such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
97
+ allowing configuration of the kernel. Will only have an effect
98
+ on the first embed_kernel call for a given process.
99
+ """
100
+
101
+ warnings.warn(
102
+ "import embed_kernel from ipykernel.embed directly (since 2013)."
103
+ " Importing from IPython will be removed in the future",
104
+ DeprecationWarning,
105
+ stacklevel=2,
106
+ )
107
+
108
+ (caller_module, caller_locals) = extract_module_locals(1)
109
+ if module is None:
110
+ module = caller_module
111
+ if local_ns is None:
112
+ local_ns = dict(**caller_locals)
113
+
114
+ # Only import .zmq when we really need it
115
+ from ipykernel.embed import embed_kernel as real_embed_kernel
116
+ real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
117
+
118
+ def start_ipython(argv=None, **kwargs):
119
+ """Launch a normal IPython instance (as opposed to embedded)
120
+
121
+ `IPython.embed()` puts a shell in a particular calling scope,
122
+ such as a function or method for debugging purposes,
123
+ which is often not desirable.
124
+
125
+ `start_ipython()` does full, regular IPython initialization,
126
+ including loading startup files, configuration, etc.
127
+ much of which is skipped by `embed()`.
128
+
129
+ This is a public API method, and will survive implementation changes.
130
+
131
+ Parameters
132
+ ----------
133
+ argv : list or None, optional
134
+ If unspecified or None, IPython will parse command-line options from sys.argv.
135
+ To prevent any command-line parsing, pass an empty list: `argv=[]`.
136
+ user_ns : dict, optional
137
+ specify this dictionary to initialize the IPython user namespace with particular values.
138
+ **kwargs : various, optional
139
+ Any other kwargs will be passed to the Application constructor,
140
+ such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
141
+ allowing configuration of the instance (see :ref:`terminal_options`).
142
+ """
143
+ from IPython.terminal.ipapp import launch_new_instance
144
+ return launch_new_instance(argv=argv, **kwargs)
lib/python3.12/site-packages/IPython/__main__.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PYTHON_ARGCOMPLETE_OK
2
+ # encoding: utf-8
3
+ """Terminal-based IPython entry point."""
4
+ # -----------------------------------------------------------------------------
5
+ # Copyright (c) 2012, IPython Development Team.
6
+ #
7
+ # Distributed under the terms of the Modified BSD License.
8
+ #
9
+ # The full license is in the file COPYING.txt, distributed with this software.
10
+ # -----------------------------------------------------------------------------
11
+
12
+ from IPython import start_ipython
13
+
14
+ start_ipython()
lib/python3.12/site-packages/IPython/core/compilerop.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compiler tools with improved interactive support.
2
+
3
+ Provides compilation machinery similar to codeop, but with caching support so
4
+ we can provide interactive tracebacks.
5
+
6
+ Authors
7
+ -------
8
+ * Robert Kern
9
+ * Fernando Perez
10
+ * Thomas Kluyver
11
+ """
12
+
13
+ # Note: though it might be more natural to name this module 'compiler', that
14
+ # name is in the stdlib and name collisions with the stdlib tend to produce
15
+ # weird problems (often with third-party tools).
16
+
17
+ #-----------------------------------------------------------------------------
18
+ # Copyright (C) 2010-2011 The IPython Development Team.
19
+ #
20
+ # Distributed under the terms of the BSD License.
21
+ #
22
+ # The full license is in the file COPYING.txt, distributed with this software.
23
+ #-----------------------------------------------------------------------------
24
+
25
+ #-----------------------------------------------------------------------------
26
+ # Imports
27
+ #-----------------------------------------------------------------------------
28
+
29
+ # Stdlib imports
30
+ import __future__
31
+ from ast import PyCF_ONLY_AST
32
+ import codeop
33
+ import functools
34
+ import hashlib
35
+ import linecache
36
+ import operator
37
+ import time
38
+ from contextlib import contextmanager
39
+
40
+ #-----------------------------------------------------------------------------
41
+ # Constants
42
+ #-----------------------------------------------------------------------------
43
+
44
+ # Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h,
45
+ # this is used as a bitmask to extract future-related code flags.
46
+ PyCF_MASK = functools.reduce(operator.or_,
47
+ (getattr(__future__, fname).compiler_flag
48
+ for fname in __future__.all_feature_names))
49
+
50
+ #-----------------------------------------------------------------------------
51
+ # Local utilities
52
+ #-----------------------------------------------------------------------------
53
+
54
+ def code_name(code, number=0):
55
+ """ Compute a (probably) unique name for code for caching.
56
+
57
+ This now expects code to be unicode.
58
+ """
59
+ hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest()
60
+ # Include the number and 12 characters of the hash in the name. It's
61
+ # pretty much impossible that in a single session we'll have collisions
62
+ # even with truncated hashes, and the full one makes tracebacks too long
63
+ return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12])
64
+
65
+ #-----------------------------------------------------------------------------
66
+ # Classes and functions
67
+ #-----------------------------------------------------------------------------
68
+
69
+ class CachingCompiler(codeop.Compile):
70
+ """A compiler that caches code compiled from interactive statements.
71
+ """
72
+
73
+ def __init__(self):
74
+ codeop.Compile.__init__(self)
75
+
76
+ # Caching a dictionary { filename: execution_count } for nicely
77
+ # rendered tracebacks. The filename corresponds to the filename
78
+ # argument used for the builtins.compile function.
79
+ self._filename_map = {}
80
+
81
+ def ast_parse(self, source, filename='<unknown>', symbol='exec'):
82
+ """Parse code to an AST with the current compiler flags active.
83
+
84
+ Arguments are exactly the same as ast.parse (in the standard library),
85
+ and are passed to the built-in compile function."""
86
+ return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)
87
+
88
+ def reset_compiler_flags(self):
89
+ """Reset compiler flags to default state."""
90
+ # This value is copied from codeop.Compile.__init__, so if that ever
91
+ # changes, it will need to be updated.
92
+ self.flags = codeop.PyCF_DONT_IMPLY_DEDENT
93
+
94
+ @property
95
+ def compiler_flags(self):
96
+ """Flags currently active in the compilation process.
97
+ """
98
+ return self.flags
99
+
100
+ def get_code_name(self, raw_code, transformed_code, number):
101
+ """Compute filename given the code, and the cell number.
102
+
103
+ Parameters
104
+ ----------
105
+ raw_code : str
106
+ The raw cell code.
107
+ transformed_code : str
108
+ The executable Python source code to cache and compile.
109
+ number : int
110
+ A number which forms part of the code's name. Used for the execution
111
+ counter.
112
+
113
+ Returns
114
+ -------
115
+ The computed filename.
116
+ """
117
+ return code_name(transformed_code, number)
118
+
119
+ def format_code_name(self, name):
120
+ """Return a user-friendly label and name for a code block.
121
+
122
+ Parameters
123
+ ----------
124
+ name : str
125
+ The name for the code block returned from get_code_name
126
+
127
+ Returns
128
+ -------
129
+ A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used.
130
+ """
131
+ if name in self._filename_map:
132
+ return "Cell", "In[%s]" % self._filename_map[name]
133
+
134
+ def cache(self, transformed_code, number=0, raw_code=None):
135
+ """Make a name for a block of code, and cache the code.
136
+
137
+ Parameters
138
+ ----------
139
+ transformed_code : str
140
+ The executable Python source code to cache and compile.
141
+ number : int
142
+ A number which forms part of the code's name. Used for the execution
143
+ counter.
144
+ raw_code : str
145
+ The raw code before transformation, if None, set to `transformed_code`.
146
+
147
+ Returns
148
+ -------
149
+ The name of the cached code (as a string). Pass this as the filename
150
+ argument to compilation, so that tracebacks are correctly hooked up.
151
+ """
152
+ if raw_code is None:
153
+ raw_code = transformed_code
154
+
155
+ name = self.get_code_name(raw_code, transformed_code, number)
156
+
157
+ # Save the execution count
158
+ self._filename_map[name] = number
159
+
160
+ # Since Python 2.5, setting mtime to `None` means the lines will
161
+ # never be removed by `linecache.checkcache`. This means all the
162
+ # monkeypatching has *never* been necessary, since this code was
163
+ # only added in 2010, at which point IPython had already stopped
164
+ # supporting Python 2.4.
165
+ #
166
+ # Note that `linecache.clearcache` and `linecache.updatecache` may
167
+ # still remove our code from the cache, but those show explicit
168
+ # intent, and we should not try to interfere. Normally the former
169
+ # is never called except when out of memory, and the latter is only
170
+ # called for lines *not* in the cache.
171
+ entry = (
172
+ len(transformed_code),
173
+ None,
174
+ [line + "\n" for line in transformed_code.splitlines()],
175
+ name,
176
+ )
177
+ linecache.cache[name] = entry
178
+ return name
179
+
180
+ @contextmanager
181
+ def extra_flags(self, flags):
182
+ ## bits that we'll set to 1
183
+ turn_on_bits = ~self.flags & flags
184
+
185
+
186
+ self.flags = self.flags | flags
187
+ try:
188
+ yield
189
+ finally:
190
+ # turn off only the bits we turned on so that something like
191
+ # __future__ that set flags stays.
192
+ self.flags &= ~turn_on_bits
lib/python3.12/site-packages/IPython/core/crashhandler.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """sys.excepthook for IPython itself, leaves a detailed report on disk.
2
+
3
+ Authors:
4
+
5
+ * Fernando Perez
6
+ * Brian E. Granger
7
+ """
8
+
9
+ #-----------------------------------------------------------------------------
10
+ # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
11
+ # Copyright (C) 2008-2011 The IPython Development Team
12
+ #
13
+ # Distributed under the terms of the BSD License. The full license is in
14
+ # the file COPYING, distributed as part of this software.
15
+ #-----------------------------------------------------------------------------
16
+
17
+ #-----------------------------------------------------------------------------
18
+ # Imports
19
+ #-----------------------------------------------------------------------------
20
+
21
+ import sys
22
+ import traceback
23
+ from pprint import pformat
24
+ from pathlib import Path
25
+
26
+ import builtins as builtin_mod
27
+
28
+ from IPython.core import ultratb
29
+ from IPython.core.application import Application
30
+ from IPython.core.release import author_email
31
+ from IPython.utils.sysinfo import sys_info
32
+
33
+ from IPython.core.release import __version__ as version
34
+
35
+ from typing import Optional, Dict
36
+ import types
37
+
38
+ #-----------------------------------------------------------------------------
39
+ # Code
40
+ #-----------------------------------------------------------------------------
41
+
42
+ # Template for the user message.
43
+ _default_message_template = """\
44
+ Oops, {app_name} crashed. We do our best to make it stable, but...
45
+
46
+ A crash report was automatically generated with the following information:
47
+ - A verbatim copy of the crash traceback.
48
+ - A copy of your input history during this session.
49
+ - Data on your current {app_name} configuration.
50
+
51
+ It was left in the file named:
52
+ \t'{crash_report_fname}'
53
+ If you can email this file to the developers, the information in it will help
54
+ them in understanding and correcting the problem.
55
+
56
+ You can mail it to: {contact_name} at {contact_email}
57
+ with the subject '{app_name} Crash Report'.
58
+
59
+ If you want to do it now, the following command will work (under Unix):
60
+ mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
61
+
62
+ In your email, please also include information about:
63
+ - The operating system under which the crash happened: Linux, macOS, Windows,
64
+ other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2,
65
+ Windows 10 Pro), and whether it is 32-bit or 64-bit;
66
+ - How {app_name} was installed: using pip or conda, from GitHub, as part of
67
+ a Docker container, or other, providing more detail if possible;
68
+ - How to reproduce the crash: what exact sequence of instructions can one
69
+ input to get the same crash? Ideally, find a minimal yet complete sequence
70
+ of instructions that yields the crash.
71
+
72
+ To ensure accurate tracking of this issue, please file a report about it at:
73
+ {bug_tracker}
74
+ """
75
+
76
+ _lite_message_template = """
77
+ If you suspect this is an IPython {version} bug, please report it at:
78
+ https://github.com/ipython/ipython/issues
79
+ or send an email to the mailing list at {email}
80
+
81
+ You can print a more detailed traceback right now with "%tb", or use "%debug"
82
+ to interactively debug it.
83
+
84
+ Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
85
+ {config}Application.verbose_crash=True
86
+ """
87
+
88
+
89
+ class CrashHandler:
90
+ """Customizable crash handlers for IPython applications.
91
+
92
+ Instances of this class provide a :meth:`__call__` method which can be
93
+ used as a ``sys.excepthook``. The :meth:`__call__` signature is::
94
+
95
+ def __call__(self, etype, evalue, etb)
96
+ """
97
+
98
+ message_template = _default_message_template
99
+ section_sep = '\n\n'+'*'*75+'\n\n'
100
+ info: Dict[str, Optional[str]]
101
+
102
+ def __init__(
103
+ self,
104
+ app: Application,
105
+ contact_name: Optional[str] = None,
106
+ contact_email: Optional[str] = None,
107
+ bug_tracker: Optional[str] = None,
108
+ show_crash_traceback: bool = True,
109
+ call_pdb: bool = False,
110
+ ):
111
+ """Create a new crash handler
112
+
113
+ Parameters
114
+ ----------
115
+ app : Application
116
+ A running :class:`Application` instance, which will be queried at
117
+ crash time for internal information.
118
+ contact_name : str
119
+ A string with the name of the person to contact.
120
+ contact_email : str
121
+ A string with the email address of the contact.
122
+ bug_tracker : str
123
+ A string with the URL for your project's bug tracker.
124
+ show_crash_traceback : bool
125
+ If false, don't print the crash traceback on stderr, only generate
126
+ the on-disk report
127
+ call_pdb
128
+ Whether to call pdb on crash
129
+
130
+ Attributes
131
+ ----------
132
+ These instances contain some non-argument attributes which allow for
133
+ further customization of the crash handler's behavior. Please see the
134
+ source for further details.
135
+
136
+ """
137
+ self.crash_report_fname = "Crash_report_%s.txt" % app.name
138
+ self.app = app
139
+ self.call_pdb = call_pdb
140
+ #self.call_pdb = True # dbg
141
+ self.show_crash_traceback = show_crash_traceback
142
+ self.info = dict(app_name = app.name,
143
+ contact_name = contact_name,
144
+ contact_email = contact_email,
145
+ bug_tracker = bug_tracker,
146
+ crash_report_fname = self.crash_report_fname)
147
+
148
+ def __call__(
149
+ self,
150
+ etype: type[BaseException],
151
+ evalue: BaseException,
152
+ etb: types.TracebackType,
153
+ ) -> None:
154
+ """Handle an exception, call for compatible with sys.excepthook"""
155
+
156
+ # do not allow the crash handler to be called twice without reinstalling it
157
+ # this prevents unlikely errors in the crash handling from entering an
158
+ # infinite loop.
159
+ sys.excepthook = sys.__excepthook__
160
+
161
+
162
+ # Use this ONLY for developer debugging (keep commented out for release)
163
+ ipython_dir = getattr(self.app, "ipython_dir", None)
164
+ if ipython_dir is not None:
165
+ assert isinstance(ipython_dir, str)
166
+ rptdir = Path(ipython_dir)
167
+ else:
168
+ rptdir = Path.cwd()
169
+ if not rptdir.is_dir():
170
+ rptdir = Path.cwd()
171
+ report_name = rptdir / self.crash_report_fname
172
+ # write the report filename into the instance dict so it can get
173
+ # properly expanded out in the user message template
174
+ self.crash_report_fname = str(report_name)
175
+ self.info["crash_report_fname"] = str(report_name)
176
+ TBhandler = ultratb.VerboseTB(
177
+ theme_name="nocolor",
178
+ long_header=True,
179
+ call_pdb=self.call_pdb,
180
+ )
181
+ if self.call_pdb:
182
+ TBhandler(etype,evalue,etb)
183
+ return
184
+ else:
185
+ traceback = TBhandler.text(etype,evalue,etb,context=31)
186
+
187
+ # print traceback to screen
188
+ if self.show_crash_traceback:
189
+ print(traceback, file=sys.stderr)
190
+
191
+ # and generate a complete report on disk
192
+ try:
193
+ report = open(report_name, "w", encoding="utf-8")
194
+ except:
195
+ print('Could not create crash report on disk.', file=sys.stderr)
196
+ return
197
+
198
+ with report:
199
+ # Inform user on stderr of what happened
200
+ print('\n'+'*'*70+'\n', file=sys.stderr)
201
+ print(self.message_template.format(**self.info), file=sys.stderr)
202
+
203
+ # Construct report on disk
204
+ report.write(self.make_report(str(traceback)))
205
+
206
+ builtin_mod.input("Hit <Enter> to quit (your terminal may close):")
207
+
208
+ def make_report(self, traceback: str) -> str:
209
+ """Return a string containing a crash report."""
210
+
211
+ sec_sep = self.section_sep
212
+
213
+ report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
214
+ rpt_add = report.append
215
+ rpt_add(sys_info())
216
+
217
+ try:
218
+ config = pformat(self.app.config)
219
+ rpt_add(sec_sep)
220
+ rpt_add("Application name: %s\n\n" % self.app.name)
221
+ rpt_add("Current user configuration structure:\n\n")
222
+ rpt_add(config)
223
+ except:
224
+ pass
225
+ rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
226
+
227
+ return ''.join(report)
228
+
229
+
230
+ def crash_handler_lite(
231
+ etype: type[BaseException], evalue: BaseException, tb: types.TracebackType
232
+ ) -> None:
233
+ """a light excepthook, adding a small message to the usual traceback"""
234
+ traceback.print_exception(etype, evalue, tb)
235
+
236
+ from IPython.core.interactiveshell import InteractiveShell
237
+ if InteractiveShell.initialized():
238
+ # we are in a Shell environment, give %magic example
239
+ config = "%config "
240
+ else:
241
+ # we are not in a shell, show generic config
242
+ config = "c."
243
+ print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr)
244
+
lib/python3.12/site-packages/IPython/core/displaypub.py ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """An interface for publishing rich data to frontends.
2
+
3
+ There are two components of the display system:
4
+
5
+ * Display formatters, which take a Python object and compute the
6
+ representation of the object in various formats (text, HTML, SVG, etc.).
7
+ * The display publisher that is used to send the representation data to the
8
+ various frontends.
9
+
10
+ This module defines the logic display publishing. The display publisher uses
11
+ the ``display_data`` message type that is defined in the IPython messaging
12
+ spec.
13
+ """
14
+
15
+ # Copyright (c) IPython Development Team.
16
+ # Distributed under the terms of the Modified BSD License.
17
+
18
+ import sys
19
+
20
+ from traitlets.config.configurable import Configurable
21
+ from traitlets import List
22
+
23
+ # This used to be defined here - it is imported for backwards compatibility
24
+ from .display_functions import publish_display_data
25
+ from .history import HistoryOutput
26
+
27
+ import typing as t
28
+
29
+ # -----------------------------------------------------------------------------
30
+ # Main payload class
31
+ # -----------------------------------------------------------------------------
32
+
33
+ _sentinel = object()
34
+
35
+
36
+ class DisplayPublisher(Configurable):
37
+ """A traited class that publishes display data to frontends.
38
+
39
+ Instances of this class are created by the main IPython object and should
40
+ be accessed there.
41
+ """
42
+
43
+ def __init__(self, shell=None, *args, **kwargs):
44
+ self.shell = shell
45
+ self._is_publishing = False
46
+ self._in_post_execute = False
47
+ if self.shell:
48
+ self._setup_execution_tracking()
49
+ super().__init__(*args, **kwargs)
50
+
51
+ def _validate_data(self, data, metadata=None):
52
+ """Validate the display data.
53
+
54
+ Parameters
55
+ ----------
56
+ data : dict
57
+ The formata data dictionary.
58
+ metadata : dict
59
+ Any metadata for the data.
60
+ """
61
+
62
+ if not isinstance(data, dict):
63
+ raise TypeError("data must be a dict, got: %r" % data)
64
+ if metadata is not None:
65
+ if not isinstance(metadata, dict):
66
+ raise TypeError("metadata must be a dict, got: %r" % data)
67
+
68
+ def _setup_execution_tracking(self):
69
+ """Set up hooks to track execution state"""
70
+ self.shell.events.register("post_execute", self._on_post_execute)
71
+ self.shell.events.register("pre_execute", self._on_pre_execute)
72
+
73
+ def _on_post_execute(self):
74
+ """Called at start of post_execute phase"""
75
+ self._in_post_execute = True
76
+
77
+ def _on_pre_execute(self):
78
+ """Called at start of pre_execute phase"""
79
+ self._in_post_execute = False
80
+
81
+ # use * to indicate transient, update are keyword-only
82
+ def publish(
83
+ self,
84
+ data,
85
+ metadata=None,
86
+ source=_sentinel,
87
+ *,
88
+ transient=None,
89
+ update=False,
90
+ **kwargs,
91
+ ) -> None:
92
+ """Publish data and metadata to all frontends.
93
+
94
+ See the ``display_data`` message in the messaging documentation for
95
+ more details about this message type.
96
+
97
+ The following MIME types are currently implemented:
98
+
99
+ * text/plain
100
+ * text/html
101
+ * text/markdown
102
+ * text/latex
103
+ * application/json
104
+ * application/javascript
105
+ * image/png
106
+ * image/jpeg
107
+ * image/svg+xml
108
+
109
+ Parameters
110
+ ----------
111
+ data : dict
112
+ A dictionary having keys that are valid MIME types (like
113
+ 'text/plain' or 'image/svg+xml') and values that are the data for
114
+ that MIME type. The data itself must be a JSON'able data
115
+ structure. Minimally all data should have the 'text/plain' data,
116
+ which can be displayed by all frontends. If more than the plain
117
+ text is given, it is up to the frontend to decide which
118
+ representation to use.
119
+ metadata : dict
120
+ A dictionary for metadata related to the data. This can contain
121
+ arbitrary key, value pairs that frontends can use to interpret
122
+ the data. Metadata specific to each mime-type can be specified
123
+ in the metadata dict with the same mime-type keys as
124
+ the data itself.
125
+ source : str, deprecated
126
+ Unused.
127
+ transient : dict, keyword-only
128
+ A dictionary for transient data.
129
+ Data in this dictionary should not be persisted as part of saving this output.
130
+ Examples include 'display_id'.
131
+ update : bool, keyword-only, default: False
132
+ If True, only update existing outputs with the same display_id,
133
+ rather than creating a new output.
134
+ """
135
+
136
+ if source is not _sentinel:
137
+ import warnings
138
+
139
+ warnings.warn(
140
+ "The 'source' parameter is deprecated since IPython 3.0 and will be ignored "
141
+ "(this warning is present since 9.0). `source` parameter will be removed in the future.",
142
+ DeprecationWarning,
143
+ stacklevel=2,
144
+ )
145
+
146
+ handlers: t.Dict = {}
147
+ if self.shell is not None:
148
+ handlers = getattr(self.shell, "mime_renderers", {})
149
+
150
+ outputs = self.shell.history_manager.outputs
151
+
152
+ target_execution_count = self.shell.execution_count - 1
153
+ if self._in_post_execute:
154
+ # We're in post_execute, so this is likely a matplotlib flush
155
+ # Use execution_count - 1 to associate with the cell that created the plot
156
+ target_execution_count = self.shell.execution_count - 1
157
+
158
+ outputs[target_execution_count].append(
159
+ HistoryOutput(output_type="display_data", bundle=data)
160
+ )
161
+
162
+ for mime, handler in handlers.items():
163
+ if mime in data:
164
+ handler(data[mime], metadata.get(mime, None))
165
+ return
166
+
167
+ self._is_publishing = True
168
+ if "text/plain" in data:
169
+ print(data["text/plain"])
170
+ self._is_publishing = False
171
+
172
+ @property
173
+ def is_publishing(self):
174
+ return self._is_publishing
175
+
176
+ def clear_output(self, wait=False):
177
+ """Clear the output of the cell receiving output."""
178
+ print("\033[2K\r", end="")
179
+ sys.stdout.flush()
180
+ print("\033[2K\r", end="")
181
+ sys.stderr.flush()
182
+
183
+
184
+ class CapturingDisplayPublisher(DisplayPublisher):
185
+ """A DisplayPublisher that stores"""
186
+
187
+ outputs: List = List()
188
+
189
+ def publish(
190
+ self, data, metadata=None, source=None, *, transient=None, update=False
191
+ ):
192
+ self.outputs.append(
193
+ {
194
+ "data": data,
195
+ "metadata": metadata,
196
+ "transient": transient,
197
+ "update": update,
198
+ }
199
+ )
200
+
201
+ def clear_output(self, wait=False):
202
+ super(CapturingDisplayPublisher, self).clear_output(wait)
203
+
204
+ # empty the list, *do not* reassign a new list
205
+ self.outputs.clear()
lib/python3.12/site-packages/IPython/core/getipython.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Simple function to call to get the current InteractiveShell instance
2
+ """
3
+
4
+ #-----------------------------------------------------------------------------
5
+ # Copyright (C) 2013 The IPython Development Team
6
+ #
7
+ # Distributed under the terms of the BSD License. The full license is in
8
+ # the file COPYING, distributed as part of this software.
9
+ #-----------------------------------------------------------------------------
10
+
11
+ #-----------------------------------------------------------------------------
12
+ # Classes and functions
13
+ #-----------------------------------------------------------------------------
14
+
15
+
16
+ def get_ipython():
17
+ """Get the global InteractiveShell instance.
18
+
19
+ Returns None if no InteractiveShell instance is registered.
20
+ """
21
+ from IPython.core.interactiveshell import InteractiveShell
22
+ if InteractiveShell.initialized():
23
+ return InteractiveShell.instance()
lib/python3.12/site-packages/IPython/core/guarded_eval.py ADDED
@@ -0,0 +1,1656 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from copy import copy
2
+ from inspect import isclass, signature, Signature, getmodule
3
+ from typing import (
4
+ Annotated,
5
+ AnyStr,
6
+ Literal,
7
+ NamedTuple,
8
+ NewType,
9
+ Optional,
10
+ Protocol,
11
+ TypeGuard,
12
+ Union,
13
+ get_args,
14
+ get_origin,
15
+ is_typeddict,
16
+ )
17
+ from collections.abc import Callable, Sequence
18
+ import ast
19
+ import builtins
20
+ import collections
21
+ import dataclasses
22
+ import operator
23
+ import sys
24
+ import typing
25
+ import warnings
26
+ from functools import cached_property
27
+ from dataclasses import dataclass, field
28
+ from types import MethodDescriptorType, ModuleType, MethodType
29
+
30
+ from IPython.utils.decorators import undoc
31
+
32
+ import types
33
+ from typing import Self, LiteralString, get_type_hints
34
+
35
+ if sys.version_info < (3, 12):
36
+ from typing_extensions import TypeAliasType
37
+ else:
38
+ from typing import TypeAliasType
39
+
40
+
41
+ @undoc
42
+ class HasGetItem(Protocol):
43
+ def __getitem__(self, key) -> None:
44
+ ...
45
+
46
+
47
+ @undoc
48
+ class InstancesHaveGetItem(Protocol):
49
+ def __call__(self, *args, **kwargs) -> HasGetItem:
50
+ ...
51
+
52
+
53
+ @undoc
54
+ class HasGetAttr(Protocol):
55
+ def __getattr__(self, key) -> None:
56
+ ...
57
+
58
+
59
+ @undoc
60
+ class DoesNotHaveGetAttr(Protocol):
61
+ pass
62
+
63
+
64
+ # By default `__getattr__` is not explicitly implemented on most objects
65
+ MayHaveGetattr = Union[HasGetAttr, DoesNotHaveGetAttr]
66
+
67
+
68
+ def _unbind_method(func: Callable) -> Union[Callable, None]:
69
+ """Get unbound method for given bound method.
70
+
71
+ Returns None if cannot get unbound method, or method is already unbound.
72
+ """
73
+ owner = getattr(func, "__self__", None)
74
+ owner_class = type(owner)
75
+ name = getattr(func, "__name__", None)
76
+ instance_dict_overrides = getattr(owner, "__dict__", None)
77
+ if (
78
+ owner is not None
79
+ and name
80
+ and (
81
+ not instance_dict_overrides
82
+ or (instance_dict_overrides and name not in instance_dict_overrides)
83
+ )
84
+ ):
85
+ return getattr(owner_class, name)
86
+ return None
87
+
88
+
89
+ @undoc
90
+ @dataclass
91
+ class EvaluationPolicy:
92
+ """Definition of evaluation policy."""
93
+
94
+ allow_locals_access: bool = False
95
+ allow_globals_access: bool = False
96
+ allow_item_access: bool = False
97
+ allow_attr_access: bool = False
98
+ allow_builtins_access: bool = False
99
+ allow_all_operations: bool = False
100
+ allow_any_calls: bool = False
101
+ allow_auto_import: bool = False
102
+ allowed_calls: set[Callable] = field(default_factory=set)
103
+
104
+ def can_get_item(self, value, item):
105
+ return self.allow_item_access
106
+
107
+ def can_get_attr(self, value, attr):
108
+ return self.allow_attr_access
109
+
110
+ def can_operate(self, dunders: tuple[str, ...], a, b=None):
111
+ if self.allow_all_operations:
112
+ return True
113
+
114
+ def can_call(self, func):
115
+ if self.allow_any_calls:
116
+ return True
117
+
118
+ if func in self.allowed_calls:
119
+ return True
120
+
121
+ owner_method = _unbind_method(func)
122
+
123
+ if owner_method and owner_method in self.allowed_calls:
124
+ return True
125
+
126
+
127
+ def _get_external(module_name: str, access_path: Sequence[str]):
128
+ """Get value from external module given a dotted access path.
129
+
130
+ Only gets value if the module is already imported.
131
+
132
+ Raises:
133
+ * `KeyError` if module is removed not found, and
134
+ * `AttributeError` if access path does not match an exported object
135
+ """
136
+ try:
137
+ member_type = sys.modules[module_name]
138
+ # standard module
139
+ for attr in access_path:
140
+ member_type = getattr(member_type, attr)
141
+ return member_type
142
+ except (KeyError, AttributeError):
143
+ # handle modules in namespace packages
144
+ module_path = ".".join([module_name, *access_path])
145
+ if module_path in sys.modules:
146
+ return sys.modules[module_path]
147
+ raise
148
+
149
+
150
+ def _has_original_dunder_external(
151
+ value,
152
+ module_name: str,
153
+ access_path: Sequence[str],
154
+ method_name: str,
155
+ ):
156
+ if module_name not in sys.modules:
157
+ full_module_path = ".".join([module_name, *access_path])
158
+ if full_module_path not in sys.modules:
159
+ # LBYLB as it is faster
160
+ return False
161
+ try:
162
+ member_type = _get_external(module_name, access_path)
163
+ value_type = type(value)
164
+ if type(value) == member_type:
165
+ return True
166
+ if isinstance(member_type, ModuleType):
167
+ value_module = getmodule(value_type)
168
+ if not value_module or not value_module.__name__:
169
+ return False
170
+ if (
171
+ value_module.__name__ == member_type.__name__
172
+ or value_module.__name__.startswith(member_type.__name__ + ".")
173
+ ):
174
+ return True
175
+ if method_name == "__getattribute__":
176
+ # we have to short-circuit here due to an unresolved issue in
177
+ # `isinstance` implementation: https://bugs.python.org/issue32683
178
+ return False
179
+ if not isinstance(member_type, ModuleType) and isinstance(value, member_type):
180
+ method = getattr(value_type, method_name, None)
181
+ member_method = getattr(member_type, method_name, None)
182
+ if member_method == method:
183
+ return True
184
+ if isinstance(member_type, ModuleType):
185
+ method = getattr(value_type, method_name, None)
186
+ for base_class in value_type.__mro__[1:]:
187
+ base_module = getmodule(base_class)
188
+ if base_module and (
189
+ base_module.__name__ == member_type.__name__
190
+ or base_module.__name__.startswith(member_type.__name__ + ".")
191
+ ):
192
+ # Check if the method comes from this trusted base class
193
+ base_method = getattr(base_class, method_name, None)
194
+ if base_method is not None and base_method == method:
195
+ return True
196
+ except (AttributeError, KeyError):
197
+ return False
198
+
199
+
200
+ def _has_original_dunder(
201
+ value, allowed_types, allowed_methods, allowed_external, method_name
202
+ ):
203
+ # note: Python ignores `__getattr__`/`__getitem__` on instances,
204
+ # we only need to check at class level
205
+ value_type = type(value)
206
+
207
+ # strict type check passes → no need to check method
208
+ if value_type in allowed_types:
209
+ return True
210
+
211
+ method = getattr(value_type, method_name, None)
212
+
213
+ if method is None:
214
+ return None
215
+
216
+ if method in allowed_methods:
217
+ return True
218
+
219
+ for module_name, *access_path in allowed_external:
220
+ if _has_original_dunder_external(value, module_name, access_path, method_name):
221
+ return True
222
+
223
+ return False
224
+
225
+
226
+ def _coerce_path_to_tuples(
227
+ allow_list: set[tuple[str, ...] | str],
228
+ ) -> set[tuple[str, ...]]:
229
+ """Replace dotted paths on the provided allow-list with tuples."""
230
+ return {
231
+ path if isinstance(path, tuple) else tuple(path.split("."))
232
+ for path in allow_list
233
+ }
234
+
235
+
236
+ @undoc
237
+ @dataclass
238
+ class SelectivePolicy(EvaluationPolicy):
239
+ allowed_getitem: set[InstancesHaveGetItem] = field(default_factory=set)
240
+ allowed_getitem_external: set[tuple[str, ...] | str] = field(default_factory=set)
241
+
242
+ allowed_getattr: set[MayHaveGetattr] = field(default_factory=set)
243
+ allowed_getattr_external: set[tuple[str, ...] | str] = field(default_factory=set)
244
+
245
+ allowed_operations: set = field(default_factory=set)
246
+ allowed_operations_external: set[tuple[str, ...] | str] = field(default_factory=set)
247
+
248
+ allow_getitem_on_types: bool = field(default_factory=bool)
249
+
250
+ _operation_methods_cache: dict[str, set[Callable]] = field(
251
+ default_factory=dict, init=False
252
+ )
253
+
254
+ def can_get_attr(self, value, attr):
255
+ allowed_getattr_external = _coerce_path_to_tuples(self.allowed_getattr_external)
256
+
257
+ has_original_attribute = _has_original_dunder(
258
+ value,
259
+ allowed_types=self.allowed_getattr,
260
+ allowed_methods=self._getattribute_methods,
261
+ allowed_external=allowed_getattr_external,
262
+ method_name="__getattribute__",
263
+ )
264
+ has_original_attr = _has_original_dunder(
265
+ value,
266
+ allowed_types=self.allowed_getattr,
267
+ allowed_methods=self._getattr_methods,
268
+ allowed_external=allowed_getattr_external,
269
+ method_name="__getattr__",
270
+ )
271
+
272
+ accept = False
273
+
274
+ # Many objects do not have `__getattr__`, this is fine.
275
+ if has_original_attr is None and has_original_attribute:
276
+ accept = True
277
+ else:
278
+ # Accept objects without modifications to `__getattr__` and `__getattribute__`
279
+ accept = has_original_attr and has_original_attribute
280
+
281
+ if accept:
282
+ # We still need to check for overridden properties.
283
+
284
+ value_class = type(value)
285
+ if not hasattr(value_class, attr):
286
+ return True
287
+
288
+ class_attr_val = getattr(value_class, attr)
289
+ is_property = isinstance(class_attr_val, property)
290
+
291
+ if not is_property:
292
+ return True
293
+
294
+ # Properties in allowed types are ok (although we do not include any
295
+ # properties in our default allow list currently).
296
+ if type(value) in self.allowed_getattr:
297
+ return True # pragma: no cover
298
+
299
+ # Properties in subclasses of allowed types may be ok if not changed
300
+ for module_name, *access_path in allowed_getattr_external:
301
+ try:
302
+ external_class = _get_external(module_name, access_path)
303
+ external_class_attr_val = getattr(external_class, attr)
304
+ except (KeyError, AttributeError):
305
+ return False # pragma: no cover
306
+ return class_attr_val == external_class_attr_val
307
+
308
+ return False
309
+
310
+ def can_get_item(self, value, item):
311
+ """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified."""
312
+ allowed_getitem_external = _coerce_path_to_tuples(self.allowed_getitem_external)
313
+ if self.allow_getitem_on_types:
314
+ # e.g. Union[str, int] or Literal[True, 1]
315
+ if isinstance(value, (typing._SpecialForm, typing._BaseGenericAlias)):
316
+ return True
317
+ # PEP 560 e.g. list[str]
318
+ if isinstance(value, type) and hasattr(value, "__class_getitem__"):
319
+ return True
320
+ return _has_original_dunder(
321
+ value,
322
+ allowed_types=self.allowed_getitem,
323
+ allowed_methods=self._getitem_methods,
324
+ allowed_external=allowed_getitem_external,
325
+ method_name="__getitem__",
326
+ )
327
+
328
+ def can_operate(self, dunders: tuple[str, ...], a, b=None):
329
+ allowed_operations_external = _coerce_path_to_tuples(
330
+ self.allowed_operations_external
331
+ )
332
+ objects = [a]
333
+ if b is not None:
334
+ objects.append(b)
335
+ return all(
336
+ [
337
+ _has_original_dunder(
338
+ obj,
339
+ allowed_types=self.allowed_operations,
340
+ allowed_methods=self._operator_dunder_methods(dunder),
341
+ allowed_external=allowed_operations_external,
342
+ method_name=dunder,
343
+ )
344
+ for dunder in dunders
345
+ for obj in objects
346
+ ]
347
+ )
348
+
349
+ def _operator_dunder_methods(self, dunder: str) -> set[Callable]:
350
+ if dunder not in self._operation_methods_cache:
351
+ self._operation_methods_cache[dunder] = self._safe_get_methods(
352
+ self.allowed_operations, dunder
353
+ )
354
+ return self._operation_methods_cache[dunder]
355
+
356
+ @cached_property
357
+ def _getitem_methods(self) -> set[Callable]:
358
+ return self._safe_get_methods(self.allowed_getitem, "__getitem__")
359
+
360
+ @cached_property
361
+ def _getattr_methods(self) -> set[Callable]:
362
+ return self._safe_get_methods(self.allowed_getattr, "__getattr__")
363
+
364
+ @cached_property
365
+ def _getattribute_methods(self) -> set[Callable]:
366
+ return self._safe_get_methods(self.allowed_getattr, "__getattribute__")
367
+
368
+ def _safe_get_methods(self, classes, name) -> set[Callable]:
369
+ return {
370
+ method
371
+ for class_ in classes
372
+ for method in [getattr(class_, name, None)]
373
+ if method
374
+ }
375
+
376
+
377
+ class _DummyNamedTuple(NamedTuple):
378
+ """Used internally to retrieve methods of named tuple instance."""
379
+
380
+
381
+ EvaluationPolicyName = Literal["forbidden", "minimal", "limited", "unsafe", "dangerous"]
382
+
383
+
384
+ @dataclass
385
+ class EvaluationContext:
386
+ #: Local namespace
387
+ locals: dict
388
+ #: Global namespace
389
+ globals: dict
390
+ #: Evaluation policy identifier
391
+ evaluation: EvaluationPolicyName = "forbidden"
392
+ #: Whether the evaluation of code takes place inside of a subscript.
393
+ #: Useful for evaluating ``:-1, 'col'`` in ``df[:-1, 'col']``.
394
+ in_subscript: bool = False
395
+ #: Auto import method
396
+ auto_import: Callable[[Sequence[str]], ModuleType] | None = None
397
+ #: Overrides for evaluation policy
398
+ policy_overrides: dict = field(default_factory=dict)
399
+ #: Transient local namespace used to store mocks
400
+ transient_locals: dict = field(default_factory=dict)
401
+ #: Transients of class level
402
+ class_transients: dict | None = None
403
+ #: Instance variable name used in the method definition
404
+ instance_arg_name: str | None = None
405
+ #: Currently associated value
406
+ #: Useful for adding items to _Duck on annotated assignment
407
+ current_value: ast.AST | None = None
408
+
409
+ def replace(self, /, **changes):
410
+ """Return a new copy of the context, with specified changes"""
411
+ return dataclasses.replace(self, **changes)
412
+
413
+
414
+ class _IdentitySubscript:
415
+ """Returns the key itself when item is requested via subscript."""
416
+
417
+ def __getitem__(self, key):
418
+ return key
419
+
420
+
421
+ IDENTITY_SUBSCRIPT = _IdentitySubscript()
422
+ SUBSCRIPT_MARKER = "__SUBSCRIPT_SENTINEL__"
423
+ UNKNOWN_SIGNATURE = Signature()
424
+ NOT_EVALUATED = object()
425
+
426
+
427
+ class GuardRejection(Exception):
428
+ """Exception raised when guard rejects evaluation attempt."""
429
+
430
+ pass
431
+
432
+
433
+ def guarded_eval(code: str, context: EvaluationContext):
434
+ """Evaluate provided code in the evaluation context.
435
+
436
+ If evaluation policy given by context is set to ``forbidden``
437
+ no evaluation will be performed; if it is set to ``dangerous``
438
+ standard :func:`eval` will be used; finally, for any other,
439
+ policy :func:`eval_node` will be called on parsed AST.
440
+ """
441
+ locals_ = context.locals
442
+
443
+ if context.evaluation == "forbidden":
444
+ raise GuardRejection("Forbidden mode")
445
+
446
+ # note: not using `ast.literal_eval` as it does not implement
447
+ # getitem at all, for example it fails on simple `[0][1]`
448
+
449
+ if context.in_subscript:
450
+ # syntactic sugar for ellipsis (:) is only available in subscripts
451
+ # so we need to trick the ast parser into thinking that we have
452
+ # a subscript, but we need to be able to later recognise that we did
453
+ # it so we can ignore the actual __getitem__ operation
454
+ if not code:
455
+ return tuple()
456
+ locals_ = locals_.copy()
457
+ locals_[SUBSCRIPT_MARKER] = IDENTITY_SUBSCRIPT
458
+ code = SUBSCRIPT_MARKER + "[" + code + "]"
459
+ context = context.replace(locals=locals_)
460
+
461
+ if context.evaluation == "dangerous":
462
+ return eval(code, context.globals, context.locals)
463
+
464
+ node = ast.parse(code, mode="exec")
465
+
466
+ return eval_node(node, context)
467
+
468
+
469
+ BINARY_OP_DUNDERS: dict[type[ast.operator], tuple[str]] = {
470
+ ast.Add: ("__add__",),
471
+ ast.Sub: ("__sub__",),
472
+ ast.Mult: ("__mul__",),
473
+ ast.Div: ("__truediv__",),
474
+ ast.FloorDiv: ("__floordiv__",),
475
+ ast.Mod: ("__mod__",),
476
+ ast.Pow: ("__pow__",),
477
+ ast.LShift: ("__lshift__",),
478
+ ast.RShift: ("__rshift__",),
479
+ ast.BitOr: ("__or__",),
480
+ ast.BitXor: ("__xor__",),
481
+ ast.BitAnd: ("__and__",),
482
+ ast.MatMult: ("__matmul__",),
483
+ }
484
+
485
+ COMP_OP_DUNDERS: dict[type[ast.cmpop], tuple[str, ...]] = {
486
+ ast.Eq: ("__eq__",),
487
+ ast.NotEq: ("__ne__", "__eq__"),
488
+ ast.Lt: ("__lt__", "__gt__"),
489
+ ast.LtE: ("__le__", "__ge__"),
490
+ ast.Gt: ("__gt__", "__lt__"),
491
+ ast.GtE: ("__ge__", "__le__"),
492
+ ast.In: ("__contains__",),
493
+ # Note: ast.Is, ast.IsNot, ast.NotIn are handled specially
494
+ }
495
+
496
+ UNARY_OP_DUNDERS: dict[type[ast.unaryop], tuple[str, ...]] = {
497
+ ast.USub: ("__neg__",),
498
+ ast.UAdd: ("__pos__",),
499
+ # we have to check both __inv__ and __invert__!
500
+ ast.Invert: ("__invert__", "__inv__"),
501
+ ast.Not: ("__not__",),
502
+ }
503
+
504
+ GENERIC_CONTAINER_TYPES = (dict, list, set, tuple, frozenset)
505
+
506
+
507
+ class ImpersonatingDuck:
508
+ """A dummy class used to create objects of other classes without calling their ``__init__``"""
509
+
510
+ # no-op: override __class__ to impersonate
511
+
512
+
513
+ class _Duck:
514
+ """A dummy class used to create objects pretending to have given attributes"""
515
+
516
+ def __init__(self, attributes: Optional[dict] = None, items: Optional[dict] = None):
517
+ self.attributes = attributes if attributes is not None else {}
518
+ self.items = items if items is not None else {}
519
+
520
+ def __getattr__(self, attr: str):
521
+ return self.attributes[attr]
522
+
523
+ def __hasattr__(self, attr: str):
524
+ return attr in self.attributes
525
+
526
+ def __dir__(self):
527
+ return [*dir(super), *self.attributes]
528
+
529
+ def __getitem__(self, key: str):
530
+ return self.items[key]
531
+
532
+ def __hasitem__(self, key: str):
533
+ return self.items[key]
534
+
535
+ def _ipython_key_completions_(self):
536
+ return self.items.keys()
537
+
538
+
539
+ def _find_dunder(node_op, dunders) -> Union[tuple[str, ...], None]:
540
+ dunder = None
541
+ for op, candidate_dunder in dunders.items():
542
+ if isinstance(node_op, op):
543
+ dunder = candidate_dunder
544
+ return dunder
545
+
546
+
547
+ def get_policy(context: EvaluationContext) -> EvaluationPolicy:
548
+ policy = copy(EVALUATION_POLICIES[context.evaluation])
549
+
550
+ for key, value in context.policy_overrides.items():
551
+ if hasattr(policy, key):
552
+ setattr(policy, key, value)
553
+ return policy
554
+
555
+
556
+ def _validate_policy_overrides(
557
+ policy_name: EvaluationPolicyName, policy_overrides: dict
558
+ ) -> bool:
559
+ policy = EVALUATION_POLICIES[policy_name]
560
+
561
+ all_good = True
562
+ for key, value in policy_overrides.items():
563
+ if not hasattr(policy, key):
564
+ warnings.warn(
565
+ f"Override {key!r} is not valid with {policy_name!r} evaluation policy"
566
+ )
567
+ all_good = False
568
+ return all_good
569
+
570
+
571
+ def _is_type_annotation(obj) -> bool:
572
+ """
573
+ Returns True if obj is a type annotation, False otherwise.
574
+ """
575
+ if isinstance(obj, type):
576
+ return True
577
+ if isinstance(obj, types.GenericAlias):
578
+ return True
579
+ if hasattr(types, "UnionType") and isinstance(obj, types.UnionType):
580
+ return True
581
+ if isinstance(obj, (typing._SpecialForm, typing._BaseGenericAlias)):
582
+ return True
583
+ if isinstance(obj, typing.TypeVar):
584
+ return True
585
+ # Types that support __class_getitem__
586
+ if isinstance(obj, type) and hasattr(obj, "__class_getitem__"):
587
+ return True
588
+ # Fallback: check if get_origin returns something
589
+ if hasattr(typing, "get_origin") and get_origin(obj) is not None:
590
+ return True
591
+
592
+ return False
593
+
594
+
595
+ def _handle_assign(node: ast.Assign, context: EvaluationContext):
596
+ value = eval_node(node.value, context)
597
+ transient_locals = context.transient_locals
598
+ policy = get_policy(context)
599
+ class_transients = context.class_transients
600
+ for target in node.targets:
601
+ if isinstance(target, (ast.Tuple, ast.List)):
602
+ # Handle unpacking assignment
603
+ values = list(value)
604
+ targets = target.elts
605
+ starred = [i for i, t in enumerate(targets) if isinstance(t, ast.Starred)]
606
+
607
+ # Unified handling: treat no starred as starred at end
608
+ star_or_last_idx = starred[0] if starred else len(targets)
609
+
610
+ # Before starred
611
+ for i in range(star_or_last_idx):
612
+ # Check for self.x assignment
613
+ if _is_instance_attribute_assignment(targets[i], context):
614
+ class_transients[targets[i].attr] = values[i]
615
+ else:
616
+ transient_locals[targets[i].id] = values[i]
617
+
618
+ # Starred if exists
619
+ if starred:
620
+ end = len(values) - (len(targets) - star_or_last_idx - 1)
621
+ if _is_instance_attribute_assignment(
622
+ targets[star_or_last_idx], context
623
+ ):
624
+ class_transients[targets[star_or_last_idx].attr] = values[
625
+ star_or_last_idx:end
626
+ ]
627
+ else:
628
+ transient_locals[targets[star_or_last_idx].value.id] = values[
629
+ star_or_last_idx:end
630
+ ]
631
+
632
+ # After starred
633
+ for i in range(star_or_last_idx + 1, len(targets)):
634
+ if _is_instance_attribute_assignment(targets[i], context):
635
+ class_transients[targets[i].attr] = values[
636
+ len(values) - (len(targets) - i)
637
+ ]
638
+ else:
639
+ transient_locals[targets[i].id] = values[
640
+ len(values) - (len(targets) - i)
641
+ ]
642
+ elif isinstance(target, ast.Subscript):
643
+ if isinstance(target.value, ast.Name):
644
+ name = target.value.id
645
+ container = transient_locals.get(name)
646
+ if container is None:
647
+ container = context.locals.get(name)
648
+ if container is None:
649
+ container = context.globals.get(name)
650
+ if container is None:
651
+ raise NameError(
652
+ f"{name} not found in locals, globals, nor builtins"
653
+ )
654
+ storage_dict = transient_locals
655
+ storage_key = name
656
+ elif isinstance(
657
+ target.value, ast.Attribute
658
+ ) and _is_instance_attribute_assignment(target.value, context):
659
+ attr = target.value.attr
660
+ container = class_transients.get(attr, None)
661
+ if container is None:
662
+ raise NameError(f"{attr} not found in class transients")
663
+ storage_dict = class_transients
664
+ storage_key = attr
665
+ else:
666
+ return
667
+
668
+ key = eval_node(target.slice, context)
669
+ attributes = (
670
+ dict.fromkeys(dir(container))
671
+ if policy.can_call(container.__dir__)
672
+ else {}
673
+ )
674
+ items = {}
675
+
676
+ if policy.can_get_item(container, None):
677
+ try:
678
+ items = dict(container.items())
679
+ except Exception:
680
+ pass
681
+
682
+ items[key] = value
683
+ duck_container = _Duck(attributes=attributes, items=items)
684
+ storage_dict[storage_key] = duck_container
685
+ elif _is_instance_attribute_assignment(target, context):
686
+ class_transients[target.attr] = value
687
+ else:
688
+ transient_locals[target.id] = value
689
+ return None
690
+
691
+
692
+ def _handle_annassign(node, context):
693
+ context_with_value = context.replace(current_value=getattr(node, "value", None))
694
+ annotation_result = eval_node(node.annotation, context_with_value)
695
+ if _is_type_annotation(annotation_result):
696
+ annotation_value = _resolve_annotation(annotation_result, context)
697
+ # Use Value for generic types
698
+ use_value = (
699
+ isinstance(annotation_value, GENERIC_CONTAINER_TYPES) and node.value is not None
700
+ )
701
+ else:
702
+ annotation_value = annotation_result
703
+ use_value = False
704
+
705
+ # LOCAL VARIABLE
706
+ if getattr(node, "simple", False) and isinstance(node.target, ast.Name):
707
+ name = node.target.id
708
+ if use_value:
709
+ return _handle_assign(
710
+ ast.Assign(targets=[node.target], value=node.value), context
711
+ )
712
+ context.transient_locals[name] = annotation_value
713
+ return None
714
+
715
+ # INSTANCE ATTRIBUTE
716
+ if _is_instance_attribute_assignment(node.target, context):
717
+ attr = node.target.attr
718
+ if use_value:
719
+ return _handle_assign(
720
+ ast.Assign(targets=[node.target], value=node.value), context
721
+ )
722
+ context.class_transients[attr] = annotation_value
723
+ return None
724
+
725
+ return None
726
+
727
+ def _extract_args_and_kwargs(node: ast.Call, context: EvaluationContext):
728
+ args = [eval_node(arg, context) for arg in node.args]
729
+ kwargs = {
730
+ k: v
731
+ for kw in node.keywords
732
+ for k, v in (
733
+ {kw.arg: eval_node(kw.value, context)}
734
+ if kw.arg
735
+ else eval_node(kw.value, context)
736
+ ).items()
737
+ }
738
+ return args, kwargs
739
+
740
+
741
+ def _is_instance_attribute_assignment(
742
+ target: ast.AST, context: EvaluationContext
743
+ ) -> bool:
744
+ """Return True if target is an attribute access on the instance argument."""
745
+ return (
746
+ context.class_transients is not None
747
+ and context.instance_arg_name is not None
748
+ and isinstance(target, ast.Attribute)
749
+ and isinstance(getattr(target, "value", None), ast.Name)
750
+ and getattr(target.value, "id", None) == context.instance_arg_name
751
+ )
752
+
753
+
754
+ def _get_coroutine_attributes() -> dict[str, Optional[object]]:
755
+ async def _dummy():
756
+ return None
757
+
758
+ coro = _dummy()
759
+ try:
760
+ return {attr: getattr(coro, attr, None) for attr in dir(coro)}
761
+ finally:
762
+ coro.close()
763
+
764
+
765
+ def eval_node(node: Union[ast.AST, None], context: EvaluationContext):
766
+ """Evaluate AST node in provided context.
767
+
768
+ Applies evaluation restrictions defined in the context. Currently does not support evaluation of functions with keyword arguments.
769
+
770
+ Does not evaluate actions that always have side effects:
771
+
772
+ - class definitions (``class sth: ...``)
773
+ - function definitions (``def sth: ...``)
774
+ - variable assignments (``x = 1``)
775
+ - augmented assignments (``x += 1``)
776
+ - deletions (``del x``)
777
+
778
+ Does not evaluate operations which do not return values:
779
+
780
+ - assertions (``assert x``)
781
+ - pass (``pass``)
782
+ - imports (``import x``)
783
+ - control flow:
784
+
785
+ - conditionals (``if x:``) except for ternary IfExp (``a if x else b``)
786
+ - loops (``for`` and ``while``)
787
+ - exception handling
788
+
789
+ The purpose of this function is to guard against unwanted side-effects;
790
+ it does not give guarantees on protection from malicious code execution.
791
+ """
792
+ policy = get_policy(context)
793
+
794
+ if node is None:
795
+ return None
796
+ if isinstance(node, (ast.Interactive, ast.Module)):
797
+ result = None
798
+ for child_node in node.body:
799
+ result = eval_node(child_node, context)
800
+ return result
801
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
802
+ is_async = isinstance(node, ast.AsyncFunctionDef)
803
+ func_locals = context.transient_locals.copy()
804
+ func_context = context.replace(transient_locals=func_locals)
805
+ is_property = False
806
+ is_static = False
807
+ is_classmethod = False
808
+ for decorator_node in node.decorator_list:
809
+ try:
810
+ decorator = eval_node(decorator_node, context)
811
+ except NameError:
812
+ # if the decorator is not yet defined this is fine
813
+ # especialy because we don't handle imports yet
814
+ continue
815
+ if decorator is property:
816
+ is_property = True
817
+ elif decorator is staticmethod:
818
+ is_static = True
819
+ elif decorator is classmethod:
820
+ is_classmethod = True
821
+
822
+ if func_context.class_transients is not None:
823
+ if not is_static and not is_classmethod:
824
+ func_context.instance_arg_name = (
825
+ node.args.args[0].arg if node.args.args else None
826
+ )
827
+
828
+ return_type = eval_node(node.returns, context=context)
829
+
830
+ for child_node in node.body:
831
+ eval_node(child_node, func_context)
832
+
833
+ if is_property:
834
+ if return_type is not None:
835
+ if _is_type_annotation(return_type):
836
+ context.transient_locals[node.name] = _resolve_annotation(
837
+ return_type, context
838
+ )
839
+ else:
840
+ context.transient_locals[node.name] = return_type
841
+ else:
842
+ return_value = _infer_return_value(node, func_context)
843
+ context.transient_locals[node.name] = return_value
844
+
845
+ return None
846
+
847
+ def dummy_function(*args, **kwargs):
848
+ pass
849
+
850
+ if return_type is not None:
851
+ if _is_type_annotation(return_type):
852
+ dummy_function.__annotations__["return"] = return_type
853
+ else:
854
+ dummy_function.__inferred_return__ = return_type
855
+ else:
856
+ inferred_return = _infer_return_value(node, func_context)
857
+ if inferred_return is not None:
858
+ dummy_function.__inferred_return__ = inferred_return
859
+
860
+ dummy_function.__name__ = node.name
861
+ dummy_function.__node__ = node
862
+ dummy_function.__is_async__ = is_async
863
+ context.transient_locals[node.name] = dummy_function
864
+ return None
865
+ if isinstance(node, ast.Lambda):
866
+
867
+ def dummy_function(*args, **kwargs):
868
+ pass
869
+
870
+ dummy_function.__inferred_return__ = eval_node(node.body, context)
871
+ return dummy_function
872
+ if isinstance(node, ast.ClassDef):
873
+ # TODO support class decorators?
874
+ class_locals = {}
875
+ outer_locals = context.locals.copy()
876
+ outer_locals.update(context.transient_locals)
877
+ class_context = context.replace(
878
+ transient_locals=class_locals, locals=outer_locals
879
+ )
880
+ class_context.class_transients = class_locals
881
+ for child_node in node.body:
882
+ eval_node(child_node, class_context)
883
+ bases = tuple([eval_node(base, context) for base in node.bases])
884
+ dummy_class = type(node.name, bases, class_locals)
885
+ context.transient_locals[node.name] = dummy_class
886
+ return None
887
+ if isinstance(node, ast.Await):
888
+ value = eval_node(node.value, context)
889
+ if hasattr(value, "__awaited_type__"):
890
+ return value.__awaited_type__
891
+ return value
892
+ if isinstance(node, ast.While):
893
+ loop_locals = context.transient_locals.copy()
894
+ loop_context = context.replace(transient_locals=loop_locals)
895
+
896
+ result = None
897
+ for stmt in node.body:
898
+ result = eval_node(stmt, loop_context)
899
+
900
+ policy = get_policy(context)
901
+ merged_locals = _merge_dicts_by_key(
902
+ [loop_locals, context.transient_locals.copy()], policy
903
+ )
904
+ context.transient_locals.update(merged_locals)
905
+
906
+ return result
907
+ if isinstance(node, ast.For):
908
+ try:
909
+ iterable = eval_node(node.iter, context)
910
+ except Exception:
911
+ iterable = None
912
+
913
+ sample = None
914
+ if iterable is not None:
915
+ try:
916
+ if policy.can_call(getattr(iterable, "__iter__", None)):
917
+ sample = next(iter(iterable))
918
+ except Exception:
919
+ sample = None
920
+
921
+ loop_locals = context.transient_locals.copy()
922
+ loop_context = context.replace(transient_locals=loop_locals)
923
+
924
+ if sample is not None:
925
+ try:
926
+ fake_assign = ast.Assign(
927
+ targets=[node.target], value=ast.Constant(value=sample)
928
+ )
929
+ _handle_assign(fake_assign, loop_context)
930
+ except Exception:
931
+ pass
932
+
933
+ result = None
934
+ for stmt in node.body:
935
+ result = eval_node(stmt, loop_context)
936
+
937
+ policy = get_policy(context)
938
+ merged_locals = _merge_dicts_by_key(
939
+ [loop_locals, context.transient_locals.copy()], policy
940
+ )
941
+ context.transient_locals.update(merged_locals)
942
+
943
+ return result
944
+ if isinstance(node, ast.If):
945
+ branches = []
946
+ current = node
947
+ result = None
948
+ while True:
949
+ branch_locals = context.transient_locals.copy()
950
+ branch_context = context.replace(transient_locals=branch_locals)
951
+ for stmt in current.body:
952
+ result = eval_node(stmt, branch_context)
953
+ branches.append(branch_locals)
954
+ if not current.orelse:
955
+ break
956
+ elif len(current.orelse) == 1 and isinstance(current.orelse[0], ast.If):
957
+ # It's an elif - continue loop
958
+ current = current.orelse[0]
959
+ else:
960
+ # It's an else block - process and break
961
+ else_locals = context.transient_locals.copy()
962
+ else_context = context.replace(transient_locals=else_locals)
963
+ for stmt in current.orelse:
964
+ result = eval_node(stmt, else_context)
965
+ branches.append(else_locals)
966
+ break
967
+ branches.append(context.transient_locals.copy())
968
+ policy = get_policy(context)
969
+ merged_locals = _merge_dicts_by_key(branches, policy)
970
+ context.transient_locals.update(merged_locals)
971
+ return result
972
+ if isinstance(node, ast.Assign):
973
+ return _handle_assign(node, context)
974
+ if isinstance(node, ast.AnnAssign):
975
+ return _handle_annassign(node, context)
976
+ if isinstance(node, ast.Expression):
977
+ return eval_node(node.body, context)
978
+ if isinstance(node, ast.Expr):
979
+ return eval_node(node.value, context)
980
+ if isinstance(node, ast.Pass):
981
+ return None
982
+ if isinstance(node, ast.Import):
983
+ # TODO: populate transient_locals
984
+ return None
985
+ if isinstance(node, (ast.AugAssign, ast.Delete)):
986
+ return None
987
+ if isinstance(node, (ast.Global, ast.Nonlocal)):
988
+ return None
989
+ if isinstance(node, ast.BinOp):
990
+ left = eval_node(node.left, context)
991
+ right = eval_node(node.right, context)
992
+ if (
993
+ isinstance(node.op, ast.BitOr)
994
+ and _is_type_annotation(left)
995
+ and _is_type_annotation(right)
996
+ ):
997
+ left_duck = (
998
+ _Duck(dict.fromkeys(dir(left)))
999
+ if policy.can_call(left.__dir__)
1000
+ else _Duck()
1001
+ )
1002
+ right_duck = (
1003
+ _Duck(dict.fromkeys(dir(right)))
1004
+ if policy.can_call(right.__dir__)
1005
+ else _Duck()
1006
+ )
1007
+ value_node = context.current_value
1008
+ if value_node is not None and isinstance(value_node, ast.Dict):
1009
+ if dict in [left, right]:
1010
+ return _merge_values(
1011
+ [left_duck, right_duck, ast.literal_eval(value_node)],
1012
+ policy=get_policy(context),
1013
+ )
1014
+ return _merge_values([left_duck, right_duck], policy=get_policy(context))
1015
+ dunders = _find_dunder(node.op, BINARY_OP_DUNDERS)
1016
+ if dunders:
1017
+ if policy.can_operate(dunders, left, right):
1018
+ return getattr(left, dunders[0])(right)
1019
+ else:
1020
+ raise GuardRejection(
1021
+ f"Operation (`{dunders}`) for",
1022
+ type(left),
1023
+ f"not allowed in {context.evaluation} mode",
1024
+ )
1025
+ if isinstance(node, ast.Compare):
1026
+ left = eval_node(node.left, context)
1027
+ all_true = True
1028
+ negate = False
1029
+ for op, right in zip(node.ops, node.comparators):
1030
+ right = eval_node(right, context)
1031
+ dunder = None
1032
+ dunders = _find_dunder(op, COMP_OP_DUNDERS)
1033
+ if not dunders:
1034
+ if isinstance(op, ast.NotIn):
1035
+ dunders = COMP_OP_DUNDERS[ast.In]
1036
+ negate = True
1037
+ if isinstance(op, ast.Is):
1038
+ dunder = "is_"
1039
+ if isinstance(op, ast.IsNot):
1040
+ dunder = "is_"
1041
+ negate = True
1042
+ if not dunder and dunders:
1043
+ dunder = dunders[0]
1044
+ if dunder:
1045
+ a, b = (right, left) if dunder == "__contains__" else (left, right)
1046
+ if dunder == "is_" or dunders and policy.can_operate(dunders, a, b):
1047
+ result = getattr(operator, dunder)(a, b)
1048
+ if negate:
1049
+ result = not result
1050
+ if not result:
1051
+ all_true = False
1052
+ left = right
1053
+ else:
1054
+ raise GuardRejection(
1055
+ f"Comparison (`{dunder}`) for",
1056
+ type(left),
1057
+ f"not allowed in {context.evaluation} mode",
1058
+ )
1059
+ else:
1060
+ raise ValueError(
1061
+ f"Comparison `{dunder}` not supported"
1062
+ ) # pragma: no cover
1063
+ return all_true
1064
+ if isinstance(node, ast.Constant):
1065
+ return node.value
1066
+ if isinstance(node, ast.Tuple):
1067
+ return tuple(eval_node(e, context) for e in node.elts)
1068
+ if isinstance(node, ast.List):
1069
+ return [eval_node(e, context) for e in node.elts]
1070
+ if isinstance(node, ast.Set):
1071
+ return {eval_node(e, context) for e in node.elts}
1072
+ if isinstance(node, ast.Dict):
1073
+ return dict(
1074
+ zip(
1075
+ [eval_node(k, context) for k in node.keys],
1076
+ [eval_node(v, context) for v in node.values],
1077
+ )
1078
+ )
1079
+ if isinstance(node, ast.Slice):
1080
+ return slice(
1081
+ eval_node(node.lower, context),
1082
+ eval_node(node.upper, context),
1083
+ eval_node(node.step, context),
1084
+ )
1085
+ if isinstance(node, ast.UnaryOp):
1086
+ value = eval_node(node.operand, context)
1087
+ dunders = _find_dunder(node.op, UNARY_OP_DUNDERS)
1088
+ if dunders:
1089
+ if policy.can_operate(dunders, value):
1090
+ try:
1091
+ return getattr(value, dunders[0])()
1092
+ except AttributeError:
1093
+ raise TypeError(
1094
+ f"bad operand type for unary {node.op}: {type(value)}"
1095
+ )
1096
+ else:
1097
+ raise GuardRejection(
1098
+ f"Operation (`{dunders}`) for",
1099
+ type(value),
1100
+ f"not allowed in {context.evaluation} mode",
1101
+ )
1102
+ if isinstance(node, ast.Subscript):
1103
+ value = eval_node(node.value, context)
1104
+ slice_ = eval_node(node.slice, context)
1105
+ if policy.can_get_item(value, slice_):
1106
+ return value[slice_]
1107
+ raise GuardRejection(
1108
+ "Subscript access (`__getitem__`) for",
1109
+ type(value), # not joined to avoid calling `repr`
1110
+ f" not allowed in {context.evaluation} mode",
1111
+ )
1112
+ if isinstance(node, ast.Name):
1113
+ return _eval_node_name(node.id, context)
1114
+ if isinstance(node, ast.Attribute):
1115
+ if (
1116
+ context.class_transients is not None
1117
+ and isinstance(node.value, ast.Name)
1118
+ and node.value.id == context.instance_arg_name
1119
+ ):
1120
+ return context.class_transients.get(node.attr)
1121
+ value = eval_node(node.value, context)
1122
+ if policy.can_get_attr(value, node.attr):
1123
+ return getattr(value, node.attr)
1124
+ try:
1125
+ cls = (
1126
+ value if isinstance(value, type) else getattr(value, "__class__", None)
1127
+ )
1128
+ if cls is not None:
1129
+ resolved_hints = get_type_hints(
1130
+ cls,
1131
+ globalns=(context.globals or {}),
1132
+ localns=(context.locals or {}),
1133
+ )
1134
+ if node.attr in resolved_hints:
1135
+ annotated = resolved_hints[node.attr]
1136
+ return _resolve_annotation(annotated, context)
1137
+ except Exception:
1138
+ # Fall through to the guard rejection
1139
+ pass
1140
+ raise GuardRejection(
1141
+ "Attribute access (`__getattr__`) for",
1142
+ type(value), # not joined to avoid calling `repr`
1143
+ f"not allowed in {context.evaluation} mode",
1144
+ )
1145
+ if isinstance(node, ast.IfExp):
1146
+ test = eval_node(node.test, context)
1147
+ if test:
1148
+ return eval_node(node.body, context)
1149
+ else:
1150
+ return eval_node(node.orelse, context)
1151
+ if isinstance(node, ast.Call):
1152
+ func = eval_node(node.func, context)
1153
+ if policy.can_call(func):
1154
+ args, kwargs = _extract_args_and_kwargs(node, context)
1155
+ return func(*args, **kwargs)
1156
+ if isclass(func):
1157
+ # this code path gets entered when calling class e.g. `MyClass()`
1158
+ # or `my_instance.__class__()` - in both cases `func` is `MyClass`.
1159
+ # Should return `MyClass` if `__new__` is not overridden,
1160
+ # otherwise whatever `__new__` return type is.
1161
+ overridden_return_type = _eval_return_type(func.__new__, node, context)
1162
+ if overridden_return_type is not NOT_EVALUATED:
1163
+ return overridden_return_type
1164
+ return _create_duck_for_heap_type(func)
1165
+ else:
1166
+ inferred_return = getattr(func, "__inferred_return__", NOT_EVALUATED)
1167
+ return_type = _eval_return_type(func, node, context)
1168
+ if getattr(func, "__is_async__", False):
1169
+ awaited_type = (
1170
+ inferred_return if inferred_return is not None else return_type
1171
+ )
1172
+ coroutine_duck = _Duck(attributes=_get_coroutine_attributes())
1173
+ coroutine_duck.__awaited_type__ = awaited_type
1174
+ return coroutine_duck
1175
+ if inferred_return is not NOT_EVALUATED:
1176
+ return inferred_return
1177
+ if return_type is not NOT_EVALUATED:
1178
+ return return_type
1179
+ raise GuardRejection(
1180
+ "Call for",
1181
+ func, # not joined to avoid calling `repr`
1182
+ f"not allowed in {context.evaluation} mode",
1183
+ )
1184
+ if isinstance(node, ast.Assert):
1185
+ # message is always the second item, so if it is defined user would be completing
1186
+ # on the message, not on the assertion test
1187
+ if node.msg:
1188
+ return eval_node(node.msg, context)
1189
+ return eval_node(node.test, context)
1190
+ return None
1191
+
1192
+
1193
+ def _merge_dicts_by_key(dicts: list, policy: EvaluationPolicy):
1194
+ """Merge multiple dictionaries, combining values for each key."""
1195
+ if len(dicts) == 1:
1196
+ return dicts[0]
1197
+
1198
+ all_keys = set()
1199
+ for d in dicts:
1200
+ all_keys.update(d.keys())
1201
+
1202
+ merged = {}
1203
+ for key in all_keys:
1204
+ values = [d[key] for d in dicts if key in d]
1205
+ if values:
1206
+ merged[key] = _merge_values(values, policy)
1207
+
1208
+ return merged
1209
+
1210
+
1211
+ def _merge_values(values, policy: EvaluationPolicy):
1212
+ """Recursively merge multiple values, combining attributes and dict items."""
1213
+ if len(values) == 1:
1214
+ return values[0]
1215
+
1216
+ types = {type(v) for v in values}
1217
+ merged_items = None
1218
+ key_values = {}
1219
+ attributes = set()
1220
+ for v in values:
1221
+ if policy.can_call(v.__dir__):
1222
+ attributes.update(dir(v))
1223
+ try:
1224
+ if policy.can_call(v.items):
1225
+ try:
1226
+ for k, val in v.items():
1227
+ key_values.setdefault(k, []).append(val)
1228
+ except Exception as e:
1229
+ pass
1230
+ elif policy.can_call(v.keys):
1231
+ try:
1232
+ for k in v.keys():
1233
+ key_values.setdefault(k, []).append(None)
1234
+ except Exception as e:
1235
+ pass
1236
+ except Exception as e:
1237
+ pass
1238
+
1239
+ if key_values:
1240
+ merged_items = {
1241
+ k: _merge_values(vals, policy) if vals[0] is not None else None
1242
+ for k, vals in key_values.items()
1243
+ }
1244
+
1245
+ if len(types) == 1:
1246
+ t = next(iter(types))
1247
+ if t not in (dict,) and not (
1248
+ hasattr(next(iter(values)), "__getitem__")
1249
+ and (
1250
+ hasattr(next(iter(values)), "items")
1251
+ or hasattr(next(iter(values)), "keys")
1252
+ )
1253
+ ):
1254
+ if t in (list, set, tuple):
1255
+ return t
1256
+ return values[0]
1257
+
1258
+ return _Duck(attributes=dict.fromkeys(attributes), items=merged_items)
1259
+
1260
+
1261
+ def _infer_return_value(node: ast.FunctionDef, context: EvaluationContext):
1262
+ """Infer the return value(s) of a function by evaluating all return statements."""
1263
+ return_values = _collect_return_values(node.body, context)
1264
+
1265
+ if not return_values:
1266
+ return None
1267
+ if len(return_values) == 1:
1268
+ return return_values[0]
1269
+
1270
+ policy = get_policy(context)
1271
+ return _merge_values(return_values, policy)
1272
+
1273
+
1274
+ def _collect_return_values(body, context):
1275
+ """Recursively collect return values from a list of AST statements."""
1276
+ return_values = []
1277
+ for stmt in body:
1278
+ if isinstance(stmt, ast.Return):
1279
+ if stmt.value is None:
1280
+ continue
1281
+ try:
1282
+ value = eval_node(stmt.value, context)
1283
+ if value is not None and value is not NOT_EVALUATED:
1284
+ return_values.append(value)
1285
+ except Exception:
1286
+ pass
1287
+ if isinstance(
1288
+ stmt, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Lambda)
1289
+ ):
1290
+ continue
1291
+ elif hasattr(stmt, "body") and isinstance(stmt.body, list):
1292
+ return_values.extend(_collect_return_values(stmt.body, context))
1293
+ if isinstance(stmt, ast.Try):
1294
+ for h in stmt.handlers:
1295
+ if hasattr(h, "body"):
1296
+ return_values.extend(_collect_return_values(h.body, context))
1297
+ if hasattr(stmt, "orelse"):
1298
+ return_values.extend(_collect_return_values(stmt.orelse, context))
1299
+ if hasattr(stmt, "finalbody"):
1300
+ return_values.extend(_collect_return_values(stmt.finalbody, context))
1301
+ if hasattr(stmt, "orelse") and isinstance(stmt.orelse, list):
1302
+ return_values.extend(_collect_return_values(stmt.orelse, context))
1303
+ return return_values
1304
+
1305
+
1306
+ def _eval_return_type(func: Callable, node: ast.Call, context: EvaluationContext):
1307
+ """Evaluate return type of a given callable function.
1308
+
1309
+ Returns the built-in type, a duck or NOT_EVALUATED sentinel.
1310
+ """
1311
+ try:
1312
+ sig = signature(func)
1313
+ except ValueError:
1314
+ sig = UNKNOWN_SIGNATURE
1315
+ # if annotation was not stringized, or it was stringized
1316
+ # but resolved by signature call we know the return type
1317
+ not_empty = sig.return_annotation is not Signature.empty
1318
+ if not_empty:
1319
+ return _resolve_annotation(sig.return_annotation, context, sig, func, node)
1320
+ return NOT_EVALUATED
1321
+
1322
+
1323
+ def _eval_annotation(
1324
+ annotation: str,
1325
+ context: EvaluationContext,
1326
+ ):
1327
+ return (
1328
+ _eval_node_name(annotation, context)
1329
+ if isinstance(annotation, str)
1330
+ else annotation
1331
+ )
1332
+
1333
+
1334
+ class _GetItemDuck(dict):
1335
+ """A dict subclass that always returns the factory instance and claims to have any item."""
1336
+
1337
+ def __init__(self, factory, *args, **kwargs):
1338
+ super().__init__(*args, **kwargs)
1339
+ self._factory = factory
1340
+
1341
+ def __getitem__(self, key):
1342
+ return self._factory()
1343
+
1344
+ def __contains__(self, key):
1345
+ return True
1346
+
1347
+
1348
+ def _resolve_annotation(
1349
+ annotation: object | str,
1350
+ context: EvaluationContext,
1351
+ sig: Signature | None = None,
1352
+ func: Callable | None = None,
1353
+ node: ast.Call | None = None,
1354
+ ):
1355
+ """Resolve annotation created by user with `typing` module and custom objects."""
1356
+ if annotation is None:
1357
+ return None
1358
+ annotation = _eval_annotation(annotation, context)
1359
+ origin = get_origin(annotation)
1360
+ if annotation is Self and func and hasattr(func, "__self__"):
1361
+ return func.__self__
1362
+ elif origin is Literal:
1363
+ type_args = get_args(annotation)
1364
+ if len(type_args) == 1:
1365
+ return type_args[0]
1366
+ elif annotation is LiteralString:
1367
+ return ""
1368
+ elif annotation is AnyStr:
1369
+ index = None
1370
+ if func and hasattr(func, "__node__"):
1371
+ def_node = func.__node__
1372
+ for i, arg in enumerate(def_node.args.args):
1373
+ if not arg.annotation:
1374
+ continue
1375
+ annotation = _eval_annotation(arg.annotation.id, context)
1376
+ if annotation is AnyStr:
1377
+ index = i
1378
+ break
1379
+ is_bound_method = (
1380
+ isinstance(func, MethodType) and getattr(func, "__self__") is not None
1381
+ )
1382
+ if index and is_bound_method:
1383
+ index -= 1
1384
+ elif sig:
1385
+ for i, (key, value) in enumerate(sig.parameters.items()):
1386
+ if value.annotation is AnyStr:
1387
+ index = i
1388
+ break
1389
+ if index is None:
1390
+ return None
1391
+ if index < 0 or index >= len(node.args):
1392
+ return None
1393
+ return eval_node(node.args[index], context)
1394
+ elif origin is TypeGuard:
1395
+ return False
1396
+ elif origin is set or origin is list:
1397
+ # only one type argument allowed
1398
+ attributes = [
1399
+ attr
1400
+ for attr in dir(
1401
+ _resolve_annotation(get_args(annotation)[0], context, sig, func, node)
1402
+ )
1403
+ ]
1404
+ duck = _Duck(attributes=dict.fromkeys(attributes))
1405
+ return _Duck(
1406
+ attributes=dict.fromkeys(dir(origin())),
1407
+ # items are not strrictly needed for set
1408
+ items=_GetItemDuck(lambda: duck),
1409
+ )
1410
+ elif origin is tuple:
1411
+ # multiple type arguments
1412
+ return tuple(
1413
+ _resolve_annotation(arg, context, sig, func, node)
1414
+ for arg in get_args(annotation)
1415
+ )
1416
+ elif origin is Union:
1417
+ # multiple type arguments
1418
+ attributes = [
1419
+ attr
1420
+ for type_arg in get_args(annotation)
1421
+ for attr in dir(_resolve_annotation(type_arg, context, sig, func, node))
1422
+ ]
1423
+ return _Duck(attributes=dict.fromkeys(attributes))
1424
+ elif is_typeddict(annotation):
1425
+ return _Duck(
1426
+ attributes=dict.fromkeys(dir(dict())),
1427
+ items={
1428
+ k: _resolve_annotation(v, context, sig, func, node)
1429
+ for k, v in annotation.__annotations__.items()
1430
+ },
1431
+ )
1432
+ elif hasattr(annotation, "_is_protocol"):
1433
+ return _Duck(attributes=dict.fromkeys(dir(annotation)))
1434
+ elif origin is Annotated:
1435
+ type_arg = get_args(annotation)[0]
1436
+ return _resolve_annotation(type_arg, context, sig, func, node)
1437
+ elif isinstance(annotation, NewType):
1438
+ return _eval_or_create_duck(annotation.__supertype__, context)
1439
+ elif isinstance(annotation, TypeAliasType):
1440
+ return _eval_or_create_duck(annotation.__value__, context)
1441
+ else:
1442
+ return _eval_or_create_duck(annotation, context)
1443
+
1444
+
1445
+ def _eval_node_name(node_id: str, context: EvaluationContext):
1446
+ policy = get_policy(context)
1447
+ if node_id in context.transient_locals:
1448
+ return context.transient_locals[node_id]
1449
+ if policy.allow_locals_access and node_id in context.locals:
1450
+ return context.locals[node_id]
1451
+ if policy.allow_globals_access and node_id in context.globals:
1452
+ return context.globals[node_id]
1453
+ if policy.allow_builtins_access and hasattr(builtins, node_id):
1454
+ # note: do not use __builtins__, it is implementation detail of cPython
1455
+ return getattr(builtins, node_id)
1456
+ if policy.allow_auto_import and context.auto_import:
1457
+ return context.auto_import(node_id)
1458
+ if not policy.allow_globals_access and not policy.allow_locals_access:
1459
+ raise GuardRejection(
1460
+ f"Namespace access not allowed in {context.evaluation} mode"
1461
+ )
1462
+ else:
1463
+ raise NameError(f"{node_id} not found in locals, globals, nor builtins")
1464
+
1465
+
1466
+ def _eval_or_create_duck(duck_type, context: EvaluationContext):
1467
+ policy = get_policy(context)
1468
+ # if allow-listed builtin is on type annotation, instantiate it
1469
+ if policy.can_call(duck_type):
1470
+ return duck_type()
1471
+ # if custom class is in type annotation, mock it
1472
+ return _create_duck_for_heap_type(duck_type)
1473
+
1474
+
1475
+ def _create_duck_for_heap_type(duck_type):
1476
+ """Create an imitation of an object of a given type (a duck).
1477
+
1478
+ Returns the duck or NOT_EVALUATED sentinel if duck could not be created.
1479
+ """
1480
+ duck = ImpersonatingDuck()
1481
+ try:
1482
+ # this only works for heap types, not builtins
1483
+ duck.__class__ = duck_type
1484
+ return duck
1485
+ except TypeError:
1486
+ pass
1487
+ return NOT_EVALUATED
1488
+
1489
+
1490
+ SUPPORTED_EXTERNAL_GETITEM = {
1491
+ ("pandas", "core", "indexing", "_iLocIndexer"),
1492
+ ("pandas", "core", "indexing", "_LocIndexer"),
1493
+ ("pandas", "DataFrame"),
1494
+ ("pandas", "Series"),
1495
+ ("numpy", "ndarray"),
1496
+ ("numpy", "void"),
1497
+ }
1498
+
1499
+
1500
+ BUILTIN_GETITEM: set[InstancesHaveGetItem] = {
1501
+ dict,
1502
+ str, # type: ignore[arg-type]
1503
+ bytes, # type: ignore[arg-type]
1504
+ list,
1505
+ tuple,
1506
+ type, # for type annotations like list[str]
1507
+ _Duck,
1508
+ collections.defaultdict,
1509
+ collections.deque,
1510
+ collections.OrderedDict,
1511
+ collections.ChainMap,
1512
+ collections.UserDict,
1513
+ collections.UserList,
1514
+ collections.UserString, # type: ignore[arg-type]
1515
+ _DummyNamedTuple,
1516
+ _IdentitySubscript,
1517
+ }
1518
+
1519
+
1520
+ def _list_methods(cls, source=None):
1521
+ """For use on immutable objects or with methods returning a copy"""
1522
+ return [getattr(cls, k) for k in (source if source else dir(cls))]
1523
+
1524
+
1525
+ dict_non_mutating_methods = ("copy", "keys", "values", "items")
1526
+ list_non_mutating_methods = ("copy", "index", "count")
1527
+ set_non_mutating_methods = set(dir(set)) & set(dir(frozenset))
1528
+
1529
+
1530
+ dict_keys: type[collections.abc.KeysView] = type({}.keys())
1531
+ dict_values: type = type({}.values())
1532
+ dict_items: type = type({}.items())
1533
+
1534
+ NUMERICS = {int, float, complex}
1535
+
1536
+ ALLOWED_CALLS = {
1537
+ bytes,
1538
+ *_list_methods(bytes),
1539
+ bytes.__iter__,
1540
+ dict,
1541
+ *_list_methods(dict, dict_non_mutating_methods),
1542
+ dict.__iter__,
1543
+ dict_keys.__iter__,
1544
+ dict_values.__iter__,
1545
+ dict_items.__iter__,
1546
+ dict_keys.isdisjoint,
1547
+ list,
1548
+ *_list_methods(list, list_non_mutating_methods),
1549
+ list.__iter__,
1550
+ set,
1551
+ *_list_methods(set, set_non_mutating_methods),
1552
+ set.__iter__,
1553
+ frozenset,
1554
+ *_list_methods(frozenset),
1555
+ frozenset.__iter__,
1556
+ range,
1557
+ range.__iter__,
1558
+ str,
1559
+ *_list_methods(str),
1560
+ str.__iter__,
1561
+ tuple,
1562
+ *_list_methods(tuple),
1563
+ tuple.__iter__,
1564
+ bool,
1565
+ *_list_methods(bool),
1566
+ *NUMERICS,
1567
+ *[method for numeric_cls in NUMERICS for method in _list_methods(numeric_cls)],
1568
+ collections.deque,
1569
+ *_list_methods(collections.deque, list_non_mutating_methods),
1570
+ collections.deque.__iter__,
1571
+ collections.defaultdict,
1572
+ *_list_methods(collections.defaultdict, dict_non_mutating_methods),
1573
+ collections.defaultdict.__iter__,
1574
+ collections.OrderedDict,
1575
+ *_list_methods(collections.OrderedDict, dict_non_mutating_methods),
1576
+ collections.OrderedDict.__iter__,
1577
+ collections.UserDict,
1578
+ *_list_methods(collections.UserDict, dict_non_mutating_methods),
1579
+ collections.UserDict.__iter__,
1580
+ collections.UserList,
1581
+ *_list_methods(collections.UserList, list_non_mutating_methods),
1582
+ collections.UserList.__iter__,
1583
+ collections.UserString,
1584
+ *_list_methods(collections.UserString, dir(str)),
1585
+ collections.UserString.__iter__,
1586
+ collections.Counter,
1587
+ *_list_methods(collections.Counter, dict_non_mutating_methods),
1588
+ collections.Counter.__iter__,
1589
+ collections.Counter.elements,
1590
+ collections.Counter.most_common,
1591
+ object.__dir__,
1592
+ type.__dir__,
1593
+ _Duck.__dir__,
1594
+ }
1595
+
1596
+ BUILTIN_GETATTR: set[MayHaveGetattr] = {
1597
+ *BUILTIN_GETITEM,
1598
+ set,
1599
+ frozenset,
1600
+ object,
1601
+ type, # `type` handles a lot of generic cases, e.g. numbers as in `int.real`.
1602
+ *NUMERICS,
1603
+ dict_keys,
1604
+ MethodDescriptorType,
1605
+ ModuleType,
1606
+ }
1607
+
1608
+
1609
+ BUILTIN_OPERATIONS = {*BUILTIN_GETATTR}
1610
+
1611
+ EVALUATION_POLICIES = {
1612
+ "minimal": EvaluationPolicy(
1613
+ allow_builtins_access=True,
1614
+ allow_locals_access=False,
1615
+ allow_globals_access=False,
1616
+ allow_item_access=False,
1617
+ allow_attr_access=False,
1618
+ allowed_calls=set(),
1619
+ allow_any_calls=False,
1620
+ allow_all_operations=False,
1621
+ ),
1622
+ "limited": SelectivePolicy(
1623
+ allowed_getitem=BUILTIN_GETITEM,
1624
+ allowed_getitem_external=SUPPORTED_EXTERNAL_GETITEM,
1625
+ allowed_getattr=BUILTIN_GETATTR,
1626
+ allowed_getattr_external={
1627
+ # pandas Series/Frame implements custom `__getattr__`
1628
+ ("pandas", "DataFrame"),
1629
+ ("pandas", "Series"),
1630
+ },
1631
+ allowed_operations=BUILTIN_OPERATIONS,
1632
+ allow_builtins_access=True,
1633
+ allow_locals_access=True,
1634
+ allow_globals_access=True,
1635
+ allow_getitem_on_types=True,
1636
+ allowed_calls=ALLOWED_CALLS,
1637
+ ),
1638
+ "unsafe": EvaluationPolicy(
1639
+ allow_builtins_access=True,
1640
+ allow_locals_access=True,
1641
+ allow_globals_access=True,
1642
+ allow_attr_access=True,
1643
+ allow_item_access=True,
1644
+ allow_any_calls=True,
1645
+ allow_all_operations=True,
1646
+ ),
1647
+ }
1648
+
1649
+
1650
+ __all__ = [
1651
+ "guarded_eval",
1652
+ "eval_node",
1653
+ "GuardRejection",
1654
+ "EvaluationContext",
1655
+ "_unbind_method",
1656
+ ]
lib/python3.12/site-packages/IPython/core/magic.py ADDED
@@ -0,0 +1,786 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ """Magic functions for InteractiveShell."""
4
+
5
+ # -----------------------------------------------------------------------------
6
+ # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
7
+ # Copyright (C) 2001 Fernando Perez <fperez@colorado.edu>
8
+ # Copyright (C) 2008 The IPython Development Team
9
+
10
+ # Distributed under the terms of the BSD License. The full license is in
11
+ # the file COPYING, distributed as part of this software.
12
+ # -----------------------------------------------------------------------------
13
+
14
+ import os
15
+ import re
16
+ import sys
17
+ from getopt import getopt, GetoptError
18
+
19
+ from traitlets.config.configurable import Configurable
20
+ from . import oinspect
21
+ from .error import UsageError
22
+ from .inputtransformer2 import ESC_MAGIC, ESC_MAGIC2
23
+ from ..utils.ipstruct import Struct
24
+ from ..utils.process import arg_split
25
+ from ..utils.text import dedent
26
+ from traitlets import Bool, Dict, Instance, observe
27
+ from logging import error
28
+
29
+ import typing as t
30
+
31
+ if t.TYPE_CHECKING:
32
+ from IPython.core.interactiveshell import InteractiveShell
33
+
34
+
35
+ # -----------------------------------------------------------------------------
36
+ # Globals
37
+ # -----------------------------------------------------------------------------
38
+
39
+ # A dict we'll use for each class that has magics, used as temporary storage to
40
+ # pass information between the @line/cell_magic method decorators and the
41
+ # @magics_class class decorator, because the method decorators have no
42
+ # access to the class when they run. See for more details:
43
+ # http://stackoverflow.com/questions/2366713/can-a-python-decorator-of-an-instance-method-access-the-class
44
+
45
+ magics: t.Dict = dict(line={}, cell={})
46
+
47
+ magic_kinds = ("line", "cell")
48
+ magic_spec = ("line", "cell", "line_cell")
49
+ magic_escapes = dict(line=ESC_MAGIC, cell=ESC_MAGIC2)
50
+
51
+ # -----------------------------------------------------------------------------
52
+ # Utility classes and functions
53
+ # -----------------------------------------------------------------------------
54
+
55
+
56
+ class Bunch:
57
+ pass
58
+
59
+
60
+ def on_off(tag):
61
+ """Return an ON/OFF string for a 1/0 input. Simple utility function."""
62
+ return ["OFF", "ON"][tag]
63
+
64
+
65
+ def compress_dhist(dh):
66
+ """Compress a directory history into a new one with at most 20 entries.
67
+
68
+ Return a new list made from the first and last 10 elements of dhist after
69
+ removal of duplicates.
70
+ """
71
+ head, tail = dh[:-10], dh[-10:]
72
+
73
+ newhead = []
74
+ done = set()
75
+ for h in head:
76
+ if h in done:
77
+ continue
78
+ newhead.append(h)
79
+ done.add(h)
80
+
81
+ return newhead + tail
82
+
83
+
84
+ def needs_local_scope(func):
85
+ """Decorator to mark magic functions which need to local scope to run."""
86
+ func.needs_local_scope = True
87
+ return func
88
+
89
+
90
+ # -----------------------------------------------------------------------------
91
+ # Class and method decorators for registering magics
92
+ # -----------------------------------------------------------------------------
93
+
94
+
95
+ def magics_class(cls):
96
+ """Class decorator for all subclasses of the main Magics class.
97
+
98
+ Any class that subclasses Magics *must* also apply this decorator, to
99
+ ensure that all the methods that have been decorated as line/cell magics
100
+ get correctly registered in the class instance. This is necessary because
101
+ when method decorators run, the class does not exist yet, so they
102
+ temporarily store their information into a module global. Application of
103
+ this class decorator copies that global data to the class instance and
104
+ clears the global.
105
+
106
+ Obviously, this mechanism is not thread-safe, which means that the
107
+ *creation* of subclasses of Magic should only be done in a single-thread
108
+ context. Instantiation of the classes has no restrictions. Given that
109
+ these classes are typically created at IPython startup time and before user
110
+ application code becomes active, in practice this should not pose any
111
+ problems.
112
+ """
113
+ cls.registered = True
114
+ cls.magics = dict(line=magics["line"], cell=magics["cell"])
115
+ magics["line"] = {}
116
+ magics["cell"] = {}
117
+ return cls
118
+
119
+
120
+ def record_magic(dct, magic_kind, magic_name, func):
121
+ """Utility function to store a function as a magic of a specific kind.
122
+
123
+ Parameters
124
+ ----------
125
+ dct : dict
126
+ A dictionary with 'line' and 'cell' subdicts.
127
+ magic_kind : str
128
+ Kind of magic to be stored.
129
+ magic_name : str
130
+ Key to store the magic as.
131
+ func : function
132
+ Callable object to store.
133
+ """
134
+ if magic_kind == "line_cell":
135
+ dct["line"][magic_name] = dct["cell"][magic_name] = func
136
+ else:
137
+ dct[magic_kind][magic_name] = func
138
+
139
+
140
+ def validate_type(magic_kind):
141
+ """Ensure that the given magic_kind is valid.
142
+
143
+ Check that the given magic_kind is one of the accepted spec types (stored
144
+ in the global `magic_spec`), raise ValueError otherwise.
145
+ """
146
+ if magic_kind not in magic_spec:
147
+ raise ValueError(
148
+ "magic_kind must be one of %s, %s given" % magic_kinds, magic_kind
149
+ )
150
+
151
+
152
+ # The docstrings for the decorator below will be fairly similar for the two
153
+ # types (method and function), so we generate them here once and reuse the
154
+ # templates below.
155
+ _docstring_template = """Decorate the given {0} as {1} magic.
156
+
157
+ The decorator can be used with or without arguments, as follows.
158
+
159
+ i) without arguments: it will create a {1} magic named as the {0} being
160
+ decorated::
161
+
162
+ @deco
163
+ def foo(...)
164
+
165
+ will create a {1} magic named `foo`.
166
+
167
+ ii) with one string argument: which will be used as the actual name of the
168
+ resulting magic::
169
+
170
+ @deco('bar')
171
+ def foo(...)
172
+
173
+ will create a {1} magic named `bar`.
174
+
175
+ To register a class magic use ``Interactiveshell.register_magic(class or instance)``.
176
+ """
177
+
178
+ # These two are decorator factories. While they are conceptually very similar,
179
+ # there are enough differences in the details that it's simpler to have them
180
+ # written as completely standalone functions rather than trying to share code
181
+ # and make a single one with convoluted logic.
182
+
183
+
184
+ def _method_magic_marker(magic_kind):
185
+ """Decorator factory for methods in Magics subclasses."""
186
+
187
+ validate_type(magic_kind)
188
+
189
+ # This is a closure to capture the magic_kind. We could also use a class,
190
+ # but it's overkill for just that one bit of state.
191
+ def magic_deco(arg):
192
+ if callable(arg):
193
+ # "Naked" decorator call (just @foo, no args)
194
+ func = arg
195
+ name = func.__name__
196
+ retval = arg
197
+ record_magic(magics, magic_kind, name, name)
198
+ elif isinstance(arg, str):
199
+ # Decorator called with arguments (@foo('bar'))
200
+ name = arg
201
+
202
+ def mark(func, *a, **kw):
203
+ record_magic(magics, magic_kind, name, func.__name__)
204
+ return func
205
+
206
+ retval = mark
207
+ else:
208
+ raise TypeError("Decorator can only be called with string or function")
209
+ return retval
210
+
211
+ # Ensure the resulting decorator has a usable docstring
212
+ magic_deco.__doc__ = _docstring_template.format("method", magic_kind)
213
+ return magic_deco
214
+
215
+
216
+ def _function_magic_marker(magic_kind):
217
+ """Decorator factory for standalone functions."""
218
+ validate_type(magic_kind)
219
+
220
+ # This is a closure to capture the magic_kind. We could also use a class,
221
+ # but it's overkill for just that one bit of state.
222
+ def magic_deco(arg):
223
+ # Find get_ipython() in the caller's namespace
224
+ caller = sys._getframe(1)
225
+ for ns in ["f_locals", "f_globals", "f_builtins"]:
226
+ get_ipython = getattr(caller, ns).get("get_ipython")
227
+ if get_ipython is not None:
228
+ break
229
+ else:
230
+ raise NameError(
231
+ "Decorator can only run in context where `get_ipython` exists"
232
+ )
233
+
234
+ ip = get_ipython()
235
+
236
+ if callable(arg):
237
+ # "Naked" decorator call (just @foo, no args)
238
+ func = arg
239
+ name = func.__name__
240
+ ip.register_magic_function(func, magic_kind, name)
241
+ retval = arg
242
+ elif isinstance(arg, str):
243
+ # Decorator called with arguments (@foo('bar'))
244
+ name = arg
245
+
246
+ def mark(func, *a, **kw):
247
+ ip.register_magic_function(func, magic_kind, name)
248
+ return func
249
+
250
+ retval = mark
251
+ else:
252
+ raise TypeError("Decorator can only be called with string or function")
253
+ return retval
254
+
255
+ # Ensure the resulting decorator has a usable docstring
256
+ ds = _docstring_template.format("function", magic_kind)
257
+
258
+ ds += dedent(
259
+ """
260
+ Note: this decorator can only be used in a context where IPython is already
261
+ active, so that the `get_ipython()` call succeeds. You can therefore use
262
+ it in your startup files loaded after IPython initializes, but *not* in the
263
+ IPython configuration file itself, which is executed before IPython is
264
+ fully up and running. Any file located in the `startup` subdirectory of
265
+ your configuration profile will be OK in this sense.
266
+ """
267
+ )
268
+
269
+ magic_deco.__doc__ = ds
270
+ return magic_deco
271
+
272
+
273
+ MAGIC_NO_VAR_EXPAND_ATTR = "_ipython_magic_no_var_expand"
274
+ MAGIC_OUTPUT_CAN_BE_SILENCED = "_ipython_magic_output_can_be_silenced"
275
+
276
+
277
+ def no_var_expand(magic_func):
278
+ """Mark a magic function as not needing variable expansion
279
+
280
+ By default, IPython interprets `{a}` or `$a` in the line passed to magics
281
+ as variables that should be interpolated from the interactive namespace
282
+ before passing the line to the magic function.
283
+ This is not always desirable, e.g. when the magic executes Python code
284
+ (%timeit, %time, etc.).
285
+ Decorate magics with `@no_var_expand` to opt-out of variable expansion.
286
+
287
+ .. versionadded:: 7.3
288
+ """
289
+ setattr(magic_func, MAGIC_NO_VAR_EXPAND_ATTR, True)
290
+ return magic_func
291
+
292
+
293
+ def output_can_be_silenced(magic_func):
294
+ """Mark a magic function so its output may be silenced.
295
+
296
+ The output is silenced if the Python code used as a parameter of
297
+ the magic ends in a semicolon, not counting a Python comment that can
298
+ follow it.
299
+ """
300
+ setattr(magic_func, MAGIC_OUTPUT_CAN_BE_SILENCED, True)
301
+ return magic_func
302
+
303
+
304
+ # Create the actual decorators for public use
305
+
306
+ # These three are used to decorate methods in class definitions
307
+ line_magic = _method_magic_marker("line")
308
+ cell_magic = _method_magic_marker("cell")
309
+ line_cell_magic = _method_magic_marker("line_cell")
310
+
311
+ # These three decorate standalone functions and perform the decoration
312
+ # immediately. They can only run where get_ipython() works
313
+ register_line_magic = _function_magic_marker("line")
314
+ register_cell_magic = _function_magic_marker("cell")
315
+ register_line_cell_magic = _function_magic_marker("line_cell")
316
+
317
+ # -----------------------------------------------------------------------------
318
+ # Core Magic classes
319
+ # -----------------------------------------------------------------------------
320
+
321
+
322
+ class MagicsManager(Configurable):
323
+ """Object that handles all magic-related functionality for IPython."""
324
+
325
+ # Non-configurable class attributes
326
+
327
+ # A two-level dict, first keyed by magic type, then by magic function, and
328
+ # holding the actual callable object as value. This is the dict used for
329
+ # magic function dispatch
330
+ magics = Dict()
331
+ lazy_magics = Dict(
332
+ help="""
333
+ Mapping from magic names to modules to load.
334
+
335
+ This can be used in IPython/IPykernel configuration to declare lazy magics
336
+ that will only be imported/registered on first use.
337
+
338
+ For example::
339
+
340
+ c.MagicsManager.lazy_magics = {
341
+ "my_magic": "slow.to.import",
342
+ "my_other_magic": "also.slow",
343
+ }
344
+
345
+ On first invocation of `%my_magic`, `%%my_magic`, `%%my_other_magic` or
346
+ `%%my_other_magic`, the corresponding module will be loaded as an ipython
347
+ extensions as if you had previously done `%load_ext ipython`.
348
+
349
+ Magics names should be without percent(s) as magics can be both cell
350
+ and line magics.
351
+
352
+ Lazy loading happen relatively late in execution process, and
353
+ complex extensions that manipulate Python/IPython internal state or global state
354
+ might not support lazy loading.
355
+ """
356
+ ).tag(
357
+ config=True,
358
+ )
359
+
360
+ # A registry of the original objects that we've been given holding magics.
361
+ registry = Dict()
362
+
363
+ shell = Instance(
364
+ "IPython.core.interactiveshell.InteractiveShellABC", allow_none=True
365
+ )
366
+
367
+ auto_magic = Bool(
368
+ True, help="Automatically call line magics without requiring explicit % prefix"
369
+ ).tag(config=True)
370
+
371
+ @observe("auto_magic")
372
+ def _auto_magic_changed(self, change):
373
+ assert self.shell is not None
374
+ self.shell.automagic = change["new"]
375
+
376
+ _auto_status = [
377
+ "Automagic is OFF, % prefix IS needed for line magics.",
378
+ "Automagic is ON, % prefix IS NOT needed for line magics.",
379
+ ]
380
+
381
+ user_magics = Instance("IPython.core.magics.UserMagics", allow_none=True)
382
+
383
+ def __init__(self, shell=None, config=None, user_magics=None, **traits):
384
+ super(MagicsManager, self).__init__(
385
+ shell=shell, config=config, user_magics=user_magics, **traits
386
+ )
387
+ self.magics = dict(line={}, cell={})
388
+ # Let's add the user_magics to the registry for uniformity, so *all*
389
+ # registered magic containers can be found there.
390
+ self.registry[user_magics.__class__.__name__] = user_magics
391
+
392
+ def auto_status(self):
393
+ """Return descriptive string with automagic status."""
394
+ return self._auto_status[self.auto_magic]
395
+
396
+ def lsmagic(self):
397
+ """Return a dict of currently available magic functions.
398
+
399
+ The return dict has the keys 'line' and 'cell', corresponding to the
400
+ two types of magics we support. Each value is a list of names.
401
+ """
402
+ return self.magics
403
+
404
+ def lsmagic_docs(self, brief=False, missing=""):
405
+ """Return dict of documentation of magic functions.
406
+
407
+ The return dict has the keys 'line' and 'cell', corresponding to the
408
+ two types of magics we support. Each value is a dict keyed by magic
409
+ name whose value is the function docstring. If a docstring is
410
+ unavailable, the value of `missing` is used instead.
411
+
412
+ If brief is True, only the first line of each docstring will be returned.
413
+ """
414
+ docs = {}
415
+ for m_type in self.magics:
416
+ m_docs = {}
417
+ for m_name, m_func in self.magics[m_type].items():
418
+ if m_func.__doc__:
419
+ if brief:
420
+ m_docs[m_name] = m_func.__doc__.split("\n", 1)[0]
421
+ else:
422
+ m_docs[m_name] = m_func.__doc__.rstrip()
423
+ else:
424
+ m_docs[m_name] = missing
425
+ docs[m_type] = m_docs
426
+ return docs
427
+
428
+ def register_lazy(self, name: str, fully_qualified_name: str) -> None:
429
+ """
430
+ Lazily register a magic via an extension.
431
+
432
+
433
+ Parameters
434
+ ----------
435
+ name : str
436
+ Name of the magic you wish to register.
437
+ fully_qualified_name :
438
+ Fully qualified name of the module/submodule that should be loaded
439
+ as an extensions when the magic is first called.
440
+ It is assumed that loading this extensions will register the given
441
+ magic.
442
+ """
443
+
444
+ self.lazy_magics[name] = fully_qualified_name
445
+
446
+ def register(self, *magic_objects):
447
+ """Register one or more instances of Magics.
448
+
449
+ Take one or more classes or instances of classes that subclass the main
450
+ `core.Magic` class, and register them with IPython to use the magic
451
+ functions they provide. The registration process will then ensure that
452
+ any methods that have decorated to provide line and/or cell magics will
453
+ be recognized with the `%x`/`%%x` syntax as a line/cell magic
454
+ respectively.
455
+
456
+ If classes are given, they will be instantiated with the default
457
+ constructor. If your classes need a custom constructor, you should
458
+ instanitate them first and pass the instance.
459
+
460
+ The provided arguments can be an arbitrary mix of classes and instances.
461
+
462
+ Parameters
463
+ ----------
464
+ *magic_objects : one or more classes or instances
465
+ """
466
+ # Start by validating them to ensure they have all had their magic
467
+ # methods registered at the instance level
468
+ for m in magic_objects:
469
+ if not m.registered:
470
+ raise ValueError(
471
+ "Class of magics %r was constructed without "
472
+ "the @register_magics class decorator"
473
+ )
474
+ if isinstance(m, type):
475
+ # If we're given an uninstantiated class
476
+ m = m(shell=self.shell)
477
+
478
+ # Now that we have an instance, we can register it and update the
479
+ # table of callables
480
+ self.registry[m.__class__.__name__] = m
481
+ for mtype in magic_kinds:
482
+ self.magics[mtype].update(m.magics[mtype])
483
+
484
+ def register_function(self, func, magic_kind="line", magic_name=None):
485
+ """Expose a standalone function as magic function for IPython.
486
+
487
+ This will create an IPython magic (line, cell or both) from a
488
+ standalone function. The functions should have the following
489
+ signatures:
490
+
491
+ * For line magics: `def f(line)`
492
+ * For cell magics: `def f(line, cell)`
493
+ * For a function that does both: `def f(line, cell=None)`
494
+
495
+ In the latter case, the function will be called with `cell==None` when
496
+ invoked as `%f`, and with cell as a string when invoked as `%%f`.
497
+
498
+ Parameters
499
+ ----------
500
+ func : callable
501
+ Function to be registered as a magic.
502
+ magic_kind : str
503
+ Kind of magic, one of 'line', 'cell' or 'line_cell'
504
+ magic_name : optional str
505
+ If given, the name the magic will have in the IPython namespace. By
506
+ default, the name of the function itself is used.
507
+ """
508
+
509
+ # Create the new method in the user_magics and register it in the
510
+ # global table
511
+ validate_type(magic_kind)
512
+ magic_name = func.__name__ if magic_name is None else magic_name
513
+ setattr(self.user_magics, magic_name, func)
514
+ record_magic(self.magics, magic_kind, magic_name, func)
515
+
516
+ def register_alias(
517
+ self, alias_name, magic_name, magic_kind="line", magic_params=None
518
+ ):
519
+ """Register an alias to a magic function.
520
+
521
+ The alias is an instance of :class:`MagicAlias`, which holds the
522
+ name and kind of the magic it should call. Binding is done at
523
+ call time, so if the underlying magic function is changed the alias
524
+ will call the new function.
525
+
526
+ Parameters
527
+ ----------
528
+ alias_name : str
529
+ The name of the magic to be registered.
530
+ magic_name : str
531
+ The name of an existing magic.
532
+ magic_kind : str
533
+ Kind of magic, one of 'line' or 'cell'
534
+ """
535
+
536
+ # `validate_type` is too permissive, as it allows 'line_cell'
537
+ # which we do not handle.
538
+ if magic_kind not in magic_kinds:
539
+ raise ValueError(
540
+ "magic_kind must be one of %s, %s given" % magic_kinds, magic_kind
541
+ )
542
+
543
+ alias = MagicAlias(self.shell, magic_name, magic_kind, magic_params)
544
+ setattr(self.user_magics, alias_name, alias)
545
+ record_magic(self.magics, magic_kind, alias_name, alias)
546
+
547
+
548
+ # Key base class that provides the central functionality for magics.
549
+
550
+
551
+ class Magics(Configurable):
552
+ """Base class for implementing magic functions.
553
+
554
+ Shell functions which can be reached as %function_name. All magic
555
+ functions should accept a string, which they can parse for their own
556
+ needs. This can make some functions easier to type, eg `%cd ../`
557
+ vs. `%cd("../")`
558
+
559
+ Classes providing magic functions need to subclass this class, and they
560
+ MUST:
561
+
562
+ - Use the method decorators `@line_magic` and `@cell_magic` to decorate
563
+ individual methods as magic functions, AND
564
+
565
+ - Use the class decorator `@magics_class` to ensure that the magic
566
+ methods are properly registered at the instance level upon instance
567
+ initialization.
568
+
569
+ See :mod:`magic_functions` for examples of actual implementation classes.
570
+ """
571
+
572
+ # Dict holding all command-line options for each magic.
573
+ options_table: dict[str, t.Any] = {}
574
+ # Dict for the mapping of magic names to methods, set by class decorator
575
+ magics: dict[str, t.Any] = {}
576
+ # Flag to check that the class decorator was properly applied
577
+ registered: bool = False
578
+ # Instance of IPython shell
579
+ shell: None | InteractiveShell = None
580
+
581
+ def __init__(self, shell=None, **kwargs):
582
+ if not (self.__class__.registered):
583
+ raise ValueError(
584
+ "Magics subclass without registration - "
585
+ "did you forget to apply @magics_class?"
586
+ )
587
+ if shell is not None:
588
+ if hasattr(shell, "configurables"):
589
+ shell.configurables.append(self)
590
+ if hasattr(shell, "config"):
591
+ kwargs.setdefault("parent", shell)
592
+
593
+ self.shell = shell
594
+ self.options_table = {}
595
+ # The method decorators are run when the instance doesn't exist yet, so
596
+ # they can only record the names of the methods they are supposed to
597
+ # grab. Only now, that the instance exists, can we create the proper
598
+ # mapping to bound methods. So we read the info off the original names
599
+ # table and replace each method name by the actual bound method.
600
+ # But we mustn't clobber the *class* mapping, in case of multiple instances.
601
+ class_magics = self.magics
602
+ self.magics = {}
603
+ for mtype in magic_kinds:
604
+ tab = self.magics[mtype] = {}
605
+ cls_tab = class_magics[mtype]
606
+ for magic_name, meth_name in cls_tab.items():
607
+ if isinstance(meth_name, str):
608
+ # it's a method name, grab it
609
+ tab[magic_name] = getattr(self, meth_name)
610
+ else:
611
+ # it's the real thing
612
+ tab[magic_name] = meth_name
613
+ # Configurable **needs** to be initiated at the end or the config
614
+ # magics get screwed up.
615
+ super(Magics, self).__init__(**kwargs)
616
+
617
+ def arg_err(self, func):
618
+ """Print docstring if incorrect arguments were passed"""
619
+ print("Error in arguments:")
620
+ print(oinspect.getdoc(func))
621
+
622
+ def format_latex(self, strng):
623
+ """Format a string for latex inclusion."""
624
+
625
+ # Characters that need to be escaped for latex:
626
+ escape_re = re.compile(r"(%|_|\$|#|&)", re.MULTILINE)
627
+ # Magic command names as headers:
628
+ cmd_name_re = re.compile(r"^(%s.*?):" % ESC_MAGIC, re.MULTILINE)
629
+ # Magic commands
630
+ cmd_re = re.compile(r"(?P<cmd>%s.+?\b)(?!\}\}:)" % ESC_MAGIC, re.MULTILINE)
631
+ # Paragraph continue
632
+ par_re = re.compile(r"\\$", re.MULTILINE)
633
+
634
+ # The "\n" symbol
635
+ newline_re = re.compile(r"\\n")
636
+
637
+ # Now build the string for output:
638
+ # strng = cmd_name_re.sub(r'\n\\texttt{\\textsl{\\large \1}}:',strng)
639
+ strng = cmd_name_re.sub(r"\n\\bigskip\n\\texttt{\\textbf{ \1}}:", strng)
640
+ strng = cmd_re.sub(r"\\texttt{\g<cmd>}", strng)
641
+ strng = par_re.sub(r"\\\\", strng)
642
+ strng = escape_re.sub(r"\\\1", strng)
643
+ strng = newline_re.sub(r"\\textbackslash{}n", strng)
644
+ return strng
645
+
646
+ def parse_options(self, arg_str, opt_str, *long_opts, **kw):
647
+ """Parse options passed to an argument string.
648
+
649
+ The interface is similar to that of :func:`getopt.getopt`, but it
650
+ returns a :class:`~IPython.utils.struct.Struct` with the options as keys
651
+ and the stripped argument string still as a string.
652
+
653
+ arg_str is quoted as a true sys.argv vector by using shlex.split.
654
+ This allows us to easily expand variables, glob files, quote
655
+ arguments, etc.
656
+
657
+ Parameters
658
+ ----------
659
+ arg_str : str
660
+ The arguments to parse.
661
+ opt_str : str
662
+ The options specification.
663
+ mode : str, default 'string'
664
+ If given as 'list', the argument string is returned as a list (split
665
+ on whitespace) instead of a string.
666
+ list_all : bool, default False
667
+ Put all option values in lists. Normally only options
668
+ appearing more than once are put in a list.
669
+ posix : bool, default True
670
+ Whether to split the input line in POSIX mode or not, as per the
671
+ conventions outlined in the :mod:`shlex` module from the standard
672
+ library.
673
+ """
674
+
675
+ # inject default options at the beginning of the input line
676
+ caller = sys._getframe(1).f_code.co_name
677
+ arg_str = "%s %s" % (self.options_table.get(caller, ""), arg_str)
678
+
679
+ mode = kw.get("mode", "string")
680
+ if mode not in ["string", "list"]:
681
+ raise ValueError("incorrect mode given: %s" % mode)
682
+ # Get options
683
+ list_all = kw.get("list_all", 0)
684
+ posix = kw.get("posix", os.name == "posix")
685
+ strict = kw.get("strict", True)
686
+
687
+ preserve_non_opts = kw.get("preserve_non_opts", False)
688
+ remainder_arg_str = arg_str
689
+
690
+ # Check if we have more than one argument to warrant extra processing:
691
+ odict: dict[str, t.Any] = {} # Dictionary with options
692
+ args = arg_str.split()
693
+ if len(args) >= 1:
694
+ # If the list of inputs only has 0 or 1 thing in it, there's no
695
+ # need to look for options
696
+ argv = arg_split(arg_str, posix, strict)
697
+ # Do regular option processing
698
+ try:
699
+ opts, args = getopt(argv, opt_str, long_opts)
700
+ except GetoptError as e:
701
+ raise UsageError(
702
+ '%s (allowed: "%s"%s)'
703
+ % (e.msg, opt_str, " ".join(("",) + long_opts) if long_opts else "")
704
+ ) from e
705
+ for o, a in opts:
706
+ if mode == "string" and preserve_non_opts:
707
+ # remove option-parts from the original args-string and preserve remaining-part.
708
+ # This relies on the arg_split(...) and getopt(...)'s impl spec, that the parsed options are
709
+ # returned in the original order.
710
+ remainder_arg_str = remainder_arg_str.replace(o, "", 1).replace(
711
+ a, "", 1
712
+ )
713
+ if o.startswith("--"):
714
+ o = o[2:]
715
+ else:
716
+ o = o[1:]
717
+ try:
718
+ odict[o].append(a)
719
+ except AttributeError:
720
+ odict[o] = [odict[o], a]
721
+ except KeyError:
722
+ if list_all:
723
+ odict[o] = [a]
724
+ else:
725
+ odict[o] = a
726
+
727
+ # Prepare opts,args for return
728
+ opts = Struct(odict) # type: ignore[assignment]
729
+ if mode == "string":
730
+ if preserve_non_opts:
731
+ args = remainder_arg_str.lstrip()
732
+ else:
733
+ args = " ".join(args)
734
+
735
+ return opts, args
736
+
737
+ def default_option(self, fn, optstr):
738
+ """Make an entry in the options_table for fn, with value optstr"""
739
+ assert False, "is this even called?"
740
+ if fn not in self.lsmagic():
741
+ error("%s is not a magic function" % fn)
742
+ self.options_table[fn] = optstr
743
+
744
+
745
+ class MagicAlias:
746
+ """An alias to another magic function.
747
+
748
+ An alias is determined by its magic name and magic kind. Lookup
749
+ is done at call time, so if the underlying magic changes the alias
750
+ will call the new function.
751
+
752
+ Use the :meth:`MagicsManager.register_alias` method or the
753
+ `%alias_magic` magic function to create and register a new alias.
754
+ """
755
+
756
+ def __init__(self, shell, magic_name, magic_kind, magic_params=None):
757
+ self.shell = shell
758
+ self.magic_name = magic_name
759
+ self.magic_params = magic_params
760
+ self.magic_kind = magic_kind
761
+
762
+ self.pretty_target = "%s%s" % (magic_escapes[self.magic_kind], self.magic_name)
763
+ self.__doc__ = "Alias for `%s`." % self.pretty_target
764
+
765
+ self._in_call = False
766
+
767
+ def __call__(self, *args, **kwargs):
768
+ """Call the magic alias."""
769
+ fn = self.shell.find_magic(self.magic_name, self.magic_kind)
770
+ if fn is None:
771
+ raise UsageError("Magic `%s` not found." % self.pretty_target)
772
+
773
+ # Protect against infinite recursion.
774
+ if self._in_call:
775
+ raise UsageError(
776
+ "Infinite recursion detected; magic aliases cannot call themselves."
777
+ )
778
+ self._in_call = True
779
+ try:
780
+ if self.magic_params:
781
+ args_list = list(args)
782
+ args_list[0] = self.magic_params + " " + args[0]
783
+ args = tuple(args_list)
784
+ return fn(*args, **kwargs)
785
+ finally:
786
+ self._in_call = False
lib/python3.12/site-packages/IPython/core/release.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Release data for the IPython project."""
3
+
4
+ #-----------------------------------------------------------------------------
5
+ # Copyright (c) 2008, IPython Development Team.
6
+ # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu>
7
+ # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
8
+ # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
9
+ #
10
+ # Distributed under the terms of the Modified BSD License.
11
+ #
12
+ # The full license is in the file COPYING.txt, distributed with this software.
13
+ #-----------------------------------------------------------------------------
14
+
15
+ # IPython version information. An empty _version_extra corresponds to a full
16
+ # release. 'dev' as a _version_extra string means this is a development
17
+ # version
18
+ _version_major = 9
19
+ _version_minor = 10
20
+ _version_patch = 0
21
+ _version_extra = ".dev"
22
+ # _version_extra = "b2"
23
+ _version_extra = "" # Uncomment this for full releases
24
+
25
+ # Construct full version string from these.
26
+ _ver = [_version_major, _version_minor, _version_patch]
27
+
28
+ __version__ = '.'.join(map(str, _ver))
29
+ if _version_extra:
30
+ __version__ = __version__ + _version_extra
31
+
32
+ version = __version__ # backwards compatibility name
33
+ version_info = (_version_major, _version_minor, _version_patch, _version_extra)
34
+
35
+
36
+ license = "BSD-3-Clause"
37
+
38
+ authors = {
39
+ "Fernando": ("Fernando Perez", "fperez.net@gmail.com"),
40
+ "M": ("M Bussonnier", "mbussonnier@gmail.com"),
41
+ }
42
+
43
+ author = 'The IPython Development Team'
44
+
45
+ author_email = 'ipython-dev@python.org'
lib/python3.12/site-packages/IPython/display.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Public API for display tools in IPython."""
2
+
3
+ # -----------------------------------------------------------------------------
4
+ # Copyright (C) 2012 The IPython Development Team
5
+ #
6
+ # Distributed under the terms of the BSD License. The full license is in
7
+ # the file COPYING, distributed as part of this software.
8
+ # -----------------------------------------------------------------------------
9
+
10
+ # -----------------------------------------------------------------------------
11
+ # Imports
12
+ # -----------------------------------------------------------------------------
13
+
14
+ from IPython.core.display_functions import *
15
+ from IPython.core.display import (
16
+ display_pretty as display_pretty,
17
+ display_html as display_html,
18
+ display_markdown as display_markdown,
19
+ display_svg as display_svg,
20
+ display_png as display_png,
21
+ display_jpeg as display_jpeg,
22
+ display_latex as display_latex,
23
+ display_json as display_json,
24
+ display_javascript as display_javascript,
25
+ display_pdf as display_pdf,
26
+ DisplayObject as DisplayObject,
27
+ TextDisplayObject as TextDisplayObject,
28
+ Pretty as Pretty,
29
+ HTML as HTML,
30
+ Markdown as Markdown,
31
+ Math as Math,
32
+ Latex as Latex,
33
+ SVG as SVG,
34
+ ProgressBar as ProgressBar,
35
+ JSON as JSON,
36
+ GeoJSON as GeoJSON,
37
+ Javascript as Javascript,
38
+ Image as Image,
39
+ Video as Video,
40
+ )
41
+ from IPython.lib.display import *
lib/python3.12/site-packages/IPython/paths.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Find files and directories which IPython uses.
2
+ """
3
+ import os.path
4
+ import tempfile
5
+ from warnings import warn
6
+
7
+ import IPython
8
+ from IPython.utils.importstring import import_item
9
+ from IPython.utils.path import (
10
+ get_home_dir,
11
+ get_xdg_dir,
12
+ get_xdg_cache_dir,
13
+ compress_user,
14
+ _writable_dir,
15
+ ensure_dir_exists,
16
+ )
17
+
18
+
19
+ def get_ipython_dir() -> str:
20
+ """Get the IPython directory for this platform and user.
21
+
22
+ This uses the logic in `get_home_dir` to find the home directory
23
+ and then adds .ipython to the end of the path.
24
+ """
25
+
26
+ env = os.environ
27
+ pjoin = os.path.join
28
+
29
+
30
+ ipdir_def = '.ipython'
31
+
32
+ home_dir = get_home_dir()
33
+ xdg_dir = get_xdg_dir()
34
+
35
+ ipdir = env.get("IPYTHONDIR", None)
36
+ if ipdir is None:
37
+ # not set explicitly, use ~/.ipython
38
+ ipdir = pjoin(home_dir, ipdir_def)
39
+ if xdg_dir:
40
+ # Several IPython versions (up to 1.x) defaulted to .config/ipython
41
+ # on Linux. We have decided to go back to using .ipython everywhere
42
+ xdg_ipdir = pjoin(xdg_dir, 'ipython')
43
+
44
+ if _writable_dir(xdg_ipdir):
45
+ cu = compress_user
46
+ if os.path.exists(ipdir):
47
+ warn(('Ignoring {0} in favour of {1}. Remove {0} to '
48
+ 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
49
+ elif os.path.islink(xdg_ipdir):
50
+ warn(('{0} is deprecated. Move link to {1} to '
51
+ 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
52
+ else:
53
+ ipdir = xdg_ipdir
54
+
55
+ ipdir = os.path.normpath(os.path.expanduser(ipdir))
56
+
57
+ if os.path.exists(ipdir) and not _writable_dir(ipdir):
58
+ # ipdir exists, but is not writable
59
+ warn("IPython dir '{0}' is not a writable location,"
60
+ " using a temp directory.".format(ipdir))
61
+ ipdir = tempfile.mkdtemp()
62
+ elif not os.path.exists(ipdir):
63
+ parent = os.path.dirname(ipdir)
64
+ if not _writable_dir(parent):
65
+ # ipdir does not exist and parent isn't writable
66
+ warn("IPython parent '{0}' is not a writable location,"
67
+ " using a temp directory.".format(parent))
68
+ ipdir = tempfile.mkdtemp()
69
+ else:
70
+ os.makedirs(ipdir, exist_ok=True)
71
+ assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not."
72
+ return ipdir
73
+
74
+
75
+ def get_ipython_cache_dir() -> str:
76
+ """Get the cache directory it is created if it does not exist."""
77
+ xdgdir = get_xdg_cache_dir()
78
+ if xdgdir is None:
79
+ return get_ipython_dir()
80
+ ipdir = os.path.join(xdgdir, "ipython")
81
+ if not os.path.exists(ipdir) and _writable_dir(xdgdir):
82
+ ensure_dir_exists(ipdir)
83
+ elif not _writable_dir(xdgdir):
84
+ return get_ipython_dir()
85
+
86
+ return ipdir
87
+
88
+
89
+ def get_ipython_package_dir() -> str:
90
+ """Get the base directory where IPython itself is installed."""
91
+ ipdir = os.path.dirname(IPython.__file__)
92
+ assert isinstance(ipdir, str)
93
+ return ipdir
94
+
95
+
96
+ def get_ipython_module_path(module_str):
97
+ """Find the path to an IPython module in this version of IPython.
98
+
99
+ This will always find the version of the module that is in this importable
100
+ IPython package. This will always return the path to the ``.py``
101
+ version of the module.
102
+ """
103
+ if module_str == 'IPython':
104
+ return os.path.join(get_ipython_package_dir(), '__init__.py')
105
+ mod = import_item(module_str)
106
+ the_path = mod.__file__.replace('.pyc', '.py')
107
+ the_path = the_path.replace('.pyo', '.py')
108
+ return the_path
109
+
110
+
111
+ def locate_profile(profile='default'):
112
+ """Find the path to the folder associated with a given profile.
113
+
114
+ I.e. find $IPYTHONDIR/profile_whatever.
115
+ """
116
+ from IPython.core.profiledir import ProfileDir, ProfileDirError
117
+ try:
118
+ pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
119
+ except ProfileDirError as e:
120
+ # IOError makes more sense when people are expecting a path
121
+ raise IOError("Couldn't find profile %r" % profile) from e
122
+ return pd.location
lib/python3.12/site-packages/IPython/py.typed ADDED
File without changes
lib/python3.12/site-packages/IPython/sphinxext/__init__.py ADDED
File without changes
lib/python3.12/site-packages/IPython/sphinxext/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (196 Bytes). View file
 
lib/python3.12/site-packages/IPython/sphinxext/__pycache__/custom_doctests.cpython-312.pyc ADDED
Binary file (4.86 kB). View file
 
lib/python3.12/site-packages/IPython/sphinxext/__pycache__/ipython_console_highlighting.cpython-312.pyc ADDED
Binary file (773 Bytes). View file
 
lib/python3.12/site-packages/IPython/sphinxext/__pycache__/ipython_directive.cpython-312.pyc ADDED
Binary file (40.3 kB). View file
 
lib/python3.12/site-packages/IPython/sphinxext/custom_doctests.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Handlers for IPythonDirective's @doctest pseudo-decorator.
3
+
4
+ The Sphinx extension that provides support for embedded IPython code provides
5
+ a pseudo-decorator @doctest, which treats the input/output block as a
6
+ doctest, raising a RuntimeError during doc generation if the actual output
7
+ (after running the input) does not match the expected output.
8
+
9
+ An example usage is:
10
+
11
+ .. code-block:: rst
12
+
13
+ .. ipython::
14
+
15
+ In [1]: x = 1
16
+
17
+ @doctest
18
+ In [2]: x + 2
19
+ Out[3]: 3
20
+
21
+ One can also provide arguments to the decorator. The first argument should be
22
+ the name of a custom handler. The specification of any other arguments is
23
+ determined by the handler. For example,
24
+
25
+ .. code-block:: rst
26
+
27
+ .. ipython::
28
+
29
+ @doctest float
30
+ In [154]: 0.1 + 0.2
31
+ Out[154]: 0.3
32
+
33
+ allows the actual output ``0.30000000000000004`` to match the expected output
34
+ due to a comparison with `np.allclose`.
35
+
36
+ This module contains handlers for the @doctest pseudo-decorator. Handlers
37
+ should have the following function signature::
38
+
39
+ handler(sphinx_shell, args, input_lines, found, submitted)
40
+
41
+ where `sphinx_shell` is the embedded Sphinx shell, `args` contains the list
42
+ of arguments that follow: '@doctest handler_name', `input_lines` contains
43
+ a list of the lines relevant to the current doctest, `found` is a string
44
+ containing the output from the IPython shell, and `submitted` is a string
45
+ containing the expected output from the IPython shell.
46
+
47
+ Handlers must be registered in the `doctests` dict at the end of this module.
48
+
49
+ """
50
+
51
+ def str_to_array(s):
52
+ """
53
+ Simplistic converter of strings from repr to float NumPy arrays.
54
+
55
+ If the repr representation has ellipsis in it, then this will fail.
56
+
57
+ Parameters
58
+ ----------
59
+ s : str
60
+ The repr version of a NumPy array.
61
+
62
+ Examples
63
+ --------
64
+ >>> s = "array([ 0.3, inf, nan])"
65
+ >>> a = str_to_array(s)
66
+
67
+ """
68
+ import numpy as np
69
+
70
+ # Need to make sure eval() knows about inf and nan.
71
+ # This also assumes default printoptions for NumPy.
72
+ from numpy import inf, nan
73
+
74
+ if s.startswith(u'array'):
75
+ # Remove array( and )
76
+ s = s[6:-1]
77
+
78
+ if s.startswith(u'['):
79
+ a = np.array(eval(s), dtype=float)
80
+ else:
81
+ # Assume its a regular float. Force 1D so we can index into it.
82
+ a = np.atleast_1d(float(s))
83
+ return a
84
+
85
+ def float_doctest(sphinx_shell, args, input_lines, found, submitted):
86
+ """
87
+ Doctest which allow the submitted output to vary slightly from the input.
88
+
89
+ Here is how it might appear in an rst file:
90
+
91
+ .. code-block:: rst
92
+
93
+ .. ipython::
94
+
95
+ @doctest float
96
+ In [1]: 0.1 + 0.2
97
+ Out[1]: 0.3
98
+
99
+ """
100
+ import numpy as np
101
+
102
+ if len(args) == 2:
103
+ rtol = 1e-05
104
+ atol = 1e-08
105
+ else:
106
+ # Both must be specified if any are specified.
107
+ try:
108
+ rtol = float(args[2])
109
+ atol = float(args[3])
110
+ except IndexError:
111
+ e = ("Both `rtol` and `atol` must be specified "
112
+ "if either are specified: {0}".format(args))
113
+ raise IndexError(e) from e
114
+
115
+ try:
116
+ submitted = str_to_array(submitted)
117
+ found = str_to_array(found)
118
+ except:
119
+ # For example, if the array is huge and there are ellipsis in it.
120
+ error = True
121
+ else:
122
+ found_isnan = np.isnan(found)
123
+ submitted_isnan = np.isnan(submitted)
124
+ error = not np.allclose(found_isnan, submitted_isnan)
125
+ error |= not np.allclose(found[~found_isnan],
126
+ submitted[~submitted_isnan],
127
+ rtol=rtol, atol=atol)
128
+
129
+ TAB = ' ' * 4
130
+ directive = sphinx_shell.directive
131
+ if directive is None:
132
+ source = 'Unavailable'
133
+ content = 'Unavailable'
134
+ else:
135
+ source = directive.state.document.current_source
136
+ # Add tabs and make into a single string.
137
+ content = '\n'.join([TAB + line for line in directive.content])
138
+
139
+ if error:
140
+
141
+ e = ('doctest float comparison failure\n\n'
142
+ 'Document source: {0}\n\n'
143
+ 'Raw content: \n{1}\n\n'
144
+ 'On input line(s):\n{TAB}{2}\n\n'
145
+ 'we found output:\n{TAB}{3}\n\n'
146
+ 'instead of the expected:\n{TAB}{4}\n\n')
147
+ e = e.format(source, content, '\n'.join(input_lines), repr(found),
148
+ repr(submitted), TAB=TAB)
149
+ raise RuntimeError(e)
150
+
151
+ # dict of allowable doctest handlers. The key represents the first argument
152
+ # that must be given to @doctest in order to activate the handler.
153
+ doctests = {
154
+ 'float': float_doctest,
155
+ }
lib/python3.12/site-packages/IPython/sphinxext/ipython_console_highlighting.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ reST directive for syntax-highlighting ipython interactive sessions.
3
+
4
+ """
5
+
6
+ from sphinx import highlighting
7
+ from ipython_pygments_lexers import IPyLexer
8
+
9
+
10
+ def setup(app):
11
+ """Setup as a sphinx extension."""
12
+
13
+ # This is only a lexer, so adding it below to pygments appears sufficient.
14
+ # But if somebody knows what the right API usage should be to do that via
15
+ # sphinx, by all means fix it here. At least having this setup.py
16
+ # suppresses the sphinx warning we'd get without it.
17
+ metadata = {"parallel_read_safe": True, "parallel_write_safe": True}
18
+ return metadata
19
+
20
+
21
+ # Register the extension as a valid pygments lexer.
22
+ # Alternatively, we could register the lexer with pygments instead. This would
23
+ # require using setuptools entrypoints: http://pygments.org/docs/plugins
24
+
25
+ ipy3 = IPyLexer()
26
+
27
+ highlighting.lexers["ipython"] = ipy3
28
+ highlighting.lexers["ipython3"] = ipy3
lib/python3.12/site-packages/IPython/sphinxext/ipython_directive.py ADDED
@@ -0,0 +1,1278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Sphinx directive to support embedded IPython code.
4
+
5
+ IPython provides an extension for `Sphinx <http://www.sphinx-doc.org/>`_ to
6
+ highlight and run code.
7
+
8
+ This directive allows pasting of entire interactive IPython sessions, prompts
9
+ and all, and their code will actually get re-executed at doc build time, with
10
+ all prompts renumbered sequentially. It also allows you to input code as a pure
11
+ python input by giving the argument python to the directive. The output looks
12
+ like an interactive ipython section.
13
+
14
+ Here is an example of how the IPython directive can
15
+ **run** python code, at build time.
16
+
17
+ .. ipython::
18
+
19
+ In [1]: 1+1
20
+
21
+ In [1]: import datetime
22
+ ...: datetime.date.fromisoformat('2022-02-22')
23
+
24
+ It supports IPython construct that plain
25
+ Python does not understand (like magics):
26
+
27
+ .. ipython::
28
+
29
+ In [0]: import time
30
+
31
+ In [0]: %pdoc time.sleep
32
+
33
+ This will also support top-level async when using IPython 7.0+
34
+
35
+ .. ipython::
36
+
37
+ In [2]: import asyncio
38
+ ...: print('before')
39
+ ...: await asyncio.sleep(1)
40
+ ...: print('after')
41
+
42
+
43
+ The namespace will persist across multiple code chucks, Let's define a variable:
44
+
45
+ .. ipython::
46
+
47
+ In [0]: who = "World"
48
+
49
+ And now say hello:
50
+
51
+ .. ipython::
52
+
53
+ In [0]: print('Hello,', who)
54
+
55
+ If the current section raises an exception, you can add the ``:okexcept:`` flag
56
+ to the current block, otherwise the build will fail.
57
+
58
+ .. ipython::
59
+ :okexcept:
60
+
61
+ In [1]: 1/0
62
+
63
+ IPython Sphinx directive module
64
+ ===============================
65
+
66
+ To enable this directive, simply list it in your Sphinx ``conf.py`` file
67
+ (making sure the directory where you placed it is visible to sphinx, as is
68
+ needed for all Sphinx directives). For example, to enable syntax highlighting
69
+ and the IPython directive::
70
+
71
+ extensions = ['IPython.sphinxext.ipython_console_highlighting',
72
+ 'IPython.sphinxext.ipython_directive']
73
+
74
+ The IPython directive outputs code-blocks with the language 'ipython'. So
75
+ if you do not have the syntax highlighting extension enabled as well, then
76
+ all rendered code-blocks will be uncolored. By default this directive assumes
77
+ that your prompts are unchanged IPython ones, but this can be customized.
78
+ The configurable options that can be placed in conf.py are:
79
+
80
+ ipython_savefig_dir:
81
+ The directory in which to save the figures. This is relative to the
82
+ Sphinx source directory. The default is `html_static_path`.
83
+ ipython_rgxin:
84
+ The compiled regular expression to denote the start of IPython input
85
+ lines. The default is ``re.compile('In \\[(\\d+)\\]:\\s?(.*)\\s*')``. You
86
+ shouldn't need to change this.
87
+ ipython_warning_is_error: [default to True]
88
+ Fail the build if something unexpected happen, for example if a block raise
89
+ an exception but does not have the `:okexcept:` flag. The exact behavior of
90
+ what is considered strict, may change between the sphinx directive version.
91
+ ipython_rgxout:
92
+ The compiled regular expression to denote the start of IPython output
93
+ lines. The default is ``re.compile('Out\\[(\\d+)\\]:\\s?(.*)\\s*')``. You
94
+ shouldn't need to change this.
95
+ ipython_promptin:
96
+ The string to represent the IPython input prompt in the generated ReST.
97
+ The default is ``'In [%d]:'``. This expects that the line numbers are used
98
+ in the prompt.
99
+ ipython_promptout:
100
+ The string to represent the IPython prompt in the generated ReST. The
101
+ default is ``'Out [%d]:'``. This expects that the line numbers are used
102
+ in the prompt.
103
+ ipython_mplbackend:
104
+ The string which specifies if the embedded Sphinx shell should import
105
+ Matplotlib and set the backend. The value specifies a backend that is
106
+ passed to `matplotlib.use()` before any lines in `ipython_execlines` are
107
+ executed. If not specified in conf.py, then the default value of 'agg' is
108
+ used. To use the IPython directive without matplotlib as a dependency, set
109
+ the value to `None`. It may end up that matplotlib is still imported
110
+ if the user specifies so in `ipython_execlines` or makes use of the
111
+ @savefig pseudo decorator.
112
+ ipython_execlines:
113
+ A list of strings to be exec'd in the embedded Sphinx shell. Typical
114
+ usage is to make certain packages always available. Set this to an empty
115
+ list if you wish to have no imports always available. If specified in
116
+ ``conf.py`` as `None`, then it has the effect of making no imports available.
117
+ If omitted from conf.py altogether, then the default value of
118
+ ['import numpy as np', 'import matplotlib.pyplot as plt'] is used.
119
+ ipython_holdcount
120
+ When the @suppress pseudo-decorator is used, the execution count can be
121
+ incremented or not. The default behavior is to hold the execution count,
122
+ corresponding to a value of `True`. Set this to `False` to increment
123
+ the execution count after each suppressed command.
124
+
125
+ As an example, to use the IPython directive when `matplotlib` is not available,
126
+ one sets the backend to `None`::
127
+
128
+ ipython_mplbackend = None
129
+
130
+ An example usage of the directive is:
131
+
132
+ .. code-block:: rst
133
+
134
+ .. ipython::
135
+
136
+ In [1]: x = 1
137
+
138
+ In [2]: y = x**2
139
+
140
+ In [3]: print(y)
141
+
142
+ See http://matplotlib.org/sampledoc/ipython_directive.html for additional
143
+ documentation.
144
+
145
+ Pseudo-Decorators
146
+ =================
147
+
148
+ Note: Only one decorator is supported per input. If more than one decorator
149
+ is specified, then only the last one is used.
150
+
151
+ In addition to the Pseudo-Decorators/options described at the above link,
152
+ several enhancements have been made. The directive will emit a message to the
153
+ console at build-time if code-execution resulted in an exception or warning.
154
+ You can suppress these on a per-block basis by specifying the :okexcept:
155
+ or :okwarning: options:
156
+
157
+ .. code-block:: rst
158
+
159
+ .. ipython::
160
+ :okexcept:
161
+ :okwarning:
162
+
163
+ In [1]: 1/0
164
+ In [2]: # raise warning.
165
+
166
+ To Do
167
+ =====
168
+
169
+ - Turn the ad-hoc test() function into a real test suite.
170
+ - Break up ipython-specific functionality from matplotlib stuff into better
171
+ separated code.
172
+
173
+ """
174
+
175
+ # Authors
176
+ # =======
177
+ #
178
+ # - John D Hunter: original author.
179
+ # - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
180
+ # - VáclavŠmilauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
181
+ # - Skipper Seabold, refactoring, cleanups, pure python addition
182
+
183
+ #-----------------------------------------------------------------------------
184
+ # Imports
185
+ #-----------------------------------------------------------------------------
186
+
187
+ # Stdlib
188
+ import atexit
189
+ import errno
190
+ import os
191
+ import pathlib
192
+ import re
193
+ import sys
194
+ import tempfile
195
+ import ast
196
+ import warnings
197
+ import shutil
198
+ from io import StringIO
199
+ from typing import Any, Dict, Set
200
+
201
+ # Third-party
202
+ from docutils.parsers.rst import directives
203
+ from docutils.parsers.rst import Directive
204
+ from sphinx.util import logging
205
+
206
+ # Our own
207
+ from traitlets.config import Config
208
+ from IPython import InteractiveShell
209
+ from IPython.core.profiledir import ProfileDir
210
+
211
+ use_matplotlib = False
212
+ try:
213
+ import matplotlib
214
+ use_matplotlib = True
215
+ except Exception:
216
+ pass
217
+
218
+ #-----------------------------------------------------------------------------
219
+ # Globals
220
+ #-----------------------------------------------------------------------------
221
+ # for tokenizing blocks
222
+ COMMENT, INPUT, OUTPUT = range(3)
223
+
224
+ PSEUDO_DECORATORS = ["suppress", "verbatim", "savefig", "doctest"]
225
+
226
+ #-----------------------------------------------------------------------------
227
+ # Functions and class declarations
228
+ #-----------------------------------------------------------------------------
229
+
230
+ def block_parser(part, rgxin, rgxout, fmtin, fmtout):
231
+ """
232
+ part is a string of ipython text, comprised of at most one
233
+ input, one output, comments, and blank lines. The block parser
234
+ parses the text into a list of::
235
+
236
+ blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
237
+
238
+ where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
239
+ data is, depending on the type of token::
240
+
241
+ COMMENT : the comment string
242
+
243
+ INPUT: the (DECORATOR, INPUT_LINE, REST) where
244
+ DECORATOR: the input decorator (or None)
245
+ INPUT_LINE: the input as string (possibly multi-line)
246
+ REST : any stdout generated by the input line (not OUTPUT)
247
+
248
+ OUTPUT: the output string, possibly multi-line
249
+
250
+ """
251
+ block = []
252
+ lines = part.split('\n')
253
+ N = len(lines)
254
+ i = 0
255
+ decorator = None
256
+ while 1:
257
+
258
+ if i==N:
259
+ # nothing left to parse -- the last line
260
+ break
261
+
262
+ line = lines[i]
263
+ i += 1
264
+ line_stripped = line.strip()
265
+ if line_stripped.startswith('#'):
266
+ block.append((COMMENT, line))
267
+ continue
268
+
269
+ if any(
270
+ line_stripped.startswith("@" + pseudo_decorator)
271
+ for pseudo_decorator in PSEUDO_DECORATORS
272
+ ):
273
+ if decorator:
274
+ raise RuntimeError(
275
+ "Applying multiple pseudo-decorators on one line is not supported"
276
+ )
277
+ else:
278
+ decorator = line_stripped
279
+ continue
280
+
281
+ # does this look like an input line?
282
+ matchin = rgxin.match(line)
283
+ if matchin:
284
+ lineno, inputline = int(matchin.group(1)), matchin.group(2)
285
+
286
+ # the ....: continuation string
287
+ continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
288
+ Nc = len(continuation)
289
+ # input lines can continue on for more than one line, if
290
+ # we have a '\' line continuation char or a function call
291
+ # echo line 'print'. The input line can only be
292
+ # terminated by the end of the block or an output line, so
293
+ # we parse out the rest of the input line if it is
294
+ # multiline as well as any echo text
295
+
296
+ rest = []
297
+ while i<N:
298
+
299
+ # look ahead; if the next line is blank, or a comment, or
300
+ # an output line, we're done
301
+
302
+ nextline = lines[i]
303
+ matchout = rgxout.match(nextline)
304
+ # print("nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation)))
305
+ if matchout or nextline.startswith('#'):
306
+ break
307
+ elif nextline.startswith(continuation):
308
+ # The default ipython_rgx* treat the space following the colon as optional.
309
+ # However, If the space is there we must consume it or code
310
+ # employing the cython_magic extension will fail to execute.
311
+ #
312
+ # This works with the default ipython_rgx* patterns,
313
+ # If you modify them, YMMV.
314
+ nextline = nextline[Nc:]
315
+ if nextline and nextline[0] == ' ':
316
+ nextline = nextline[1:]
317
+
318
+ inputline += '\n' + nextline
319
+ else:
320
+ rest.append(nextline)
321
+ i+= 1
322
+
323
+ block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
324
+ continue
325
+
326
+ # if it looks like an output line grab all the text to the end
327
+ # of the block
328
+ matchout = rgxout.match(line)
329
+ if matchout:
330
+ lineno, output = int(matchout.group(1)), matchout.group(2)
331
+ if i<N-1:
332
+ output = '\n'.join([output] + lines[i:])
333
+
334
+ block.append((OUTPUT, output))
335
+ break
336
+
337
+ return block
338
+
339
+
340
+ class EmbeddedSphinxShell:
341
+ """An embedded IPython instance to run inside Sphinx"""
342
+
343
+ def __init__(self, exec_lines=None):
344
+
345
+ self.cout = StringIO()
346
+
347
+ if exec_lines is None:
348
+ exec_lines = []
349
+
350
+ # Create config object for IPython
351
+ config = Config()
352
+ config.HistoryManager.hist_file = ':memory:'
353
+ config.InteractiveShell.autocall = False
354
+ config.InteractiveShell.autoindent = False
355
+ config.InteractiveShell.colors = "nocolor"
356
+
357
+ # create a profile so instance history isn't saved
358
+ tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
359
+ profname = 'auto_profile_sphinx_build'
360
+ pdir = os.path.join(tmp_profile_dir,profname)
361
+ profile = ProfileDir.create_profile_dir(pdir)
362
+
363
+ # Create and initialize global ipython, but don't start its mainloop.
364
+ # This will persist across different EmbeddedSphinxShell instances.
365
+ IP = InteractiveShell.instance(config=config, profile_dir=profile)
366
+ atexit.register(self.cleanup)
367
+
368
+ # Store a few parts of IPython we'll need.
369
+ self.IP = IP
370
+ self.user_ns = self.IP.user_ns
371
+ self.user_global_ns = self.IP.user_global_ns
372
+
373
+ self.input = ''
374
+ self.output = ''
375
+ self.tmp_profile_dir = tmp_profile_dir
376
+
377
+ self.is_verbatim = False
378
+ self.is_doctest = False
379
+ self.is_suppress = False
380
+
381
+ # Optionally, provide more detailed information to shell.
382
+ # this is assigned by the SetUp method of IPythonDirective
383
+ # to point at itself.
384
+ #
385
+ # So, you can access handy things at self.directive.state
386
+ self.directive = None
387
+
388
+ # on the first call to the savefig decorator, we'll import
389
+ # pyplot as plt so we can make a call to the plt.gcf().savefig
390
+ self._pyplot_imported = False
391
+
392
+ # Prepopulate the namespace.
393
+ for line in exec_lines:
394
+ self.process_input_line(line, store_history=False)
395
+
396
+ def cleanup(self):
397
+ shutil.rmtree(self.tmp_profile_dir, ignore_errors=True)
398
+
399
+ def clear_cout(self):
400
+ self.cout.seek(0)
401
+ self.cout.truncate(0)
402
+
403
+ def process_input_line(self, line, store_history):
404
+ return self.process_input_lines([line], store_history=store_history)
405
+
406
+ def process_input_lines(self, lines, store_history=True):
407
+ """process the input, capturing stdout"""
408
+ stdout = sys.stdout
409
+ source_raw = '\n'.join(lines)
410
+ try:
411
+ sys.stdout = self.cout
412
+ self.IP.run_cell(source_raw, store_history=store_history)
413
+ finally:
414
+ sys.stdout = stdout
415
+
416
+ def process_image(self, decorator):
417
+ """
418
+ # build out an image directive like
419
+ # .. image:: somefile.png
420
+ # :width 4in
421
+ #
422
+ # from an input like
423
+ # savefig somefile.png width=4in
424
+ """
425
+ savefig_dir = self.savefig_dir
426
+ source_dir = self.source_dir
427
+ saveargs = decorator.split(' ')
428
+ filename = saveargs[1]
429
+ # insert relative path to image file in source
430
+ # as absolute path for Sphinx
431
+ # sphinx expects a posix path, even on Windows
432
+ path = pathlib.Path(savefig_dir, filename)
433
+ outfile = '/' + path.relative_to(source_dir).as_posix()
434
+
435
+ imagerows = ['.. image:: %s' % outfile]
436
+
437
+ for kwarg in saveargs[2:]:
438
+ arg, val = kwarg.split('=')
439
+ arg = arg.strip()
440
+ val = val.strip()
441
+ imagerows.append(' :%s: %s'%(arg, val))
442
+
443
+ image_file = os.path.basename(outfile) # only return file name
444
+ image_directive = '\n'.join(imagerows)
445
+ return image_file, image_directive
446
+
447
+ # Callbacks for each type of token
448
+ def process_input(self, data, input_prompt, lineno):
449
+ """
450
+ Process data block for INPUT token.
451
+
452
+ """
453
+ decorator, input, rest = data
454
+ image_file = None
455
+ image_directive = None
456
+
457
+ is_verbatim = decorator=='@verbatim' or self.is_verbatim
458
+ is_doctest = (decorator is not None and \
459
+ decorator.startswith('@doctest')) or self.is_doctest
460
+ is_suppress = decorator=='@suppress' or self.is_suppress
461
+ is_okexcept = decorator=='@okexcept' or self.is_okexcept
462
+ is_okwarning = decorator=='@okwarning' or self.is_okwarning
463
+ is_savefig = decorator is not None and \
464
+ decorator.startswith('@savefig')
465
+
466
+ input_lines = input.split('\n')
467
+ if len(input_lines) > 1:
468
+ if input_lines[-1] != "":
469
+ input_lines.append('') # make sure there's a blank line
470
+ # so splitter buffer gets reset
471
+
472
+ continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
473
+
474
+ if is_savefig:
475
+ image_file, image_directive = self.process_image(decorator)
476
+
477
+ ret = []
478
+ is_semicolon = False
479
+
480
+ # Hold the execution count, if requested to do so.
481
+ if is_suppress and self.hold_count:
482
+ store_history = False
483
+ else:
484
+ store_history = True
485
+
486
+ # Note: catch_warnings is not thread safe
487
+ with warnings.catch_warnings(record=True) as ws:
488
+ if input_lines[0].endswith(';'):
489
+ is_semicolon = True
490
+ #for i, line in enumerate(input_lines):
491
+
492
+ # process the first input line
493
+ if is_verbatim:
494
+ self.process_input_lines([''])
495
+ self.IP.execution_count += 1 # increment it anyway
496
+ else:
497
+ # only submit the line in non-verbatim mode
498
+ self.process_input_lines(input_lines, store_history=store_history)
499
+
500
+ if not is_suppress:
501
+ for i, line in enumerate(input_lines):
502
+ if i == 0:
503
+ formatted_line = '%s %s'%(input_prompt, line)
504
+ else:
505
+ formatted_line = '%s %s'%(continuation, line)
506
+ ret.append(formatted_line)
507
+
508
+ if not is_suppress and len(rest.strip()) and is_verbatim:
509
+ # The "rest" is the standard output of the input. This needs to be
510
+ # added when in verbatim mode. If there is no "rest", then we don't
511
+ # add it, as the new line will be added by the processed output.
512
+ ret.append(rest)
513
+
514
+ # Fetch the processed output. (This is not the submitted output.)
515
+ self.cout.seek(0)
516
+ processed_output = self.cout.read()
517
+ if not is_suppress and not is_semicolon:
518
+ #
519
+ # In IPythonDirective.run, the elements of `ret` are eventually
520
+ # combined such that '' entries correspond to newlines. So if
521
+ # `processed_output` is equal to '', then the adding it to `ret`
522
+ # ensures that there is a blank line between consecutive inputs
523
+ # that have no outputs, as in:
524
+ #
525
+ # In [1]: x = 4
526
+ #
527
+ # In [2]: x = 5
528
+ #
529
+ # When there is processed output, it has a '\n' at the tail end. So
530
+ # adding the output to `ret` will provide the necessary spacing
531
+ # between consecutive input/output blocks, as in:
532
+ #
533
+ # In [1]: x
534
+ # Out[1]: 5
535
+ #
536
+ # In [2]: x
537
+ # Out[2]: 5
538
+ #
539
+ # When there is stdout from the input, it also has a '\n' at the
540
+ # tail end, and so this ensures proper spacing as well. E.g.:
541
+ #
542
+ # In [1]: print(x)
543
+ # 5
544
+ #
545
+ # In [2]: x = 5
546
+ #
547
+ # When in verbatim mode, `processed_output` is empty (because
548
+ # nothing was passed to IP. Sometimes the submitted code block has
549
+ # an Out[] portion and sometimes it does not. When it does not, we
550
+ # need to ensure proper spacing, so we have to add '' to `ret`.
551
+ # However, if there is an Out[] in the submitted code, then we do
552
+ # not want to add a newline as `process_output` has stuff to add.
553
+ # The difficulty is that `process_input` doesn't know if
554
+ # `process_output` will be called---so it doesn't know if there is
555
+ # Out[] in the code block. The requires that we include a hack in
556
+ # `process_block`. See the comments there.
557
+ #
558
+ ret.append(processed_output)
559
+ elif is_semicolon:
560
+ # Make sure there is a newline after the semicolon.
561
+ ret.append('')
562
+
563
+ # context information
564
+ filename = "Unknown"
565
+ lineno = 0
566
+ if self.directive.state:
567
+ filename = self.directive.state.document.current_source
568
+ lineno = self.directive.state.document.current_line
569
+
570
+ # Use sphinx logger for warnings
571
+ logger = logging.getLogger(__name__)
572
+
573
+ # output any exceptions raised during execution to stdout
574
+ # unless :okexcept: has been specified.
575
+ if not is_okexcept and (
576
+ ("Traceback" in processed_output) or ("SyntaxError" in processed_output)
577
+ ):
578
+ s = "\n>>>" + ("-" * 73) + "\n"
579
+ s += "Exception in %s at block ending on line %s\n" % (filename, lineno)
580
+ s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n"
581
+ s += processed_output + "\n"
582
+ s += "<<<" + ("-" * 73)
583
+ logger.warning(s)
584
+ if self.warning_is_error:
585
+ raise RuntimeError(
586
+ "Unexpected exception in `{}` line {}".format(filename, lineno)
587
+ )
588
+
589
+ # output any warning raised during execution to stdout
590
+ # unless :okwarning: has been specified.
591
+ if not is_okwarning:
592
+ for w in ws:
593
+ s = "\n>>>" + ("-" * 73) + "\n"
594
+ s += "Warning in %s at block ending on line %s\n" % (filename, lineno)
595
+ s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n"
596
+ s += ("-" * 76) + "\n"
597
+ s += warnings.formatwarning(
598
+ w.message, w.category, w.filename, w.lineno, w.line
599
+ )
600
+ s += "<<<" + ("-" * 73)
601
+ logger.warning(s)
602
+ if self.warning_is_error:
603
+ raise RuntimeError(
604
+ "Unexpected warning in `{}` line {}".format(filename, lineno)
605
+ )
606
+
607
+ self.clear_cout()
608
+ return (ret, input_lines, processed_output,
609
+ is_doctest, decorator, image_file, image_directive)
610
+
611
+
612
+ def process_output(self, data, output_prompt, input_lines, output,
613
+ is_doctest, decorator, image_file):
614
+ """
615
+ Process data block for OUTPUT token.
616
+
617
+ """
618
+ # Recall: `data` is the submitted output, and `output` is the processed
619
+ # output from `input_lines`.
620
+
621
+ TAB = ' ' * 4
622
+
623
+ if is_doctest and output is not None:
624
+
625
+ found = output # This is the processed output
626
+ found = found.strip()
627
+ submitted = data.strip()
628
+
629
+ if self.directive is None:
630
+ source = 'Unavailable'
631
+ content = 'Unavailable'
632
+ else:
633
+ source = self.directive.state.document.current_source
634
+ content = self.directive.content
635
+ # Add tabs and join into a single string.
636
+ content = '\n'.join([TAB + line for line in content])
637
+
638
+ # Make sure the output contains the output prompt.
639
+ ind = found.find(output_prompt)
640
+ if ind < 0:
641
+ e = ('output does not contain output prompt\n\n'
642
+ 'Document source: {0}\n\n'
643
+ 'Raw content: \n{1}\n\n'
644
+ 'Input line(s):\n{TAB}{2}\n\n'
645
+ 'Output line(s):\n{TAB}{3}\n\n')
646
+ e = e.format(source, content, '\n'.join(input_lines),
647
+ repr(found), TAB=TAB)
648
+ raise RuntimeError(e)
649
+ found = found[len(output_prompt):].strip()
650
+
651
+ # Handle the actual doctest comparison.
652
+ if decorator.strip() == '@doctest':
653
+ # Standard doctest
654
+ if found != submitted:
655
+ e = ('doctest failure\n\n'
656
+ 'Document source: {0}\n\n'
657
+ 'Raw content: \n{1}\n\n'
658
+ 'On input line(s):\n{TAB}{2}\n\n'
659
+ 'we found output:\n{TAB}{3}\n\n'
660
+ 'instead of the expected:\n{TAB}{4}\n\n')
661
+ e = e.format(source, content, '\n'.join(input_lines),
662
+ repr(found), repr(submitted), TAB=TAB)
663
+ raise RuntimeError(e)
664
+ else:
665
+ self.custom_doctest(decorator, input_lines, found, submitted)
666
+
667
+ # When in verbatim mode, this holds additional submitted output
668
+ # to be written in the final Sphinx output.
669
+ # https://github.com/ipython/ipython/issues/5776
670
+ out_data = []
671
+
672
+ is_verbatim = decorator=='@verbatim' or self.is_verbatim
673
+ if is_verbatim and data.strip():
674
+ # Note that `ret` in `process_block` has '' as its last element if
675
+ # the code block was in verbatim mode. So if there is no submitted
676
+ # output, then we will have proper spacing only if we do not add
677
+ # an additional '' to `out_data`. This is why we condition on
678
+ # `and data.strip()`.
679
+
680
+ # The submitted output has no output prompt. If we want the
681
+ # prompt and the code to appear, we need to join them now
682
+ # instead of adding them separately---as this would create an
683
+ # undesired newline. How we do this ultimately depends on the
684
+ # format of the output regex. I'll do what works for the default
685
+ # prompt for now, and we might have to adjust if it doesn't work
686
+ # in other cases. Finally, the submitted output does not have
687
+ # a trailing newline, so we must add it manually.
688
+ out_data.append("{0} {1}\n".format(output_prompt, data))
689
+
690
+ return out_data
691
+
692
+ def process_comment(self, data):
693
+ """Process data fPblock for COMMENT token."""
694
+ if not self.is_suppress:
695
+ return [data]
696
+
697
+ def save_image(self, image_file):
698
+ """
699
+ Saves the image file to disk.
700
+ """
701
+ self.ensure_pyplot()
702
+ command = 'plt.gcf().savefig("%s")'%image_file
703
+ # print('SAVEFIG', command) # dbg
704
+ self.process_input_line('bookmark ipy_thisdir', store_history=False)
705
+ self.process_input_line('cd -b ipy_savedir', store_history=False)
706
+ self.process_input_line(command, store_history=False)
707
+ self.process_input_line('cd -b ipy_thisdir', store_history=False)
708
+ self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
709
+ self.clear_cout()
710
+
711
+ def process_block(self, block):
712
+ """
713
+ process block from the block_parser and return a list of processed lines
714
+ """
715
+ ret = []
716
+ output = None
717
+ input_lines = None
718
+ lineno = self.IP.execution_count
719
+
720
+ input_prompt = self.promptin % lineno
721
+ output_prompt = self.promptout % lineno
722
+ image_file = None
723
+ image_directive = None
724
+
725
+ found_input = False
726
+ for token, data in block:
727
+ if token == COMMENT:
728
+ out_data = self.process_comment(data)
729
+ elif token == INPUT:
730
+ found_input = True
731
+ (out_data, input_lines, output, is_doctest,
732
+ decorator, image_file, image_directive) = \
733
+ self.process_input(data, input_prompt, lineno)
734
+ elif token == OUTPUT:
735
+ if not found_input:
736
+
737
+ TAB = ' ' * 4
738
+ linenumber = 0
739
+ source = 'Unavailable'
740
+ content = 'Unavailable'
741
+ if self.directive:
742
+ linenumber = self.directive.state.document.current_line
743
+ source = self.directive.state.document.current_source
744
+ content = self.directive.content
745
+ # Add tabs and join into a single string.
746
+ content = '\n'.join([TAB + line for line in content])
747
+
748
+ e = ('\n\nInvalid block: Block contains an output prompt '
749
+ 'without an input prompt.\n\n'
750
+ 'Document source: {0}\n\n'
751
+ 'Content begins at line {1}: \n\n{2}\n\n'
752
+ 'Problematic block within content: \n\n{TAB}{3}\n\n')
753
+ e = e.format(source, linenumber, content, block, TAB=TAB)
754
+
755
+ # Write, rather than include in exception, since Sphinx
756
+ # will truncate tracebacks.
757
+ sys.stdout.write(e)
758
+ raise RuntimeError('An invalid block was detected.')
759
+ out_data = \
760
+ self.process_output(data, output_prompt, input_lines,
761
+ output, is_doctest, decorator,
762
+ image_file)
763
+ if out_data:
764
+ # Then there was user submitted output in verbatim mode.
765
+ # We need to remove the last element of `ret` that was
766
+ # added in `process_input`, as it is '' and would introduce
767
+ # an undesirable newline.
768
+ assert(ret[-1] == '')
769
+ del ret[-1]
770
+
771
+ if out_data:
772
+ ret.extend(out_data)
773
+
774
+ # save the image files
775
+ if image_file is not None:
776
+ self.save_image(image_file)
777
+
778
+ return ret, image_directive
779
+
780
+ def ensure_pyplot(self):
781
+ """
782
+ Ensures that pyplot has been imported into the embedded IPython shell.
783
+
784
+ Also, makes sure to set the backend appropriately if not set already.
785
+
786
+ """
787
+ # We are here if the @figure pseudo decorator was used. Thus, it's
788
+ # possible that we could be here even if python_mplbackend were set to
789
+ # `None`. That's also strange and perhaps worthy of raising an
790
+ # exception, but for now, we just set the backend to 'agg'.
791
+
792
+ if not self._pyplot_imported:
793
+ if 'matplotlib.backends' not in sys.modules:
794
+ # Then ipython_matplotlib was set to None but there was a
795
+ # call to the @figure decorator (and ipython_execlines did
796
+ # not set a backend).
797
+ #raise Exception("No backend was set, but @figure was used!")
798
+ import matplotlib
799
+ matplotlib.use('agg')
800
+
801
+ # Always import pyplot into embedded shell.
802
+ self.process_input_line('import matplotlib.pyplot as plt',
803
+ store_history=False)
804
+ self._pyplot_imported = True
805
+
806
+ def process_pure_python(self, content):
807
+ """
808
+ content is a list of strings. it is unedited directive content
809
+
810
+ This runs it line by line in the InteractiveShell, prepends
811
+ prompts as needed capturing stderr and stdout, then returns
812
+ the content as a list as if it were ipython code
813
+ """
814
+ output = []
815
+ savefig = False # keep up with this to clear figure
816
+ multiline = False # to handle line continuation
817
+ multiline_start = None
818
+ fmtin = self.promptin
819
+
820
+ ct = 0
821
+
822
+ for lineno, line in enumerate(content):
823
+
824
+ line_stripped = line.strip()
825
+ if not len(line):
826
+ output.append(line)
827
+ continue
828
+
829
+ # handle pseudo-decorators, whilst ensuring real python decorators are treated as input
830
+ if any(
831
+ line_stripped.startswith("@" + pseudo_decorator)
832
+ for pseudo_decorator in PSEUDO_DECORATORS
833
+ ):
834
+ output.extend([line])
835
+ if 'savefig' in line:
836
+ savefig = True # and need to clear figure
837
+ continue
838
+
839
+ # handle comments
840
+ if line_stripped.startswith('#'):
841
+ output.extend([line])
842
+ continue
843
+
844
+ # deal with lines checking for multiline
845
+ continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
846
+ if not multiline:
847
+ modified = u"%s %s" % (fmtin % ct, line_stripped)
848
+ output.append(modified)
849
+ ct += 1
850
+ try:
851
+ ast.parse(line_stripped)
852
+ output.append(u'')
853
+ except Exception: # on a multiline
854
+ multiline = True
855
+ multiline_start = lineno
856
+ else: # still on a multiline
857
+ modified = u'%s %s' % (continuation, line)
858
+ output.append(modified)
859
+
860
+ # if the next line is indented, it should be part of multiline
861
+ if len(content) > lineno + 1:
862
+ nextline = content[lineno + 1]
863
+ if len(nextline) - len(nextline.lstrip()) > 3:
864
+ continue
865
+ try:
866
+ mod = ast.parse(
867
+ '\n'.join(content[multiline_start:lineno+1]))
868
+ if isinstance(mod.body[0], ast.FunctionDef):
869
+ # check to see if we have the whole function
870
+ for element in mod.body[0].body:
871
+ if isinstance(element, ast.Return):
872
+ multiline = False
873
+ else:
874
+ output.append(u'')
875
+ multiline = False
876
+ except Exception:
877
+ pass
878
+
879
+ if savefig: # clear figure if plotted
880
+ self.ensure_pyplot()
881
+ self.process_input_line('plt.clf()', store_history=False)
882
+ self.clear_cout()
883
+ savefig = False
884
+
885
+ return output
886
+
887
+ def custom_doctest(self, decorator, input_lines, found, submitted):
888
+ """
889
+ Perform a specialized doctest.
890
+
891
+ """
892
+ from .custom_doctests import doctests
893
+
894
+ args = decorator.split()
895
+ doctest_type = args[1]
896
+ if doctest_type in doctests:
897
+ doctests[doctest_type](self, args, input_lines, found, submitted)
898
+ else:
899
+ e = "Invalid option to @doctest: {0}".format(doctest_type)
900
+ raise Exception(e)
901
+
902
+
903
+ class IPythonDirective(Directive):
904
+
905
+ has_content: bool = True
906
+ required_arguments: int = 0
907
+ optional_arguments: int = 4 # python, suppress, verbatim, doctest
908
+ final_argumuent_whitespace: bool = True
909
+ option_spec: Dict[str, Any] = {
910
+ "python": directives.unchanged,
911
+ "suppress": directives.flag,
912
+ "verbatim": directives.flag,
913
+ "doctest": directives.flag,
914
+ "okexcept": directives.flag,
915
+ "okwarning": directives.flag,
916
+ }
917
+
918
+ shell = None
919
+
920
+ seen_docs: Set = set()
921
+
922
+ def get_config_options(self):
923
+ # contains sphinx configuration variables
924
+ config = self.state.document.settings.env.config
925
+
926
+ # get config variables to set figure output directory
927
+ savefig_dir = config.ipython_savefig_dir
928
+ source_dir = self.state.document.settings.env.srcdir
929
+ savefig_dir = os.path.join(source_dir, savefig_dir)
930
+
931
+ # get regex and prompt stuff
932
+ rgxin = config.ipython_rgxin
933
+ rgxout = config.ipython_rgxout
934
+ warning_is_error= config.ipython_warning_is_error
935
+ promptin = config.ipython_promptin
936
+ promptout = config.ipython_promptout
937
+ mplbackend = config.ipython_mplbackend
938
+ exec_lines = config.ipython_execlines
939
+ hold_count = config.ipython_holdcount
940
+
941
+ return (savefig_dir, source_dir, rgxin, rgxout,
942
+ promptin, promptout, mplbackend, exec_lines, hold_count, warning_is_error)
943
+
944
+ def setup(self):
945
+ # Get configuration values.
946
+ (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout,
947
+ mplbackend, exec_lines, hold_count, warning_is_error) = self.get_config_options()
948
+
949
+ try:
950
+ os.makedirs(savefig_dir)
951
+ except OSError as e:
952
+ if e.errno != errno.EEXIST:
953
+ raise
954
+
955
+ if self.shell is None:
956
+ # We will be here many times. However, when the
957
+ # EmbeddedSphinxShell is created, its interactive shell member
958
+ # is the same for each instance.
959
+
960
+ if mplbackend and 'matplotlib.backends' not in sys.modules and use_matplotlib:
961
+ import matplotlib
962
+ matplotlib.use(mplbackend)
963
+
964
+ # Must be called after (potentially) importing matplotlib and
965
+ # setting its backend since exec_lines might import pylab.
966
+ self.shell = EmbeddedSphinxShell(exec_lines)
967
+
968
+ # Store IPython directive to enable better error messages
969
+ self.shell.directive = self
970
+
971
+ # reset the execution count if we haven't processed this doc
972
+ #NOTE: this may be borked if there are multiple seen_doc tmp files
973
+ #check time stamp?
974
+ if self.state.document.current_source not in self.seen_docs:
975
+ self.shell.IP.history_manager.reset()
976
+ self.shell.IP.execution_count = 1
977
+ self.seen_docs.add(self.state.document.current_source)
978
+
979
+ # and attach to shell so we don't have to pass them around
980
+ self.shell.rgxin = rgxin
981
+ self.shell.rgxout = rgxout
982
+ self.shell.promptin = promptin
983
+ self.shell.promptout = promptout
984
+ self.shell.savefig_dir = savefig_dir
985
+ self.shell.source_dir = source_dir
986
+ self.shell.hold_count = hold_count
987
+ self.shell.warning_is_error = warning_is_error
988
+
989
+ # setup bookmark for saving figures directory
990
+ self.shell.process_input_line(
991
+ 'bookmark ipy_savedir "%s"' % savefig_dir, store_history=False
992
+ )
993
+ self.shell.clear_cout()
994
+
995
+ return rgxin, rgxout, promptin, promptout
996
+
997
+ def teardown(self):
998
+ # delete last bookmark
999
+ self.shell.process_input_line('bookmark -d ipy_savedir',
1000
+ store_history=False)
1001
+ self.shell.clear_cout()
1002
+
1003
+ def run(self):
1004
+ debug = False
1005
+
1006
+ #TODO, any reason block_parser can't be a method of embeddable shell
1007
+ # then we wouldn't have to carry these around
1008
+ rgxin, rgxout, promptin, promptout = self.setup()
1009
+
1010
+ options = self.options
1011
+ self.shell.is_suppress = 'suppress' in options
1012
+ self.shell.is_doctest = 'doctest' in options
1013
+ self.shell.is_verbatim = 'verbatim' in options
1014
+ self.shell.is_okexcept = 'okexcept' in options
1015
+ self.shell.is_okwarning = 'okwarning' in options
1016
+
1017
+ # handle pure python code
1018
+ if 'python' in self.arguments:
1019
+ content = self.content
1020
+ self.content = self.shell.process_pure_python(content)
1021
+
1022
+ # parts consists of all text within the ipython-block.
1023
+ # Each part is an input/output block.
1024
+ parts = '\n'.join(self.content).split('\n\n')
1025
+
1026
+ lines = ['.. code-block:: ipython', '']
1027
+ figures = []
1028
+
1029
+ # Use sphinx logger for warnings
1030
+ logger = logging.getLogger(__name__)
1031
+
1032
+ for part in parts:
1033
+ block = block_parser(part, rgxin, rgxout, promptin, promptout)
1034
+ if len(block):
1035
+ rows, figure = self.shell.process_block(block)
1036
+ for row in rows:
1037
+ lines.extend([' {0}'.format(line)
1038
+ for line in row.split('\n')])
1039
+
1040
+ if figure is not None:
1041
+ figures.append(figure)
1042
+ else:
1043
+ message = 'Code input with no code at {}, line {}'\
1044
+ .format(
1045
+ self.state.document.current_source,
1046
+ self.state.document.current_line)
1047
+ if self.shell.warning_is_error:
1048
+ raise RuntimeError(message)
1049
+ else:
1050
+ logger.warning(message)
1051
+
1052
+ for figure in figures:
1053
+ lines.append('')
1054
+ lines.extend(figure.split('\n'))
1055
+ lines.append('')
1056
+
1057
+ if len(lines) > 2:
1058
+ if debug:
1059
+ print('\n'.join(lines))
1060
+ else:
1061
+ # This has to do with input, not output. But if we comment
1062
+ # these lines out, then no IPython code will appear in the
1063
+ # final output.
1064
+ self.state_machine.insert_input(
1065
+ lines, self.state_machine.input_lines.source(0))
1066
+
1067
+ # cleanup
1068
+ self.teardown()
1069
+
1070
+ return []
1071
+
1072
+ # Enable as a proper Sphinx directive
1073
+ def setup(app):
1074
+ setup.app = app
1075
+
1076
+ app.add_directive('ipython', IPythonDirective)
1077
+ app.add_config_value('ipython_savefig_dir', 'savefig', 'env')
1078
+ app.add_config_value('ipython_warning_is_error', True, 'env')
1079
+ app.add_config_value('ipython_rgxin',
1080
+ re.compile(r'In \[(\d+)\]:\s?(.*)\s*'), 'env')
1081
+ app.add_config_value('ipython_rgxout',
1082
+ re.compile(r'Out\[(\d+)\]:\s?(.*)\s*'), 'env')
1083
+ app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
1084
+ app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
1085
+
1086
+ # We could just let matplotlib pick whatever is specified as the default
1087
+ # backend in the matplotlibrc file, but this would cause issues if the
1088
+ # backend didn't work in headless environments. For this reason, 'agg'
1089
+ # is a good default backend choice.
1090
+ app.add_config_value('ipython_mplbackend', 'agg', 'env')
1091
+
1092
+ # If the user sets this config value to `None`, then EmbeddedSphinxShell's
1093
+ # __init__ method will treat it as [].
1094
+ execlines = ['import numpy as np']
1095
+ if use_matplotlib:
1096
+ execlines.append('import matplotlib.pyplot as plt')
1097
+ app.add_config_value('ipython_execlines', execlines, 'env')
1098
+
1099
+ app.add_config_value('ipython_holdcount', True, 'env')
1100
+
1101
+ metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
1102
+ return metadata
1103
+
1104
+ # Simple smoke test, needs to be converted to a proper automatic test.
1105
+ def test():
1106
+
1107
+ examples = [
1108
+ r"""
1109
+ In [9]: pwd
1110
+ Out[9]: '/home/jdhunter/py4science/book'
1111
+
1112
+ In [10]: cd bookdata/
1113
+ /home/jdhunter/py4science/book/bookdata
1114
+
1115
+ In [2]: from pylab import *
1116
+
1117
+ In [2]: ion()
1118
+
1119
+ In [3]: im = imread('stinkbug.png')
1120
+
1121
+ @savefig mystinkbug.png width=4in
1122
+ In [4]: imshow(im)
1123
+ Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
1124
+
1125
+ """,
1126
+ r"""
1127
+
1128
+ In [1]: x = 'hello world'
1129
+
1130
+ # string methods can be
1131
+ # used to alter the string
1132
+ @doctest
1133
+ In [2]: x.upper()
1134
+ Out[2]: 'HELLO WORLD'
1135
+
1136
+ @verbatim
1137
+ In [3]: x.st<TAB>
1138
+ x.startswith x.strip
1139
+ """,
1140
+ r"""
1141
+
1142
+ In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
1143
+ .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
1144
+
1145
+ In [131]: print url.split('&')
1146
+ ['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv']
1147
+
1148
+ In [60]: import urllib
1149
+
1150
+ """,
1151
+ r"""\
1152
+
1153
+ In [133]: import numpy.random
1154
+
1155
+ @suppress
1156
+ In [134]: numpy.random.seed(2358)
1157
+
1158
+ @doctest
1159
+ In [135]: numpy.random.rand(10,2)
1160
+ Out[135]:
1161
+ array([[ 0.64524308, 0.59943846],
1162
+ [ 0.47102322, 0.8715456 ],
1163
+ [ 0.29370834, 0.74776844],
1164
+ [ 0.99539577, 0.1313423 ],
1165
+ [ 0.16250302, 0.21103583],
1166
+ [ 0.81626524, 0.1312433 ],
1167
+ [ 0.67338089, 0.72302393],
1168
+ [ 0.7566368 , 0.07033696],
1169
+ [ 0.22591016, 0.77731835],
1170
+ [ 0.0072729 , 0.34273127]])
1171
+
1172
+ """,
1173
+
1174
+ r"""
1175
+ In [106]: print x
1176
+ jdh
1177
+
1178
+ In [109]: for i in range(10):
1179
+ .....: print i
1180
+ .....:
1181
+ .....:
1182
+ 0
1183
+ 1
1184
+ 2
1185
+ 3
1186
+ 4
1187
+ 5
1188
+ 6
1189
+ 7
1190
+ 8
1191
+ 9
1192
+ """,
1193
+
1194
+ r"""
1195
+
1196
+ In [144]: from pylab import *
1197
+
1198
+ In [145]: ion()
1199
+
1200
+ # use a semicolon to suppress the output
1201
+ @savefig test_hist.png width=4in
1202
+ In [151]: hist(np.random.randn(10000), 100);
1203
+
1204
+
1205
+ @savefig test_plot.png width=4in
1206
+ In [151]: plot(np.random.randn(10000), 'o');
1207
+ """,
1208
+
1209
+ r"""
1210
+ # use a semicolon to suppress the output
1211
+ In [151]: plt.clf()
1212
+
1213
+ @savefig plot_simple.png width=4in
1214
+ In [151]: plot([1,2,3])
1215
+
1216
+ @savefig hist_simple.png width=4in
1217
+ In [151]: hist(np.random.randn(10000), 100);
1218
+
1219
+ """,
1220
+ r"""
1221
+ # update the current fig
1222
+ In [151]: ylabel('number')
1223
+
1224
+ In [152]: title('normal distribution')
1225
+
1226
+
1227
+ @savefig hist_with_text.png
1228
+ In [153]: grid(True)
1229
+
1230
+ @doctest float
1231
+ In [154]: 0.1 + 0.2
1232
+ Out[154]: 0.3
1233
+
1234
+ @doctest float
1235
+ In [155]: np.arange(16).reshape(4,4)
1236
+ Out[155]:
1237
+ array([[ 0, 1, 2, 3],
1238
+ [ 4, 5, 6, 7],
1239
+ [ 8, 9, 10, 11],
1240
+ [12, 13, 14, 15]])
1241
+
1242
+ In [1]: x = np.arange(16, dtype=float).reshape(4,4)
1243
+
1244
+ In [2]: x[0,0] = np.inf
1245
+
1246
+ In [3]: x[0,1] = np.nan
1247
+
1248
+ @doctest float
1249
+ In [4]: x
1250
+ Out[4]:
1251
+ array([[ inf, nan, 2., 3.],
1252
+ [ 4., 5., 6., 7.],
1253
+ [ 8., 9., 10., 11.],
1254
+ [ 12., 13., 14., 15.]])
1255
+
1256
+
1257
+ """,
1258
+ ]
1259
+ # skip local-file depending first example:
1260
+ examples = examples[1:]
1261
+
1262
+ #ipython_directive.DEBUG = True # dbg
1263
+ #options = dict(suppress=True) # dbg
1264
+ options = {}
1265
+ for example in examples:
1266
+ content = example.split('\n')
1267
+ IPythonDirective('debug', arguments=None, options=options,
1268
+ content=content, lineno=0,
1269
+ content_offset=None, block_text=None,
1270
+ state=None, state_machine=None,
1271
+ )
1272
+
1273
+ # Run test suite as a script
1274
+ if __name__=='__main__':
1275
+ if not os.path.isdir('_static'):
1276
+ os.mkdir('_static')
1277
+ test()
1278
+ print('All OK? Check figures in _static/')
lib/python3.12/site-packages/IPython/testing/__init__.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Testing support (tools to test IPython itself).
2
+ """
3
+
4
+ #-----------------------------------------------------------------------------
5
+ # Copyright (C) 2009-2011 The IPython Development Team
6
+ #
7
+ # Distributed under the terms of the BSD License. The full license is in
8
+ # the file COPYING, distributed as part of this software.
9
+ #-----------------------------------------------------------------------------
10
+
11
+
12
+ import os
13
+
14
+ #-----------------------------------------------------------------------------
15
+ # Constants
16
+ #-----------------------------------------------------------------------------
17
+
18
+ # We scale all timeouts via this factor, slow machines can increase it
19
+ IPYTHON_TESTING_TIMEOUT_SCALE = float(os.getenv(
20
+ 'IPYTHON_TESTING_TIMEOUT_SCALE', 1))
lib/python3.12/site-packages/IPython/testing/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (420 Bytes). View file
 
lib/python3.12/site-packages/IPython/testing/__pycache__/decorators.cpython-312.pyc ADDED
Binary file (4.97 kB). View file
 
lib/python3.12/site-packages/IPython/testing/__pycache__/globalipapp.cpython-312.pyc ADDED
Binary file (3.87 kB). View file
 
lib/python3.12/site-packages/IPython/testing/__pycache__/ipunittest.cpython-312.pyc ADDED
Binary file (7.39 kB). View file
 
lib/python3.12/site-packages/IPython/testing/__pycache__/skipdoctest.cpython-312.pyc ADDED
Binary file (902 Bytes). View file
 
lib/python3.12/site-packages/IPython/testing/__pycache__/tools.cpython-312.pyc ADDED
Binary file (17.1 kB). View file
 
lib/python3.12/site-packages/IPython/testing/decorators.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import sys
4
+ import tempfile
5
+ from importlib import import_module
6
+
7
+ import pytest
8
+
9
+ # Expose the unittest-driven decorators
10
+ from .ipunittest import ipdoctest, ipdocstring
11
+
12
+ def skipif(skip_condition, msg=None):
13
+ """Make function raise SkipTest exception if skip_condition is true
14
+
15
+ Parameters
16
+ ----------
17
+
18
+ skip_condition : bool or callable
19
+ Flag to determine whether to skip test. If the condition is a
20
+ callable, it is used at runtime to dynamically make the decision. This
21
+ is useful for tests that may require costly imports, to delay the cost
22
+ until the test suite is actually executed.
23
+ msg : string
24
+ Message to give on raising a SkipTest exception.
25
+
26
+ Returns
27
+ -------
28
+ decorator : function
29
+ Decorator, which, when applied to a function, causes SkipTest
30
+ to be raised when the skip_condition was True, and the function
31
+ to be called normally otherwise.
32
+ """
33
+ if msg is None:
34
+ msg = "Test skipped due to test condition."
35
+
36
+ assert isinstance(skip_condition, bool)
37
+ return pytest.mark.skipif(skip_condition, reason=msg)
38
+
39
+
40
+ # A version with the condition set to true, common case just to attach a message
41
+ # to a skip decorator
42
+ def skip(msg=None):
43
+ """Decorator factory - mark a test function for skipping from test suite.
44
+
45
+ Parameters
46
+ ----------
47
+ msg : string
48
+ Optional message to be added.
49
+
50
+ Returns
51
+ -------
52
+ decorator : function
53
+ Decorator, which, when applied to a function, causes SkipTest
54
+ to be raised, with the optional message added.
55
+ """
56
+ if msg and not isinstance(msg, str):
57
+ raise ValueError(
58
+ "invalid object passed to `@skip` decorator, did you "
59
+ "meant `@skip()` with brackets ?"
60
+ )
61
+ return skipif(True, msg)
62
+
63
+
64
+ def onlyif(condition, msg):
65
+ """The reverse from skipif, see skipif for details."""
66
+
67
+ return skipif(not condition, msg)
68
+
69
+
70
+ # -----------------------------------------------------------------------------
71
+ # Utility functions for decorators
72
+ def module_not_available(module):
73
+ """Can module be imported? Returns true if module does NOT import.
74
+
75
+ This is used to make a decorator to skip tests that require module to be
76
+ available, but delay the 'import numpy' to test execution time.
77
+ """
78
+ try:
79
+ mod = import_module(module)
80
+ mod_not_avail = False
81
+ except ImportError:
82
+ mod_not_avail = True
83
+
84
+ return mod_not_avail
85
+
86
+
87
+ # -----------------------------------------------------------------------------
88
+ # Decorators for public use
89
+
90
+ # Decorators to skip certain tests on specific platforms.
91
+ skip_win32 = skipif(sys.platform == "win32", "This test does not run under Windows")
92
+
93
+
94
+ # Decorators to skip tests if not on specific platforms.
95
+ skip_if_not_win32 = skipif(sys.platform != "win32", "This test only runs under Windows")
96
+ skip_if_not_osx = skipif(
97
+ not sys.platform.startswith("darwin"), "This test only runs under macOS"
98
+ )
99
+
100
+ _x11_skip_cond = (
101
+ sys.platform not in ("darwin", "win32") and os.environ.get("DISPLAY", "") == ""
102
+ )
103
+ _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
104
+
105
+ skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
106
+
107
+ # Other skip decorators
108
+
109
+ # generic skip without module
110
+ skip_without = lambda mod: skipif(
111
+ module_not_available(mod), "This test requires %s" % mod
112
+ )
113
+
114
+ skipif_not_numpy = skip_without("numpy")
115
+
116
+ skipif_not_matplotlib = skip_without("matplotlib")
117
+
118
+ # A null 'decorator', useful to make more readable code that needs to pick
119
+ # between different decorators based on OS or other conditions
120
+ null_deco = lambda f: f
121
+
122
+ # Some tests only run where we can use unicode paths. Note that we can't just
123
+ # check os.path.supports_unicode_filenames, which is always False on Linux.
124
+ try:
125
+ f = tempfile.NamedTemporaryFile(prefix="tmp€")
126
+ except UnicodeEncodeError:
127
+ unicode_paths = False
128
+ # TODO: should this be finnally ?
129
+ else:
130
+ unicode_paths = True
131
+ f.close()
132
+
133
+ onlyif_unicode_paths = onlyif(
134
+ unicode_paths,
135
+ ("This test is only applicable where we can use unicode in filenames."),
136
+ )
137
+
138
+
139
+ def onlyif_cmds_exist(*commands):
140
+ """
141
+ Decorator to skip test when at least one of `commands` is not found.
142
+ """
143
+ for cmd in commands:
144
+ reason = f"This test runs only if command '{cmd}' is installed"
145
+ if not shutil.which(cmd):
146
+ return pytest.mark.skip(reason=reason)
147
+ return null_deco
lib/python3.12/site-packages/IPython/testing/globalipapp.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Global IPython app to support test running.
2
+
3
+ We must start our own ipython object and heavily muck with it so that all the
4
+ modifications IPython makes to system behavior don't send the doctest machinery
5
+ into a fit. This code should be considered a gross hack, but it gets the job
6
+ done.
7
+ """
8
+
9
+ # Copyright (c) IPython Development Team.
10
+ # Distributed under the terms of the Modified BSD License.
11
+
12
+ import builtins as builtin_mod
13
+ import sys
14
+ import types
15
+
16
+ from pathlib import Path
17
+
18
+ from . import tools
19
+
20
+ from IPython.core import page
21
+ from IPython.utils import io
22
+ from IPython.terminal.interactiveshell import TerminalInteractiveShell
23
+
24
+
25
+ def get_ipython():
26
+ # This will get replaced by the real thing once we start IPython below
27
+ return start_ipython()
28
+
29
+
30
+ # A couple of methods to override those in the running IPython to interact
31
+ # better with doctest (doctest captures on raw stdout, so we need to direct
32
+ # various types of output there otherwise it will miss them).
33
+
34
+ def xsys(self, cmd):
35
+ """Replace the default system call with a capturing one for doctest.
36
+ """
37
+ # We use getoutput, but we need to strip it because pexpect captures
38
+ # the trailing newline differently from commands.getoutput
39
+ print(self.getoutput(cmd, split=False, depth=1).rstrip(), end='', file=sys.stdout)
40
+ sys.stdout.flush()
41
+
42
+
43
+ def _showtraceback(self, etype, evalue, stb):
44
+ """Print the traceback purely on stdout for doctest to capture it.
45
+ """
46
+ print(self.InteractiveTB.stb2text(stb), file=sys.stdout)
47
+
48
+
49
+ def start_ipython():
50
+ """Start a global IPython shell, which we need for IPython-specific syntax.
51
+ """
52
+ global get_ipython
53
+
54
+ # This function should only ever run once!
55
+ if hasattr(start_ipython, 'already_called'):
56
+ return
57
+ start_ipython.already_called = True
58
+
59
+ # Store certain global objects that IPython modifies
60
+ _displayhook = sys.displayhook
61
+ _excepthook = sys.excepthook
62
+ _main = sys.modules.get('__main__')
63
+
64
+ # Create custom argv and namespaces for our IPython to be test-friendly
65
+ config = tools.default_config()
66
+ config.TerminalInteractiveShell.simple_prompt = True
67
+
68
+ # Create and initialize our test-friendly IPython instance.
69
+ shell = TerminalInteractiveShell.instance(config=config,
70
+ )
71
+
72
+ # A few more tweaks needed for playing nicely with doctests...
73
+
74
+ # remove history file
75
+ shell.tempfiles.append(Path(config.HistoryManager.hist_file))
76
+
77
+ # These traps are normally only active for interactive use, set them
78
+ # permanently since we'll be mocking interactive sessions.
79
+ shell.builtin_trap.activate()
80
+
81
+ # Modify the IPython system call with one that uses getoutput, so that we
82
+ # can capture subcommands and print them to Python's stdout, otherwise the
83
+ # doctest machinery would miss them.
84
+ shell.system = types.MethodType(xsys, shell)
85
+
86
+ shell._showtraceback = types.MethodType(_showtraceback, shell)
87
+
88
+ # IPython is ready, now clean up some global state...
89
+
90
+ # Deactivate the various python system hooks added by ipython for
91
+ # interactive convenience so we don't confuse the doctest system
92
+ sys.modules['__main__'] = _main
93
+ sys.displayhook = _displayhook
94
+ sys.excepthook = _excepthook
95
+
96
+ # So that ipython magics and aliases can be doctested (they work by making
97
+ # a call into a global _ip object). Also make the top-level get_ipython
98
+ # now return this without recursively calling here again.
99
+ _ip = shell
100
+ get_ipython = _ip.get_ipython
101
+ builtin_mod._ip = _ip
102
+ builtin_mod.ip = _ip
103
+ builtin_mod.get_ipython = get_ipython
104
+
105
+ # Override paging, so we don't require user interaction during the tests.
106
+ def nopage(strng, start=0, screen_lines=0, pager_cmd=None):
107
+ if isinstance(strng, dict):
108
+ strng = strng.get('text/plain', '')
109
+ print(strng)
110
+
111
+ page.orig_page = page.pager_page
112
+ page.pager_page = nopage
113
+
114
+ return _ip
lib/python3.12/site-packages/IPython/testing/ipunittest.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Experimental code for cleaner support of IPython syntax with unittest.
2
+
3
+ In IPython up until 0.10, we've used very hacked up nose machinery for running
4
+ tests with IPython special syntax, and this has proved to be extremely slow.
5
+ This module provides decorators to try a different approach, stemming from a
6
+ conversation Brian and I (FP) had about this problem Sept/09.
7
+
8
+ The goal is to be able to easily write simple functions that can be seen by
9
+ unittest as tests, and ultimately for these to support doctests with full
10
+ IPython syntax. Nose already offers this based on naming conventions and our
11
+ hackish plugins, but we are seeking to move away from nose dependencies if
12
+ possible.
13
+
14
+ This module follows a different approach, based on decorators.
15
+
16
+ - A decorator called @ipdoctest can mark any function as having a docstring
17
+ that should be viewed as a doctest, but after syntax conversion.
18
+
19
+ Authors
20
+ -------
21
+
22
+ - Fernando Perez <Fernando.Perez@berkeley.edu>
23
+ """
24
+
25
+
26
+ #-----------------------------------------------------------------------------
27
+ # Copyright (C) 2009-2011 The IPython Development Team
28
+ #
29
+ # Distributed under the terms of the BSD License. The full license is in
30
+ # the file COPYING, distributed as part of this software.
31
+ #-----------------------------------------------------------------------------
32
+
33
+ #-----------------------------------------------------------------------------
34
+ # Imports
35
+ #-----------------------------------------------------------------------------
36
+
37
+ # Stdlib
38
+ import re
39
+ import sys
40
+ import unittest
41
+ import builtins
42
+ from doctest import DocTestFinder, DocTestRunner, TestResults
43
+ from IPython.terminal.interactiveshell import InteractiveShell
44
+
45
+ #-----------------------------------------------------------------------------
46
+ # Classes and functions
47
+ #-----------------------------------------------------------------------------
48
+
49
+ def count_failures(runner):
50
+ """Count number of failures in a doctest runner.
51
+
52
+ Code modeled after the summarize() method in doctest.
53
+ """
54
+ if sys.version_info < (3, 13):
55
+ return [TestResults(f, t) for f, t in runner._name2ft.values() if f > 0]
56
+ else:
57
+ return [
58
+ TestResults(failure, try_)
59
+ for failure, try_, skip in runner._stats.values()
60
+ if failure > 0
61
+ ]
62
+
63
+
64
+ class IPython2PythonConverter:
65
+ """Convert IPython 'syntax' to valid Python.
66
+
67
+ Eventually this code may grow to be the full IPython syntax conversion
68
+ implementation, but for now it only does prompt conversion."""
69
+
70
+ def __init__(self):
71
+ self.rps1 = re.compile(r'In\ \[\d+\]: ')
72
+ self.rps2 = re.compile(r'\ \ \ \.\.\.+: ')
73
+ self.rout = re.compile(r'Out\[\d+\]: \s*?\n?')
74
+ self.pyps1 = '>>> '
75
+ self.pyps2 = '... '
76
+ self.rpyps1 = re.compile (r'(\s*%s)(.*)$' % self.pyps1)
77
+ self.rpyps2 = re.compile (r'(\s*%s)(.*)$' % self.pyps2)
78
+
79
+ def __call__(self, ds):
80
+ """Convert IPython prompts to python ones in a string."""
81
+ from . import globalipapp
82
+
83
+ pyps1 = '>>> '
84
+ pyps2 = '... '
85
+ pyout = ''
86
+
87
+ dnew = ds
88
+ dnew = self.rps1.sub(pyps1, dnew)
89
+ dnew = self.rps2.sub(pyps2, dnew)
90
+ dnew = self.rout.sub(pyout, dnew)
91
+ ip = InteractiveShell.instance()
92
+
93
+ # Convert input IPython source into valid Python.
94
+ out = []
95
+ newline = out.append
96
+ for line in dnew.splitlines():
97
+
98
+ mps1 = self.rpyps1.match(line)
99
+ if mps1 is not None:
100
+ prompt, text = mps1.groups()
101
+ newline(prompt+ip.prefilter(text, False))
102
+ continue
103
+
104
+ mps2 = self.rpyps2.match(line)
105
+ if mps2 is not None:
106
+ prompt, text = mps2.groups()
107
+ newline(prompt+ip.prefilter(text, True))
108
+ continue
109
+
110
+ newline(line)
111
+ newline('') # ensure a closing newline, needed by doctest
112
+ # print("PYSRC:", '\n'.join(out)) # dbg
113
+ return '\n'.join(out)
114
+
115
+ #return dnew
116
+
117
+
118
+ class Doc2UnitTester:
119
+ """Class whose instances act as a decorator for docstring testing.
120
+
121
+ In practice we're only likely to need one instance ever, made below (though
122
+ no attempt is made at turning it into a singleton, there is no need for
123
+ that).
124
+ """
125
+ def __init__(self, verbose=False):
126
+ """New decorator.
127
+
128
+ Parameters
129
+ ----------
130
+
131
+ verbose : boolean, optional (False)
132
+ Passed to the doctest finder and runner to control verbosity.
133
+ """
134
+ self.verbose = verbose
135
+ # We can reuse the same finder for all instances
136
+ self.finder = DocTestFinder(verbose=verbose, recurse=False)
137
+
138
+ def __call__(self, func):
139
+ """Use as a decorator: doctest a function's docstring as a unittest.
140
+
141
+ This version runs normal doctests, but the idea is to make it later run
142
+ ipython syntax instead."""
143
+
144
+ # Capture the enclosing instance with a different name, so the new
145
+ # class below can see it without confusion regarding its own 'self'
146
+ # that will point to the test instance at runtime
147
+ d2u = self
148
+
149
+ # Rewrite the function's docstring to have python syntax
150
+ if func.__doc__ is not None:
151
+ func.__doc__ = ip2py(func.__doc__)
152
+
153
+ # Now, create a tester object that is a real unittest instance, so
154
+ # normal unittest machinery (or Nose, or Trial) can find it.
155
+ class Tester(unittest.TestCase):
156
+ def test(self):
157
+ # Make a new runner per function to be tested
158
+ runner = DocTestRunner(verbose=d2u.verbose)
159
+ for the_test in d2u.finder.find(func, func.__name__):
160
+ runner.run(the_test)
161
+ failed = count_failures(runner)
162
+ if failed:
163
+ # Since we only looked at a single function's docstring,
164
+ # failed should contain at most one item. More than that
165
+ # is a case we can't handle and should error out on
166
+ if len(failed) > 1:
167
+ err = "Invalid number of test results: %s" % failed
168
+ raise ValueError(err)
169
+ # Report a normal failure.
170
+ self.fail('failed doctests: %s' % str(failed[0]))
171
+
172
+ # Rename it so test reports have the original signature.
173
+ Tester.__name__ = func.__name__
174
+ return Tester
175
+
176
+
177
+ def ipdocstring(func):
178
+ """Change the function docstring via ip2py.
179
+ """
180
+ if func.__doc__ is not None:
181
+ func.__doc__ = ip2py(func.__doc__)
182
+ return func
183
+
184
+
185
+ # Make an instance of the classes for public use
186
+ ipdoctest = Doc2UnitTester()
187
+ ip2py = IPython2PythonConverter()
lib/python3.12/site-packages/IPython/testing/plugin/__init__.py ADDED
File without changes
lib/python3.12/site-packages/IPython/testing/plugin/dtexample.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Simple example using doctests.
2
+
3
+ This file just contains doctests both using plain python and IPython prompts.
4
+ All tests should be loaded by nose.
5
+ """
6
+
7
+ import os
8
+
9
+
10
+ def pyfunc():
11
+ """Some pure python tests...
12
+
13
+ >>> pyfunc()
14
+ 'pyfunc'
15
+
16
+ >>> import os
17
+
18
+ >>> 2+3
19
+ 5
20
+
21
+ >>> for i in range(3):
22
+ ... print(i, end=' ')
23
+ ... print(i+1, end=' ')
24
+ ...
25
+ 0 1 1 2 2 3
26
+ """
27
+ return 'pyfunc'
28
+
29
+ def ipfunc():
30
+ """Some ipython tests...
31
+
32
+ In [1]: import os
33
+
34
+ In [3]: 2+3
35
+ Out[3]: 5
36
+
37
+ In [26]: for i in range(3):
38
+ ....: print(i, end=' ')
39
+ ....: print(i+1, end=' ')
40
+ ....:
41
+ 0 1 1 2 2 3
42
+
43
+
44
+ It's OK to use '_' for the last result, but do NOT try to use IPython's
45
+ numbered history of _NN outputs, since those won't exist under the
46
+ doctest environment:
47
+
48
+ In [7]: 'hi'
49
+ Out[7]: 'hi'
50
+
51
+ In [8]: print(repr(_))
52
+ 'hi'
53
+
54
+ In [7]: 3+4
55
+ Out[7]: 7
56
+
57
+ In [8]: _+3
58
+ Out[8]: 10
59
+
60
+ In [9]: ipfunc()
61
+ Out[9]: 'ipfunc'
62
+ """
63
+ return "ipfunc"
64
+
65
+
66
+ def ipos():
67
+ """Examples that access the operating system work:
68
+
69
+ In [1]: !echo hello
70
+ hello
71
+
72
+ In [2]: !echo hello > /tmp/foo_iptest
73
+
74
+ In [3]: !cat /tmp/foo_iptest
75
+ hello
76
+
77
+ In [4]: rm -f /tmp/foo_iptest
78
+ """
79
+ pass
80
+
81
+
82
+ ipos.__skip_doctest__ = os.name == "nt"
83
+
84
+
85
+ def ranfunc():
86
+ """A function with some random output.
87
+
88
+ Normal examples are verified as usual:
89
+ >>> 1+3
90
+ 4
91
+
92
+ But if you put '# random' in the output, it is ignored:
93
+ >>> 1+3
94
+ junk goes here... # random
95
+
96
+ >>> 1+2
97
+ again, anything goes #random
98
+ if multiline, the random mark is only needed once.
99
+
100
+ >>> 1+2
101
+ You can also put the random marker at the end:
102
+ # random
103
+
104
+ >>> 1+2
105
+ # random
106
+ .. or at the beginning.
107
+
108
+ More correct input is properly verified:
109
+ >>> ranfunc()
110
+ 'ranfunc'
111
+ """
112
+ return 'ranfunc'
113
+
114
+
115
+ def random_all():
116
+ """A function where we ignore the output of ALL examples.
117
+
118
+ Examples:
119
+
120
+ # all-random
121
+
122
+ This mark tells the testing machinery that all subsequent examples should
123
+ be treated as random (ignoring their output). They are still executed,
124
+ so if a they raise an error, it will be detected as such, but their
125
+ output is completely ignored.
126
+
127
+ >>> 1+3
128
+ junk goes here...
129
+
130
+ >>> 1+3
131
+ klasdfj;
132
+
133
+ >>> 1+2
134
+ again, anything goes
135
+ blah...
136
+ """
137
+ pass
138
+
139
+ def iprand():
140
+ """Some ipython tests with random output.
141
+
142
+ In [7]: 3+4
143
+ Out[7]: 7
144
+
145
+ In [8]: print('hello')
146
+ world # random
147
+
148
+ In [9]: iprand()
149
+ Out[9]: 'iprand'
150
+ """
151
+ return 'iprand'
152
+
153
+ def iprand_all():
154
+ """Some ipython tests with fully random output.
155
+
156
+ # all-random
157
+
158
+ In [7]: 1
159
+ Out[7]: 99
160
+
161
+ In [8]: print('hello')
162
+ world
163
+
164
+ In [9]: iprand_all()
165
+ Out[9]: 'junk'
166
+ """
167
+ return 'iprand_all'
lib/python3.12/site-packages/IPython/testing/plugin/ipdoctest.py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Nose Plugin that supports IPython doctests.
2
+
3
+ Limitations:
4
+
5
+ - When generating examples for use as doctests, make sure that you have
6
+ pretty-printing OFF. This can be done either by setting the
7
+ ``PlainTextFormatter.pprint`` option in your configuration file to False, or
8
+ by interactively disabling it with %Pprint. This is required so that IPython
9
+ output matches that of normal Python, which is used by doctest for internal
10
+ execution.
11
+
12
+ - Do not rely on specific prompt numbers for results (such as using
13
+ '_34==True', for example). For IPython tests run via an external process the
14
+ prompt numbers may be different, and IPython tests run as normal python code
15
+ won't even have these special _NN variables set at all.
16
+ """
17
+
18
+ #-----------------------------------------------------------------------------
19
+ # Module imports
20
+
21
+ # From the standard library
22
+ import doctest
23
+ import logging
24
+ import re
25
+
26
+ from testpath import modified_env
27
+
28
+ #-----------------------------------------------------------------------------
29
+ # Module globals and other constants
30
+ #-----------------------------------------------------------------------------
31
+
32
+ log = logging.getLogger(__name__)
33
+
34
+
35
+ #-----------------------------------------------------------------------------
36
+ # Classes and functions
37
+ #-----------------------------------------------------------------------------
38
+
39
+
40
+ class DocTestFinder(doctest.DocTestFinder):
41
+ def _get_test(self, obj, name, module, globs, source_lines):
42
+ test = super()._get_test(obj, name, module, globs, source_lines)
43
+
44
+ if bool(getattr(obj, "__skip_doctest__", False)) and test is not None:
45
+ for example in test.examples:
46
+ example.options[doctest.SKIP] = True
47
+
48
+ return test
49
+
50
+
51
+ class IPDoctestOutputChecker(doctest.OutputChecker):
52
+ """Second-chance checker with support for random tests.
53
+
54
+ If the default comparison doesn't pass, this checker looks in the expected
55
+ output string for flags that tell us to ignore the output.
56
+ """
57
+
58
+ random_re = re.compile(r'#\s*random\s+')
59
+
60
+ def check_output(self, want, got, optionflags):
61
+ """Check output, accepting special markers embedded in the output.
62
+
63
+ If the output didn't pass the default validation but the special string
64
+ '#random' is included, we accept it."""
65
+
66
+ # Let the original tester verify first, in case people have valid tests
67
+ # that happen to have a comment saying '#random' embedded in.
68
+ ret = doctest.OutputChecker.check_output(self, want, got,
69
+ optionflags)
70
+ if not ret and self.random_re.search(want):
71
+ # print('RANDOM OK:',want, file=sys.stderr) # dbg
72
+ return True
73
+
74
+ return ret
75
+
76
+
77
+ # A simple subclassing of the original with a different class name, so we can
78
+ # distinguish and treat differently IPython examples from pure python ones.
79
+ class IPExample(doctest.Example): pass
80
+
81
+
82
+ class IPDocTestParser(doctest.DocTestParser):
83
+ """
84
+ A class used to parse strings containing doctest examples.
85
+
86
+ Note: This is a version modified to properly recognize IPython input and
87
+ convert any IPython examples into valid Python ones.
88
+ """
89
+ # This regular expression is used to find doctest examples in a
90
+ # string. It defines three groups: `source` is the source code
91
+ # (including leading indentation and prompts); `indent` is the
92
+ # indentation of the first (PS1) line of the source code; and
93
+ # `want` is the expected output (including leading indentation).
94
+
95
+ # Classic Python prompts or default IPython ones
96
+ _PS1_PY = r'>>>'
97
+ _PS2_PY = r'\.\.\.'
98
+
99
+ _PS1_IP = r'In\ \[\d+\]:'
100
+ _PS2_IP = r'\ \ \ \.\.\.+:'
101
+
102
+ _RE_TPL = r'''
103
+ # Source consists of a PS1 line followed by zero or more PS2 lines.
104
+ (?P<source>
105
+ (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
106
+ (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
107
+ \n? # a newline
108
+ # Want consists of any non-blank lines that do not start with PS1.
109
+ (?P<want> (?:(?![ ]*$) # Not a blank line
110
+ (?![ ]*%s) # Not a line starting with PS1
111
+ (?![ ]*%s) # Not a line starting with PS2
112
+ .*$\n? # But any other line
113
+ )*)
114
+ '''
115
+
116
+ _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
117
+ re.MULTILINE | re.VERBOSE)
118
+
119
+ _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
120
+ re.MULTILINE | re.VERBOSE)
121
+
122
+ # Mark a test as being fully random. In this case, we simply append the
123
+ # random marker ('#random') to each individual example's output. This way
124
+ # we don't need to modify any other code.
125
+ _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
126
+
127
+ def ip2py(self,source):
128
+ """Convert input IPython source into valid Python."""
129
+ block = _ip.input_transformer_manager.transform_cell(source)
130
+ if len(block.splitlines()) == 1:
131
+ return _ip.prefilter(block)
132
+ else:
133
+ return block
134
+
135
+ def parse(self, string, name='<string>'):
136
+ """
137
+ Divide the given string into examples and intervening text,
138
+ and return them as a list of alternating Examples and strings.
139
+ Line numbers for the Examples are 0-based. The optional
140
+ argument `name` is a name identifying this string, and is only
141
+ used for error messages.
142
+ """
143
+
144
+ # print('Parse string:\n',string) # dbg
145
+
146
+ string = string.expandtabs()
147
+ # If all lines begin with the same indentation, then strip it.
148
+ min_indent = self._min_indent(string)
149
+ if min_indent > 0:
150
+ string = '\n'.join([l[min_indent:] for l in string.split('\n')])
151
+
152
+ output = []
153
+ charno, lineno = 0, 0
154
+
155
+ # We make 'all random' tests by adding the '# random' mark to every
156
+ # block of output in the test.
157
+ if self._RANDOM_TEST.search(string):
158
+ random_marker = '\n# random'
159
+ else:
160
+ random_marker = ''
161
+
162
+ # Whether to convert the input from ipython to python syntax
163
+ ip2py = False
164
+ # Find all doctest examples in the string. First, try them as Python
165
+ # examples, then as IPython ones
166
+ terms = list(self._EXAMPLE_RE_PY.finditer(string))
167
+ if terms:
168
+ # Normal Python example
169
+ Example = doctest.Example
170
+ else:
171
+ # It's an ipython example.
172
+ terms = list(self._EXAMPLE_RE_IP.finditer(string))
173
+ Example = IPExample
174
+ ip2py = True
175
+
176
+ for m in terms:
177
+ # Add the pre-example text to `output`.
178
+ output.append(string[charno:m.start()])
179
+ # Update lineno (lines before this example)
180
+ lineno += string.count('\n', charno, m.start())
181
+ # Extract info from the regexp match.
182
+ (source, options, want, exc_msg) = \
183
+ self._parse_example(m, name, lineno,ip2py)
184
+
185
+ # Append the random-output marker (it defaults to empty in most
186
+ # cases, it's only non-empty for 'all-random' tests):
187
+ want += random_marker
188
+
189
+ # Create an Example, and add it to the list.
190
+ if not self._IS_BLANK_OR_COMMENT(source):
191
+ output.append(Example(source, want, exc_msg,
192
+ lineno=lineno,
193
+ indent=min_indent+len(m.group('indent')),
194
+ options=options))
195
+ # Update lineno (lines inside this example)
196
+ lineno += string.count('\n', m.start(), m.end())
197
+ # Update charno.
198
+ charno = m.end()
199
+ # Add any remaining post-example text to `output`.
200
+ output.append(string[charno:])
201
+ return output
202
+
203
+ def _parse_example(self, m, name, lineno,ip2py=False):
204
+ """
205
+ Given a regular expression match from `_EXAMPLE_RE` (`m`),
206
+ return a pair `(source, want)`, where `source` is the matched
207
+ example's source code (with prompts and indentation stripped);
208
+ and `want` is the example's expected output (with indentation
209
+ stripped).
210
+
211
+ `name` is the string's name, and `lineno` is the line number
212
+ where the example starts; both are used for error messages.
213
+
214
+ Optional:
215
+ `ip2py`: if true, filter the input via IPython to convert the syntax
216
+ into valid python.
217
+ """
218
+
219
+ # Get the example's indentation level.
220
+ indent = len(m.group('indent'))
221
+
222
+ # Divide source into lines; check that they're properly
223
+ # indented; and then strip their indentation & prompts.
224
+ source_lines = m.group('source').split('\n')
225
+
226
+ # We're using variable-length input prompts
227
+ ps1 = m.group('ps1')
228
+ ps2 = m.group('ps2')
229
+ ps1_len = len(ps1)
230
+
231
+ self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
232
+ if ps2:
233
+ self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
234
+
235
+ source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
236
+
237
+ if ip2py:
238
+ # Convert source input from IPython into valid Python syntax
239
+ source = self.ip2py(source)
240
+
241
+ # Divide want into lines; check that it's properly indented; and
242
+ # then strip the indentation. Spaces before the last newline should
243
+ # be preserved, so plain rstrip() isn't good enough.
244
+ want = m.group('want')
245
+ want_lines = want.split('\n')
246
+ if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
247
+ del want_lines[-1] # forget final newline & spaces after it
248
+ self._check_prefix(want_lines, ' '*indent, name,
249
+ lineno + len(source_lines))
250
+
251
+ # Remove ipython output prompt that might be present in the first line
252
+ want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
253
+
254
+ want = '\n'.join([wl[indent:] for wl in want_lines])
255
+
256
+ # If `want` contains a traceback message, then extract it.
257
+ m = self._EXCEPTION_RE.match(want)
258
+ if m:
259
+ exc_msg = m.group('msg')
260
+ else:
261
+ exc_msg = None
262
+
263
+ # Extract options from the source.
264
+ options = self._find_options(source, name, lineno)
265
+
266
+ return source, options, want, exc_msg
267
+
268
+ def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
269
+ """
270
+ Given the lines of a source string (including prompts and
271
+ leading indentation), check to make sure that every prompt is
272
+ followed by a space character. If any line is not followed by
273
+ a space character, then raise ValueError.
274
+
275
+ Note: IPython-modified version which takes the input prompt length as a
276
+ parameter, so that prompts of variable length can be dealt with.
277
+ """
278
+ space_idx = indent+ps1_len
279
+ min_len = space_idx+1
280
+ for i, line in enumerate(lines):
281
+ if len(line) >= min_len and line[space_idx] != ' ':
282
+ raise ValueError('line %r of the docstring for %s '
283
+ 'lacks blank after %s: %r' %
284
+ (lineno+i+1, name,
285
+ line[indent:space_idx], line))
286
+
287
+
288
+ SKIP = doctest.register_optionflag('SKIP')
289
+
290
+
291
+ class IPDocTestRunner(doctest.DocTestRunner):
292
+ """Test runner that synchronizes the IPython namespace with test globals.
293
+ """
294
+
295
+ def run(self, test, compileflags=None, out=None, clear_globs=True):
296
+ # Override terminal size to standardise traceback format
297
+ with modified_env({'COLUMNS': '80', 'LINES': '24'}):
298
+ return super(IPDocTestRunner,self).run(test,
299
+ compileflags,out,clear_globs)
lib/python3.12/site-packages/IPython/testing/plugin/pytest_ipdoctest.py ADDED
@@ -0,0 +1,877 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Based on Pytest doctest.py
2
+ # Original license:
3
+ # The MIT License (MIT)
4
+ #
5
+ # Copyright (c) 2004-2021 Holger Krekel and others
6
+ """Discover and run ipdoctests in modules and test files."""
7
+
8
+ import bdb
9
+ import builtins
10
+ import inspect
11
+ import os
12
+ import platform
13
+ import sys
14
+ import traceback
15
+ import types
16
+ import warnings
17
+ from contextlib import contextmanager
18
+ from pathlib import Path
19
+ from typing import (
20
+ TYPE_CHECKING,
21
+ Any,
22
+ Dict,
23
+ List,
24
+ Optional,
25
+ Tuple,
26
+ Type,
27
+ Union,
28
+ )
29
+ from re import Pattern
30
+ from collections.abc import Callable, Generator, Iterable, Sequence
31
+
32
+ import pytest
33
+ from _pytest import outcomes
34
+ from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr
35
+ from _pytest._io import TerminalWriter
36
+ from _pytest.compat import safe_getattr
37
+ from _pytest.config import Config
38
+ from _pytest.config.argparsing import Parser
39
+
40
+ try:
41
+ from _pytest.fixtures import TopRequest as FixtureRequest
42
+ except ImportError:
43
+ from _pytest.fixtures import FixtureRequest
44
+ from _pytest.nodes import Collector
45
+ from _pytest.outcomes import OutcomeException
46
+ from _pytest.pathlib import fnmatch_ex, import_path
47
+ from _pytest.python_api import approx
48
+ from _pytest.warning_types import PytestWarning
49
+
50
+ if TYPE_CHECKING:
51
+ import doctest
52
+
53
+ from .ipdoctest import IPDoctestOutputChecker
54
+
55
+ DOCTEST_REPORT_CHOICE_NONE = "none"
56
+ DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
57
+ DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
58
+ DOCTEST_REPORT_CHOICE_UDIFF = "udiff"
59
+ DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure"
60
+
61
+ DOCTEST_REPORT_CHOICES = (
62
+ DOCTEST_REPORT_CHOICE_NONE,
63
+ DOCTEST_REPORT_CHOICE_CDIFF,
64
+ DOCTEST_REPORT_CHOICE_NDIFF,
65
+ DOCTEST_REPORT_CHOICE_UDIFF,
66
+ DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE,
67
+ )
68
+
69
+ # Lazy definition of runner class
70
+ RUNNER_CLASS = None
71
+ # Lazy definition of output checker class
72
+ CHECKER_CLASS: Optional[Type["IPDoctestOutputChecker"]] = None
73
+
74
+ pytest_version = tuple([int(part) for part in pytest.__version__.split(".")])
75
+
76
+
77
+ def pytest_addoption(parser: Parser) -> None:
78
+ parser.addini(
79
+ "ipdoctest_optionflags",
80
+ "option flags for ipdoctests",
81
+ type="args",
82
+ default=["ELLIPSIS"],
83
+ )
84
+ parser.addini(
85
+ "ipdoctest_encoding", "encoding used for ipdoctest files", default="utf-8"
86
+ )
87
+ group = parser.getgroup("collect")
88
+ group.addoption(
89
+ "--ipdoctest-modules",
90
+ action="store_true",
91
+ default=False,
92
+ help="run ipdoctests in all .py modules",
93
+ dest="ipdoctestmodules",
94
+ )
95
+ group.addoption(
96
+ "--ipdoctest-report",
97
+ type=str.lower,
98
+ default="udiff",
99
+ help="choose another output format for diffs on ipdoctest failure",
100
+ choices=DOCTEST_REPORT_CHOICES,
101
+ dest="ipdoctestreport",
102
+ )
103
+ group.addoption(
104
+ "--ipdoctest-glob",
105
+ action="append",
106
+ default=[],
107
+ metavar="pat",
108
+ help="ipdoctests file matching pattern, default: test*.txt",
109
+ dest="ipdoctestglob",
110
+ )
111
+ group.addoption(
112
+ "--ipdoctest-ignore-import-errors",
113
+ action="store_true",
114
+ default=False,
115
+ help="ignore ipdoctest ImportErrors",
116
+ dest="ipdoctest_ignore_import_errors",
117
+ )
118
+ group.addoption(
119
+ "--ipdoctest-continue-on-failure",
120
+ action="store_true",
121
+ default=False,
122
+ help="for a given ipdoctest, continue to run after the first failure",
123
+ dest="ipdoctest_continue_on_failure",
124
+ )
125
+
126
+
127
+ def pytest_unconfigure() -> None:
128
+ global RUNNER_CLASS
129
+
130
+ RUNNER_CLASS = None
131
+
132
+
133
+ def pytest_collect_file(
134
+ file_path: Path,
135
+ parent: Collector,
136
+ ) -> Optional[Union["IPDoctestModule", "IPDoctestTextfile"]]:
137
+ config = parent.config
138
+ if file_path.suffix == ".py":
139
+ if config.option.ipdoctestmodules and not any(
140
+ (_is_setup_py(file_path), _is_main_py(file_path))
141
+ ):
142
+ mod: IPDoctestModule = IPDoctestModule.from_parent(parent, path=file_path)
143
+ return mod
144
+ elif _is_ipdoctest(config, file_path, parent):
145
+ txt: IPDoctestTextfile = IPDoctestTextfile.from_parent(parent, path=file_path)
146
+ return txt
147
+ return None
148
+
149
+
150
+ if pytest_version[0] < 7:
151
+ _collect_file = pytest_collect_file
152
+
153
+ def pytest_collect_file(
154
+ path,
155
+ parent: Collector,
156
+ ) -> Optional[Union["IPDoctestModule", "IPDoctestTextfile"]]:
157
+ return _collect_file(Path(path), parent)
158
+
159
+ _import_path = import_path
160
+
161
+ def import_path(path, root):
162
+ import py.path
163
+
164
+ return _import_path(py.path.local(path))
165
+
166
+
167
+ def _is_setup_py(path: Path) -> bool:
168
+ if path.name != "setup.py":
169
+ return False
170
+ contents = path.read_bytes()
171
+ return b"setuptools" in contents or b"distutils" in contents
172
+
173
+
174
+ def _is_ipdoctest(config: Config, path: Path, parent: Collector) -> bool:
175
+ if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path):
176
+ return True
177
+ globs = config.getoption("ipdoctestglob") or ["test*.txt"]
178
+ return any(fnmatch_ex(glob, path) for glob in globs)
179
+
180
+
181
+ def _is_main_py(path: Path) -> bool:
182
+ return path.name == "__main__.py"
183
+
184
+
185
+ class ReprFailDoctest(TerminalRepr):
186
+ def __init__(
187
+ self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]]
188
+ ) -> None:
189
+ self.reprlocation_lines = reprlocation_lines
190
+
191
+ def toterminal(self, tw: TerminalWriter) -> None:
192
+ for reprlocation, lines in self.reprlocation_lines:
193
+ for line in lines:
194
+ tw.line(line)
195
+ reprlocation.toterminal(tw)
196
+
197
+
198
+ class MultipleDoctestFailures(Exception):
199
+ def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None:
200
+ super().__init__()
201
+ self.failures = failures
202
+
203
+
204
+ def _init_runner_class() -> Type["IPDocTestRunner"]:
205
+ import doctest
206
+ from .ipdoctest import IPDocTestRunner
207
+
208
+ class PytestDoctestRunner(IPDocTestRunner):
209
+ """Runner to collect failures.
210
+
211
+ Note that the out variable in this case is a list instead of a
212
+ stdout-like object.
213
+ """
214
+
215
+ def __init__(
216
+ self,
217
+ checker: Optional["IPDoctestOutputChecker"] = None,
218
+ verbose: Optional[bool] = None,
219
+ optionflags: int = 0,
220
+ continue_on_failure: bool = True,
221
+ ) -> None:
222
+ super().__init__(checker=checker, verbose=verbose, optionflags=optionflags)
223
+ self.continue_on_failure = continue_on_failure
224
+
225
+ def report_failure(
226
+ self,
227
+ out,
228
+ test: "doctest.DocTest",
229
+ example: "doctest.Example",
230
+ got: str,
231
+ ) -> None:
232
+ failure = doctest.DocTestFailure(test, example, got)
233
+ if self.continue_on_failure:
234
+ out.append(failure)
235
+ else:
236
+ raise failure
237
+
238
+ def report_unexpected_exception(
239
+ self,
240
+ out,
241
+ test: "doctest.DocTest",
242
+ example: "doctest.Example",
243
+ exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType],
244
+ ) -> None:
245
+ if isinstance(exc_info[1], OutcomeException):
246
+ raise exc_info[1]
247
+ if isinstance(exc_info[1], bdb.BdbQuit):
248
+ outcomes.exit("Quitting debugger")
249
+ failure = doctest.UnexpectedException(test, example, exc_info)
250
+ if self.continue_on_failure:
251
+ out.append(failure)
252
+ else:
253
+ raise failure
254
+
255
+ return PytestDoctestRunner
256
+
257
+
258
+ def _get_runner(
259
+ checker: Optional["IPDoctestOutputChecker"] = None,
260
+ verbose: Optional[bool] = None,
261
+ optionflags: int = 0,
262
+ continue_on_failure: bool = True,
263
+ ) -> "IPDocTestRunner":
264
+ # We need this in order to do a lazy import on doctest
265
+ global RUNNER_CLASS
266
+ if RUNNER_CLASS is None:
267
+ RUNNER_CLASS = _init_runner_class()
268
+ # Type ignored because the continue_on_failure argument is only defined on
269
+ # PytestDoctestRunner, which is lazily defined so can't be used as a type.
270
+ return RUNNER_CLASS( # type: ignore
271
+ checker=checker,
272
+ verbose=verbose,
273
+ optionflags=optionflags,
274
+ continue_on_failure=continue_on_failure,
275
+ )
276
+
277
+
278
+ class IPDoctestItem(pytest.Item):
279
+ _user_ns_orig: Dict[str, Any]
280
+
281
+ def __init__(
282
+ self,
283
+ name: str,
284
+ parent: "Union[IPDoctestTextfile, IPDoctestModule]",
285
+ runner: Optional["IPDocTestRunner"] = None,
286
+ dtest: Optional["doctest.DocTest"] = None,
287
+ ) -> None:
288
+ super().__init__(name, parent)
289
+ self.runner = runner
290
+ self.dtest = dtest
291
+ self.obj = None
292
+ self.fixture_request: Optional[FixtureRequest] = None
293
+ self._user_ns_orig = {}
294
+
295
+ @classmethod
296
+ def from_parent( # type: ignore
297
+ cls,
298
+ parent: "Union[IPDoctestTextfile, IPDoctestModule]",
299
+ *,
300
+ name: str,
301
+ runner: "IPDocTestRunner",
302
+ dtest: "doctest.DocTest",
303
+ ):
304
+ # incompatible signature due to imposed limits on subclass
305
+ """The public named constructor."""
306
+ return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)
307
+
308
+ def setup(self) -> None:
309
+ if self.dtest is not None:
310
+ self.fixture_request = _setup_fixtures(self)
311
+ globs = dict(getfixture=self.fixture_request.getfixturevalue)
312
+ for name, value in self.fixture_request.getfixturevalue(
313
+ "ipdoctest_namespace"
314
+ ).items():
315
+ globs[name] = value
316
+ self.dtest.globs.update(globs)
317
+
318
+ from .ipdoctest import IPExample
319
+
320
+ if isinstance(self.dtest.examples[0], IPExample):
321
+ # for IPython examples *only*, we swap the globals with the ipython
322
+ # namespace, after updating it with the globals (which doctest
323
+ # fills with the necessary info from the module being tested).
324
+ self._user_ns_orig = {}
325
+ self._user_ns_orig.update(_ip.user_ns)
326
+ _ip.user_ns.update(self.dtest.globs)
327
+ # We must remove the _ key in the namespace, so that Python's
328
+ # doctest code sets it naturally
329
+ _ip.user_ns.pop("_", None)
330
+ _ip.user_ns["__builtins__"] = builtins
331
+ self.dtest.globs = _ip.user_ns
332
+
333
+ def teardown(self) -> None:
334
+ from .ipdoctest import IPExample
335
+
336
+ # Undo the test.globs reassignment we made
337
+ if isinstance(self.dtest.examples[0], IPExample):
338
+ self.dtest.globs = {}
339
+ _ip.user_ns.clear()
340
+ _ip.user_ns.update(self._user_ns_orig)
341
+ del self._user_ns_orig
342
+
343
+ self.dtest.globs.clear()
344
+
345
+ def runtest(self) -> None:
346
+ assert self.dtest is not None
347
+ assert self.runner is not None
348
+ _check_all_skipped(self.dtest)
349
+ self._disable_output_capturing_for_darwin()
350
+ failures: List[doctest.DocTestFailure] = []
351
+
352
+ # exec(compile(..., "single", ...), ...) puts result in builtins._
353
+ had_underscore_value = hasattr(builtins, "_")
354
+ underscore_original_value = getattr(builtins, "_", None)
355
+
356
+ # Save our current directory and switch out to the one where the
357
+ # test was originally created, in case another doctest did a
358
+ # directory change. We'll restore this in the finally clause.
359
+ curdir = os.getcwd()
360
+ os.chdir(self.fspath.dirname)
361
+ try:
362
+ # Type ignored because we change the type of `out` from what
363
+ # ipdoctest expects.
364
+ self.runner.run(self.dtest, out=failures, clear_globs=False) # type: ignore[arg-type]
365
+ finally:
366
+ os.chdir(curdir)
367
+ if had_underscore_value:
368
+ setattr(builtins, "_", underscore_original_value)
369
+ elif hasattr(builtins, "_"):
370
+ delattr(builtins, "_")
371
+
372
+ if failures:
373
+ raise MultipleDoctestFailures(failures)
374
+
375
+ def _disable_output_capturing_for_darwin(self) -> None:
376
+ """Disable output capturing. Otherwise, stdout is lost to ipdoctest (pytest#985)."""
377
+ if platform.system() != "Darwin":
378
+ return
379
+ capman = self.config.pluginmanager.getplugin("capturemanager")
380
+ if capman:
381
+ capman.suspend_global_capture(in_=True)
382
+ out, err = capman.read_global_capture()
383
+ sys.stdout.write(out)
384
+ sys.stderr.write(err)
385
+
386
+ # TODO: Type ignored -- breaks Liskov Substitution.
387
+ def repr_failure( # type: ignore[override]
388
+ self,
389
+ excinfo: ExceptionInfo[BaseException],
390
+ ) -> Union[str, TerminalRepr]:
391
+ import doctest
392
+
393
+ failures: Optional[
394
+ Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]
395
+ ] = None
396
+ if isinstance(
397
+ excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
398
+ ):
399
+ failures = [excinfo.value]
400
+ elif isinstance(excinfo.value, MultipleDoctestFailures):
401
+ failures = excinfo.value.failures
402
+
403
+ if failures is None:
404
+ return super().repr_failure(excinfo)
405
+
406
+ reprlocation_lines = []
407
+ for failure in failures:
408
+ example = failure.example
409
+ test = failure.test
410
+ filename = test.filename
411
+ if test.lineno is None:
412
+ lineno = None
413
+ else:
414
+ lineno = test.lineno + example.lineno + 1
415
+ message = type(failure).__name__
416
+ # TODO: ReprFileLocation doesn't expect a None lineno.
417
+ reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type]
418
+ checker = _get_checker()
419
+ report_choice = _get_report_choice(self.config.getoption("ipdoctestreport"))
420
+ if lineno is not None:
421
+ assert failure.test.docstring is not None
422
+ lines = failure.test.docstring.splitlines(False)
423
+ # add line numbers to the left of the error message
424
+ assert test.lineno is not None
425
+ lines = [
426
+ "%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines)
427
+ ]
428
+ # trim docstring error lines to 10
429
+ lines = lines[max(example.lineno - 9, 0) : example.lineno + 1]
430
+ else:
431
+ lines = [
432
+ "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example"
433
+ ]
434
+ indent = ">>>"
435
+ for line in example.source.splitlines():
436
+ lines.append(f"??? {indent} {line}")
437
+ indent = "..."
438
+ if isinstance(failure, doctest.DocTestFailure):
439
+ lines += checker.output_difference(
440
+ example, failure.got, report_choice
441
+ ).split("\n")
442
+ else:
443
+ inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info)
444
+ lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)]
445
+ lines += [
446
+ x.strip("\n") for x in traceback.format_exception(*failure.exc_info)
447
+ ]
448
+ reprlocation_lines.append((reprlocation, lines))
449
+ return ReprFailDoctest(reprlocation_lines)
450
+
451
+ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
452
+ assert self.dtest is not None
453
+ return self.path, self.dtest.lineno, "[ipdoctest] %s" % self.name
454
+
455
+ if pytest_version[0] < 7:
456
+
457
+ @property
458
+ def path(self) -> Path:
459
+ return Path(self.fspath)
460
+
461
+
462
+ def _get_flag_lookup() -> Dict[str, int]:
463
+ import doctest
464
+
465
+ return dict(
466
+ DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
467
+ DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE,
468
+ NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
469
+ ELLIPSIS=doctest.ELLIPSIS,
470
+ IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
471
+ COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
472
+ ALLOW_UNICODE=_get_allow_unicode_flag(),
473
+ ALLOW_BYTES=_get_allow_bytes_flag(),
474
+ NUMBER=_get_number_flag(),
475
+ )
476
+
477
+
478
+ def get_optionflags(parent):
479
+ optionflags_str = parent.config.getini("ipdoctest_optionflags")
480
+ flag_lookup_table = _get_flag_lookup()
481
+ flag_acc = 0
482
+ for flag in optionflags_str:
483
+ flag_acc |= flag_lookup_table[flag]
484
+ return flag_acc
485
+
486
+
487
+ def _get_continue_on_failure(config):
488
+ continue_on_failure = config.getvalue("ipdoctest_continue_on_failure")
489
+ if continue_on_failure:
490
+ # We need to turn off this if we use pdb since we should stop at
491
+ # the first failure.
492
+ if config.getvalue("usepdb"):
493
+ continue_on_failure = False
494
+ return continue_on_failure
495
+
496
+
497
+ class IPDoctestTextfile(pytest.Module):
498
+ obj = None
499
+
500
+ def collect(self) -> Iterable[IPDoctestItem]:
501
+ import doctest
502
+ from .ipdoctest import IPDocTestParser
503
+
504
+ # Inspired by doctest.testfile; ideally we would use it directly,
505
+ # but it doesn't support passing a custom checker.
506
+ encoding = self.config.getini("ipdoctest_encoding")
507
+ text = self.path.read_text(encoding)
508
+ filename = str(self.path)
509
+ name = self.path.name
510
+ globs = {"__name__": "__main__"}
511
+
512
+ optionflags = get_optionflags(self)
513
+
514
+ runner = _get_runner(
515
+ verbose=False,
516
+ optionflags=optionflags,
517
+ checker=_get_checker(),
518
+ continue_on_failure=_get_continue_on_failure(self.config),
519
+ )
520
+
521
+ parser = IPDocTestParser()
522
+ test = parser.get_doctest(text, globs, name, filename, 0)
523
+ if test.examples:
524
+ yield IPDoctestItem.from_parent(
525
+ self, name=test.name, runner=runner, dtest=test
526
+ )
527
+
528
+ if pytest_version[0] < 7:
529
+
530
+ @property
531
+ def path(self) -> Path:
532
+ return Path(self.fspath)
533
+
534
+ @classmethod
535
+ def from_parent(
536
+ cls,
537
+ parent,
538
+ *,
539
+ fspath=None,
540
+ path: Optional[Path] = None,
541
+ **kw,
542
+ ):
543
+ if path is not None:
544
+ import py.path
545
+
546
+ fspath = py.path.local(path)
547
+ return super().from_parent(parent=parent, fspath=fspath, **kw)
548
+
549
+
550
+ def _check_all_skipped(test: "doctest.DocTest") -> None:
551
+ """Raise pytest.skip() if all examples in the given DocTest have the SKIP
552
+ option set."""
553
+ import doctest
554
+
555
+ all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
556
+ if all_skipped:
557
+ pytest.skip("all docstests skipped by +SKIP option")
558
+
559
+
560
+ def _is_mocked(obj: object) -> bool:
561
+ """Return if an object is possibly a mock object by checking the
562
+ existence of a highly improbable attribute."""
563
+ return (
564
+ safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None)
565
+ is not None
566
+ )
567
+
568
+
569
+ @contextmanager
570
+ def _patch_unwrap_mock_aware() -> Generator[None, None, None]:
571
+ """Context manager which replaces ``inspect.unwrap`` with a version
572
+ that's aware of mock objects and doesn't recurse into them."""
573
+ real_unwrap = inspect.unwrap
574
+
575
+ def _mock_aware_unwrap(
576
+ func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None
577
+ ) -> Any:
578
+ try:
579
+ if stop is None or stop is _is_mocked:
580
+ return real_unwrap(func, stop=_is_mocked)
581
+ _stop = stop
582
+ return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func))
583
+ except Exception as e:
584
+ warnings.warn(
585
+ "Got %r when unwrapping %r. This is usually caused "
586
+ "by a violation of Python's object protocol; see e.g. "
587
+ "https://github.com/pytest-dev/pytest/issues/5080" % (e, func),
588
+ PytestWarning,
589
+ )
590
+ raise
591
+
592
+ inspect.unwrap = _mock_aware_unwrap
593
+ try:
594
+ yield
595
+ finally:
596
+ inspect.unwrap = real_unwrap
597
+
598
+
599
+ class IPDoctestModule(pytest.Module):
600
+ def collect(self) -> Iterable[IPDoctestItem]:
601
+ import doctest
602
+ from .ipdoctest import DocTestFinder, IPDocTestParser
603
+
604
+ class MockAwareDocTestFinder(DocTestFinder):
605
+ """A hackish ipdoctest finder that overrides stdlib internals to fix a stdlib bug.
606
+
607
+ https://github.com/pytest-dev/pytest/issues/3456
608
+ https://bugs.python.org/issue25532
609
+ """
610
+
611
+ def _find_lineno(self, obj, source_lines):
612
+ """Doctest code does not take into account `@property`, this
613
+ is a hackish way to fix it. https://bugs.python.org/issue17446
614
+
615
+ Wrapped Doctests will need to be unwrapped so the correct
616
+ line number is returned. This will be reported upstream. #8796
617
+ """
618
+ if isinstance(obj, property):
619
+ obj = getattr(obj, "fget", obj)
620
+
621
+ if hasattr(obj, "__wrapped__"):
622
+ # Get the main obj in case of it being wrapped
623
+ obj = inspect.unwrap(obj)
624
+
625
+ # Type ignored because this is a private function.
626
+ return super()._find_lineno( # type:ignore[misc]
627
+ obj,
628
+ source_lines,
629
+ )
630
+
631
+ def _find(
632
+ self, tests, obj, name, module, source_lines, globs, seen
633
+ ) -> None:
634
+ if _is_mocked(obj):
635
+ return
636
+ with _patch_unwrap_mock_aware():
637
+ # Type ignored because this is a private function.
638
+ super()._find( # type:ignore[misc]
639
+ tests, obj, name, module, source_lines, globs, seen
640
+ )
641
+
642
+ if self.path.name == "conftest.py":
643
+ if pytest_version[0] < 7:
644
+ module = self.config.pluginmanager._importconftest(
645
+ self.path,
646
+ self.config.getoption("importmode"),
647
+ )
648
+ else:
649
+ kwargs = {"rootpath": self.config.rootpath}
650
+ if pytest_version >= (8, 1):
651
+ kwargs["consider_namespace_packages"] = False
652
+ module = self.config.pluginmanager._importconftest(
653
+ self.path,
654
+ self.config.getoption("importmode"),
655
+ **kwargs,
656
+ )
657
+ else:
658
+ try:
659
+ kwargs = {"root": self.config.rootpath}
660
+ if pytest_version >= (8, 1):
661
+ kwargs["consider_namespace_packages"] = False
662
+ module = import_path(self.path, **kwargs)
663
+ except ImportError:
664
+ if self.config.getvalue("ipdoctest_ignore_import_errors"):
665
+ pytest.skip("unable to import module %r" % self.path)
666
+ else:
667
+ raise
668
+ # Uses internal doctest module parsing mechanism.
669
+ finder = MockAwareDocTestFinder(parser=IPDocTestParser())
670
+ optionflags = get_optionflags(self)
671
+ runner = _get_runner(
672
+ verbose=False,
673
+ optionflags=optionflags,
674
+ checker=_get_checker(),
675
+ continue_on_failure=_get_continue_on_failure(self.config),
676
+ )
677
+
678
+ for test in finder.find(module, module.__name__):
679
+ if test.examples: # skip empty ipdoctests
680
+ yield IPDoctestItem.from_parent(
681
+ self, name=test.name, runner=runner, dtest=test
682
+ )
683
+
684
+ if pytest_version[0] < 7:
685
+
686
+ @property
687
+ def path(self) -> Path:
688
+ return Path(self.fspath)
689
+
690
+ @classmethod
691
+ def from_parent(
692
+ cls,
693
+ parent,
694
+ *,
695
+ fspath=None,
696
+ path: Optional[Path] = None,
697
+ **kw,
698
+ ):
699
+ if path is not None:
700
+ import py.path
701
+
702
+ fspath = py.path.local(path)
703
+ return super().from_parent(parent=parent, fspath=fspath, **kw)
704
+
705
+
706
+ def _setup_fixtures(doctest_item: IPDoctestItem) -> FixtureRequest:
707
+ """Used by IPDoctestTextfile and IPDoctestItem to setup fixture information."""
708
+
709
+ def func() -> None:
710
+ pass
711
+
712
+ doctest_item.funcargs = {} # type: ignore[attr-defined]
713
+ fm = doctest_item.session._fixturemanager
714
+ kwargs = {"node": doctest_item, "func": func, "cls": None}
715
+ if pytest_version <= (8, 0):
716
+ kwargs["funcargs"] = False
717
+ doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined]
718
+ **kwargs
719
+ )
720
+ fixture_request = FixtureRequest(doctest_item, _ispytest=True)
721
+ if pytest_version <= (8, 0):
722
+ fixture_request._fillfixtures()
723
+ return fixture_request
724
+
725
+
726
+ def _init_checker_class() -> Type["IPDoctestOutputChecker"]:
727
+ import doctest
728
+ import re
729
+ from .ipdoctest import IPDoctestOutputChecker
730
+
731
+ class LiteralsOutputChecker(IPDoctestOutputChecker):
732
+ # Based on doctest_nose_plugin.py from the nltk project
733
+ # (https://github.com/nltk/nltk) and on the "numtest" doctest extension
734
+ # by Sebastien Boisgerault (https://github.com/boisgera/numtest).
735
+
736
+ _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
737
+ _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE)
738
+ _number_re = re.compile(
739
+ r"""
740
+ (?P<number>
741
+ (?P<mantissa>
742
+ (?P<integer1> [+-]?\d*)\.(?P<fraction>\d+)
743
+ |
744
+ (?P<integer2> [+-]?\d+)\.
745
+ )
746
+ (?:
747
+ [Ee]
748
+ (?P<exponent1> [+-]?\d+)
749
+ )?
750
+ |
751
+ (?P<integer3> [+-]?\d+)
752
+ (?:
753
+ [Ee]
754
+ (?P<exponent2> [+-]?\d+)
755
+ )
756
+ )
757
+ """,
758
+ re.VERBOSE,
759
+ )
760
+
761
+ def check_output(self, want: str, got: str, optionflags: int) -> bool:
762
+ if super().check_output(want, got, optionflags):
763
+ return True
764
+
765
+ allow_unicode = optionflags & _get_allow_unicode_flag()
766
+ allow_bytes = optionflags & _get_allow_bytes_flag()
767
+ allow_number = optionflags & _get_number_flag()
768
+
769
+ if not allow_unicode and not allow_bytes and not allow_number:
770
+ return False
771
+
772
+ def remove_prefixes(regex: Pattern[str], txt: str) -> str:
773
+ return re.sub(regex, r"\1\2", txt)
774
+
775
+ if allow_unicode:
776
+ want = remove_prefixes(self._unicode_literal_re, want)
777
+ got = remove_prefixes(self._unicode_literal_re, got)
778
+
779
+ if allow_bytes:
780
+ want = remove_prefixes(self._bytes_literal_re, want)
781
+ got = remove_prefixes(self._bytes_literal_re, got)
782
+
783
+ if allow_number:
784
+ got = self._remove_unwanted_precision(want, got)
785
+
786
+ return super().check_output(want, got, optionflags)
787
+
788
+ def _remove_unwanted_precision(self, want: str, got: str) -> str:
789
+ wants = list(self._number_re.finditer(want))
790
+ gots = list(self._number_re.finditer(got))
791
+ if len(wants) != len(gots):
792
+ return got
793
+ offset = 0
794
+ for w, g in zip(wants, gots):
795
+ fraction: Optional[str] = w.group("fraction")
796
+ exponent: Optional[str] = w.group("exponent1")
797
+ if exponent is None:
798
+ exponent = w.group("exponent2")
799
+ precision = 0 if fraction is None else len(fraction)
800
+ if exponent is not None:
801
+ precision -= int(exponent)
802
+ if float(w.group()) == approx(float(g.group()), abs=10**-precision):
803
+ # They're close enough. Replace the text we actually
804
+ # got with the text we want, so that it will match when we
805
+ # check the string literally.
806
+ got = (
807
+ got[: g.start() + offset] + w.group() + got[g.end() + offset :]
808
+ )
809
+ offset += w.end() - w.start() - (g.end() - g.start())
810
+ return got
811
+
812
+ return LiteralsOutputChecker
813
+
814
+
815
+ def _get_checker() -> "IPDoctestOutputChecker":
816
+ """Return a IPDoctestOutputChecker subclass that supports some
817
+ additional options:
818
+
819
+ * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b''
820
+ prefixes (respectively) in string literals. Useful when the same
821
+ ipdoctest should run in Python 2 and Python 3.
822
+
823
+ * NUMBER to ignore floating-point differences smaller than the
824
+ precision of the literal number in the ipdoctest.
825
+
826
+ An inner class is used to avoid importing "ipdoctest" at the module
827
+ level.
828
+ """
829
+ global CHECKER_CLASS
830
+ if CHECKER_CLASS is None:
831
+ CHECKER_CLASS = _init_checker_class()
832
+ return CHECKER_CLASS()
833
+
834
+
835
+ def _get_allow_unicode_flag() -> int:
836
+ """Register and return the ALLOW_UNICODE flag."""
837
+ import doctest
838
+
839
+ return doctest.register_optionflag("ALLOW_UNICODE")
840
+
841
+
842
+ def _get_allow_bytes_flag() -> int:
843
+ """Register and return the ALLOW_BYTES flag."""
844
+ import doctest
845
+
846
+ return doctest.register_optionflag("ALLOW_BYTES")
847
+
848
+
849
+ def _get_number_flag() -> int:
850
+ """Register and return the NUMBER flag."""
851
+ import doctest
852
+
853
+ return doctest.register_optionflag("NUMBER")
854
+
855
+
856
+ def _get_report_choice(key: str) -> int:
857
+ """Return the actual `ipdoctest` module flag value.
858
+
859
+ We want to do it as late as possible to avoid importing `ipdoctest` and all
860
+ its dependencies when parsing options, as it adds overhead and breaks tests.
861
+ """
862
+ import doctest
863
+
864
+ return {
865
+ DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF,
866
+ DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF,
867
+ DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF,
868
+ DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE,
869
+ DOCTEST_REPORT_CHOICE_NONE: 0,
870
+ }[key]
871
+
872
+
873
+ @pytest.fixture(scope="session")
874
+ def ipdoctest_namespace() -> Dict[str, Any]:
875
+ """Fixture that returns a :py:class:`dict` that will be injected into the
876
+ namespace of ipdoctests."""
877
+ return dict()
lib/python3.12/site-packages/IPython/testing/plugin/setup.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """A Nose plugin to support IPython doctests.
3
+ """
4
+
5
+ from setuptools import setup
6
+
7
+ setup(name='IPython doctest plugin',
8
+ version='0.1',
9
+ author='The IPython Team',
10
+ description = 'Nose plugin to load IPython-extended doctests',
11
+ license = 'LGPL',
12
+ py_modules = ['ipdoctest'],
13
+ entry_points = {
14
+ 'nose.plugins.0.10': ['ipdoctest = ipdoctest:IPythonDoctest',
15
+ 'extdoctest = ipdoctest:ExtensionDoctest',
16
+ ],
17
+ },
18
+ )
lib/python3.12/site-packages/IPython/testing/plugin/simple.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Simple example using doctests.
2
+
3
+ This file just contains doctests both using plain python and IPython prompts.
4
+ All tests should be loaded by Pytest.
5
+ """
6
+
7
+
8
+ def pyfunc():
9
+ """Some pure python tests...
10
+
11
+ >>> pyfunc()
12
+ 'pyfunc'
13
+
14
+ >>> import os
15
+
16
+ >>> 2+3
17
+ 5
18
+
19
+ >>> for i in range(3):
20
+ ... print(i, end=' ')
21
+ ... print(i+1, end=' ')
22
+ ...
23
+ 0 1 1 2 2 3
24
+ """
25
+ return "pyfunc"
26
+
27
+
28
+ def ipyfunc():
29
+ """Some IPython tests...
30
+
31
+ In [1]: ipyfunc()
32
+ Out[1]: 'ipyfunc'
33
+
34
+ In [2]: import os
35
+
36
+ In [3]: 2+3
37
+ Out[3]: 5
38
+
39
+ In [4]: for i in range(3):
40
+ ...: print(i, end=' ')
41
+ ...: print(i+1, end=' ')
42
+ ...:
43
+ Out[4]: 0 1 1 2 2 3
44
+ """
45
+ return "ipyfunc"
lib/python3.12/site-packages/IPython/testing/plugin/test_combo.txt ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ =======================
2
+ Combo testing example
3
+ =======================
4
+
5
+ This is a simple example that mixes ipython doctests::
6
+
7
+ In [1]: import code
8
+
9
+ In [2]: 2**12
10
+ Out[2]: 4096
11
+
12
+ with command-line example information that does *not* get executed::
13
+
14
+ $ mpirun -n 4 ipengine --controller-port=10000 --controller-ip=host0
15
+
16
+ and with literal examples of Python source code::
17
+
18
+ controller = dict(host='myhost',
19
+ engine_port=None, # default is 10105
20
+ control_port=None,
21
+ )
22
+
23
+ # keys are hostnames, values are the number of engine on that host
24
+ engines = dict(node1=2,
25
+ node2=2,
26
+ node3=2,
27
+ node3=2,
28
+ )
29
+
30
+ # Force failure to detect that this test is being run.
31
+ 1/0
32
+
33
+ These source code examples are executed but no output is compared at all. An
34
+ error or failure is reported only if an exception is raised.
35
+
36
+ NOTE: the execution of pure python blocks is not yet working!
lib/python3.12/site-packages/IPython/testing/plugin/test_exampleip.txt ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ =================================
2
+ Tests in example form - IPython
3
+ =================================
4
+
5
+ You can write text files with examples that use IPython prompts (as long as you
6
+ use the nose ipython doctest plugin), but you can not mix and match prompt
7
+ styles in a single file. That is, you either use all ``>>>`` prompts or all
8
+ IPython-style prompts. Your test suite *can* have both types, you just need to
9
+ put each type of example in a separate. Using IPython prompts, you can paste
10
+ directly from your session::
11
+
12
+ In [5]: s="Hello World"
13
+
14
+ In [6]: s.upper()
15
+ Out[6]: 'HELLO WORLD'
16
+
17
+ Another example::
18
+
19
+ In [8]: 1+3
20
+ Out[8]: 4
21
+
22
+ Just like in IPython docstrings, you can use all IPython syntax and features::
23
+
24
+ In [9]: !echo hello
25
+ hello
26
+
27
+ In [10]: a='hi'
28
+
29
+ In [11]: !echo $a
30
+ hi
lib/python3.12/site-packages/IPython/testing/plugin/test_ipdoctest.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for the ipdoctest machinery itself.
2
+
3
+ Note: in a file named test_X, functions whose only test is their docstring (as
4
+ a doctest) and which have no test functionality of their own, should be called
5
+ 'doctest_foo' instead of 'test_foo', otherwise they get double-counted (the
6
+ empty function call is counted as a test, which just inflates tests numbers
7
+ artificially).
8
+ """
9
+
10
+ def doctest_simple():
11
+ """ipdoctest must handle simple inputs
12
+
13
+ In [1]: 1
14
+ Out[1]: 1
15
+
16
+ In [2]: print(1)
17
+ 1
18
+ """
19
+
20
+ def doctest_multiline1():
21
+ """The ipdoctest machinery must handle multiline examples gracefully.
22
+
23
+ In [2]: for i in range(4):
24
+ ...: print(i)
25
+ ...:
26
+ 0
27
+ 1
28
+ 2
29
+ 3
30
+ """
31
+
32
+ def doctest_multiline2():
33
+ """Multiline examples that define functions and print output.
34
+
35
+ In [7]: def f(x):
36
+ ...: return x+1
37
+ ...:
38
+
39
+ In [8]: f(1)
40
+ Out[8]: 2
41
+
42
+ In [9]: def g(x):
43
+ ...: print('x is:',x)
44
+ ...:
45
+
46
+ In [10]: g(1)
47
+ x is: 1
48
+
49
+ In [11]: g('hello')
50
+ x is: hello
51
+ """
52
+
53
+
54
+ def doctest_multiline3():
55
+ """Multiline examples with blank lines.
56
+
57
+ In [12]: def h(x):
58
+ ....: if x>1:
59
+ ....: return x**2
60
+ ....: # To leave a blank line in the input, you must mark it
61
+ ....: # with a comment character:
62
+ ....: #
63
+ ....: # otherwise the doctest parser gets confused.
64
+ ....: else:
65
+ ....: return -1
66
+ ....:
67
+
68
+ In [13]: h(5)
69
+ Out[13]: 25
70
+
71
+ In [14]: h(1)
72
+ Out[14]: -1
73
+
74
+ In [15]: h(0)
75
+ Out[15]: -1
76
+ """
77
+
78
+
79
+ def doctest_builtin_underscore():
80
+ """Defining builtins._ should not break anything outside the doctest
81
+ while also should be working as expected inside the doctest.
82
+
83
+ In [1]: import builtins
84
+
85
+ In [2]: builtins._ = 42
86
+
87
+ In [3]: builtins._
88
+ Out[3]: 42
89
+
90
+ In [4]: _
91
+ Out[4]: 42
92
+ """
lib/python3.12/site-packages/IPython/testing/plugin/test_refs.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Some simple tests for the plugin while running scripts.
2
+ """
3
+ # Module imports
4
+ # Std lib
5
+ import inspect
6
+
7
+ # Our own
8
+
9
+ #-----------------------------------------------------------------------------
10
+ # Testing functions
11
+
12
+ def test_trivial():
13
+ """A trivial passing test."""
14
+ pass
15
+
16
+ def doctest_run():
17
+ """Test running a trivial script.
18
+
19
+ In [13]: run simplevars.py
20
+ x is: 1
21
+ """
22
+
23
+ def doctest_runvars():
24
+ """Test that variables defined in scripts get loaded correctly via %run.
25
+
26
+ In [13]: run simplevars.py
27
+ x is: 1
28
+
29
+ In [14]: x
30
+ Out[14]: 1
31
+ """
32
+
33
+ def doctest_ivars():
34
+ """Test that variables defined interactively are picked up.
35
+ In [5]: zz=1
36
+
37
+ In [6]: zz
38
+ Out[6]: 1
39
+ """
lib/python3.12/site-packages/IPython/testing/skipdoctest.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Decorators marks that a doctest should be skipped.
2
+
3
+ The IPython.testing.decorators module triggers various extra imports, including
4
+ numpy and sympy if they're present. Since this decorator is used in core parts
5
+ of IPython, it's in a separate module so that running IPython doesn't trigger
6
+ those imports."""
7
+
8
+ # Copyright (C) IPython Development Team
9
+ # Distributed under the terms of the Modified BSD License.
10
+
11
+
12
+ def skip_doctest(f):
13
+ """Decorator - mark a function or method for skipping its doctest.
14
+
15
+ This decorator allows you to mark a function whose docstring you wish to
16
+ omit from testing, while preserving the docstring for introspection, help,
17
+ etc."""
18
+ f.__skip_doctest__ = True
19
+ return f
lib/python3.12/site-packages/IPython/testing/tools.py ADDED
@@ -0,0 +1,434 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generic testing tools.
2
+
3
+ Authors
4
+ -------
5
+ - Fernando Perez <Fernando.Perez@berkeley.edu>
6
+ """
7
+
8
+
9
+ # Copyright (c) IPython Development Team.
10
+ # Distributed under the terms of the Modified BSD License.
11
+
12
+ import os
13
+ from pathlib import Path
14
+ import re
15
+ import sys
16
+ import tempfile
17
+ import unittest
18
+
19
+ from contextlib import contextmanager
20
+ from io import StringIO
21
+ from subprocess import Popen, PIPE
22
+ from unittest.mock import patch
23
+
24
+ from traitlets.config.loader import Config
25
+ from IPython.utils.process import get_output_error_code
26
+ from IPython.utils.text import list_strings
27
+ from IPython.utils.io import temp_pyfile, Tee
28
+ from IPython.utils import py3compat
29
+
30
+ from . import decorators as dec
31
+ from . import skipdoctest
32
+
33
+
34
+ # The docstring for full_path doctests differently on win32 (different path
35
+ # separator) so just skip the doctest there. The example remains informative.
36
+ doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
37
+
38
+ @doctest_deco
39
+ def full_path(startPath: str, files: list[str]) -> list[str]:
40
+ """Make full paths for all the listed files, based on startPath.
41
+
42
+ Only the base part of startPath is kept, since this routine is typically
43
+ used with a script's ``__file__`` variable as startPath. The base of startPath
44
+ is then prepended to all the listed files, forming the output list.
45
+
46
+ Parameters
47
+ ----------
48
+ startPath : string
49
+ Initial path to use as the base for the results. This path is split
50
+ using os.path.split() and only its first component is kept.
51
+
52
+ files : list
53
+ One or more files.
54
+
55
+ Examples
56
+ --------
57
+
58
+ >>> full_path('/foo/bar.py',['a.txt','b.txt'])
59
+ ['/foo/a.txt', '/foo/b.txt']
60
+
61
+ >>> full_path('/foo',['a.txt','b.txt'])
62
+ ['/a.txt', '/b.txt']
63
+
64
+ """
65
+ assert isinstance(files, list)
66
+ base = os.path.split(startPath)[0]
67
+ return [ os.path.join(base,f) for f in files ]
68
+
69
+
70
+ def parse_test_output(txt):
71
+ """Parse the output of a test run and return errors, failures.
72
+
73
+ Parameters
74
+ ----------
75
+ txt : str
76
+ Text output of a test run, assumed to contain a line of one of the
77
+ following forms::
78
+
79
+ 'FAILED (errors=1)'
80
+ 'FAILED (failures=1)'
81
+ 'FAILED (errors=1, failures=1)'
82
+
83
+ Returns
84
+ -------
85
+ nerr, nfail
86
+ number of errors and failures.
87
+ """
88
+
89
+ err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
90
+ if err_m:
91
+ nerr = int(err_m.group(1))
92
+ nfail = 0
93
+ return nerr, nfail
94
+
95
+ fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
96
+ if fail_m:
97
+ nerr = 0
98
+ nfail = int(fail_m.group(1))
99
+ return nerr, nfail
100
+
101
+ both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
102
+ re.MULTILINE)
103
+ if both_m:
104
+ nerr = int(both_m.group(1))
105
+ nfail = int(both_m.group(2))
106
+ return nerr, nfail
107
+
108
+ # If the input didn't match any of these forms, assume no error/failures
109
+ return 0, 0
110
+
111
+
112
+ # So nose doesn't think this is a test
113
+ parse_test_output.__test__ = False
114
+
115
+
116
+ def default_argv():
117
+ """Return a valid default argv for creating testing instances of ipython"""
118
+
119
+ return [
120
+ "--quick", # so no config file is loaded
121
+ # Other defaults to minimize side effects on stdout
122
+ "--colors=nocolor",
123
+ "--no-term-title",
124
+ "--no-banner",
125
+ "--autocall=0",
126
+ ]
127
+
128
+
129
+ def default_config():
130
+ """Return a config object with good defaults for testing."""
131
+ config = Config()
132
+ config.TerminalInteractiveShell.colors = "nocolor"
133
+ config.TerminalTerminalInteractiveShell.term_title = (False,)
134
+ config.TerminalInteractiveShell.autocall = 0
135
+ f = tempfile.NamedTemporaryFile(suffix="test_hist.sqlite", delete=False)
136
+ config.HistoryManager.hist_file = Path(f.name)
137
+ f.close()
138
+ config.HistoryManager.db_cache_size = 10000
139
+ return config
140
+
141
+
142
+ def get_ipython_cmd(as_string=False):
143
+ """
144
+ Return appropriate IPython command line name. By default, this will return
145
+ a list that can be used with subprocess.Popen, for example, but passing
146
+ `as_string=True` allows for returning the IPython command as a string.
147
+
148
+ Parameters
149
+ ----------
150
+ as_string: bool
151
+ Flag to allow to return the command as a string.
152
+ """
153
+ ipython_cmd = [sys.executable, "-m", "IPython"]
154
+
155
+ if as_string:
156
+ ipython_cmd = " ".join(ipython_cmd)
157
+
158
+ return ipython_cmd
159
+
160
+ def ipexec(fname, options=None, commands=()):
161
+ """Utility to call 'ipython filename'.
162
+
163
+ Starts IPython with a minimal and safe configuration to make startup as fast
164
+ as possible.
165
+
166
+ Note that this starts IPython in a subprocess!
167
+
168
+ Parameters
169
+ ----------
170
+ fname : str, Path
171
+ Name of file to be executed (should have .py or .ipy extension).
172
+
173
+ options : optional, list
174
+ Extra command-line flags to be passed to IPython.
175
+
176
+ commands : optional, list
177
+ Commands to send in on stdin
178
+
179
+ Returns
180
+ -------
181
+ ``(stdout, stderr)`` of ipython subprocess.
182
+ """
183
+ __tracebackhide__ = True
184
+
185
+ if options is None:
186
+ options = []
187
+
188
+ cmdargs = default_argv() + options
189
+
190
+ test_dir = os.path.dirname(__file__)
191
+
192
+ ipython_cmd = get_ipython_cmd()
193
+ # Absolute path for filename
194
+ full_fname = os.path.join(test_dir, fname)
195
+ full_cmd = ipython_cmd + cmdargs + ['--', full_fname]
196
+ env = os.environ.copy()
197
+ # FIXME: ignore all warnings in ipexec while we have shims
198
+ # should we keep suppressing warnings here, even after removing shims?
199
+ env['PYTHONWARNINGS'] = 'ignore'
200
+ # env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr
201
+ # Prevent coloring under PyCharm ("\x1b[0m" at the end of the stdout)
202
+ env.pop("PYCHARM_HOSTED", None)
203
+ for k, v in env.items():
204
+ # Debug a bizarre failure we've seen on Windows:
205
+ # TypeError: environment can only contain strings
206
+ if not isinstance(v, str):
207
+ print(k, v)
208
+ p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env)
209
+ out, err = p.communicate(input=py3compat.encode('\n'.join(commands)) or None)
210
+ out, err = py3compat.decode(out), py3compat.decode(err)
211
+ # `import readline` causes 'ESC[?1034h' to be output sometimes,
212
+ # so strip that out before doing comparisons
213
+ if out:
214
+ out = re.sub(r'\x1b\[[^h]+h', '', out)
215
+ return out, err
216
+
217
+
218
+ def ipexec_validate(fname, expected_out, expected_err='',
219
+ options=None, commands=()):
220
+ """Utility to call 'ipython filename' and validate output/error.
221
+
222
+ This function raises an AssertionError if the validation fails.
223
+
224
+ Note that this starts IPython in a subprocess!
225
+
226
+ Parameters
227
+ ----------
228
+ fname : str, Path
229
+ Name of the file to be executed (should have .py or .ipy extension).
230
+
231
+ expected_out : str
232
+ Expected stdout of the process.
233
+
234
+ expected_err : optional, str
235
+ Expected stderr of the process.
236
+
237
+ options : optional, list
238
+ Extra command-line flags to be passed to IPython.
239
+
240
+ Returns
241
+ -------
242
+ None
243
+ """
244
+ __tracebackhide__ = True
245
+
246
+ out, err = ipexec(fname, options, commands)
247
+ # print('OUT', out) # dbg
248
+ # print('ERR', err) # dbg
249
+ # If there are any errors, we must check those before stdout, as they may be
250
+ # more informative than simply having an empty stdout.
251
+ if err:
252
+ if expected_err:
253
+ assert "\n".join(err.strip().splitlines()) == "\n".join(
254
+ expected_err.strip().splitlines()
255
+ )
256
+ else:
257
+ raise ValueError('Running file %r produced error: %r' %
258
+ (fname, err))
259
+ # If no errors or output on stderr was expected, match stdout
260
+ assert "\n".join(out.strip().splitlines()) == "\n".join(
261
+ expected_out.strip().splitlines()
262
+ )
263
+
264
+
265
+ class TempFileMixin(unittest.TestCase):
266
+ """Utility class to create temporary Python/IPython files.
267
+
268
+ Meant as a mixin class for test cases."""
269
+
270
+ def mktmp(self, src, ext='.py'):
271
+ """Make a valid python temp file."""
272
+ fname = temp_pyfile(src, ext)
273
+ if not hasattr(self, 'tmps'):
274
+ self.tmps=[]
275
+ self.tmps.append(fname)
276
+ self.fname = fname
277
+
278
+ def tearDown(self):
279
+ # If the tmpfile wasn't made because of skipped tests, like in
280
+ # win32, there's nothing to cleanup.
281
+ if hasattr(self, 'tmps'):
282
+ for fname in self.tmps:
283
+ # If the tmpfile wasn't made because of skipped tests, like in
284
+ # win32, there's nothing to cleanup.
285
+ try:
286
+ os.unlink(fname)
287
+ except:
288
+ # On Windows, even though we close the file, we still can't
289
+ # delete it. I have no clue why
290
+ pass
291
+
292
+ def __enter__(self):
293
+ return self
294
+
295
+ def __exit__(self, exc_type, exc_value, traceback):
296
+ self.tearDown()
297
+
298
+
299
+ MyStringIO = StringIO
300
+
301
+ _re_type = type(re.compile(r''))
302
+
303
+ notprinted_msg = """Did not find {0!r} in printed output (on {1}):
304
+ -------
305
+ {2!s}
306
+ -------
307
+ """
308
+
309
+ class AssertPrints:
310
+ """Context manager for testing that code prints certain text.
311
+
312
+ Examples
313
+ --------
314
+ >>> with AssertPrints("abc", suppress=False):
315
+ ... print("abcd")
316
+ ... print("def")
317
+ ...
318
+ abcd
319
+ def
320
+ """
321
+ def __init__(self, s, channel='stdout', suppress=True):
322
+ self.s = s
323
+ if isinstance(self.s, (str, _re_type)):
324
+ self.s = [self.s]
325
+ self.channel = channel
326
+ self.suppress = suppress
327
+
328
+ def __enter__(self):
329
+ self.orig_stream = getattr(sys, self.channel)
330
+ self.buffer = MyStringIO()
331
+ self.tee = Tee(self.buffer, channel=self.channel)
332
+ setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
333
+
334
+ def __exit__(self, etype, value, traceback):
335
+ __tracebackhide__ = True
336
+
337
+ try:
338
+ if value is not None:
339
+ # If an error was raised, don't check anything else
340
+ return False
341
+ self.tee.flush()
342
+ setattr(sys, self.channel, self.orig_stream)
343
+ printed = self.buffer.getvalue()
344
+ for s in self.s:
345
+ if isinstance(s, _re_type):
346
+ assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed)
347
+ else:
348
+ assert s in printed, notprinted_msg.format(s, self.channel, printed)
349
+ return False
350
+ finally:
351
+ self.tee.close()
352
+
353
+ printed_msg = """Found {0!r} in printed output (on {1}):
354
+ -------
355
+ {2!s}
356
+ -------
357
+ """
358
+
359
+ class AssertNotPrints(AssertPrints):
360
+ """Context manager for checking that certain output *isn't* produced.
361
+
362
+ Counterpart of AssertPrints"""
363
+ def __exit__(self, etype, value, traceback):
364
+ __tracebackhide__ = True
365
+
366
+ try:
367
+ if value is not None:
368
+ # If an error was raised, don't check anything else
369
+ self.tee.close()
370
+ return False
371
+ self.tee.flush()
372
+ setattr(sys, self.channel, self.orig_stream)
373
+ printed = self.buffer.getvalue()
374
+ for s in self.s:
375
+ if isinstance(s, _re_type):
376
+ assert not s.search(printed),printed_msg.format(
377
+ s.pattern, self.channel, printed)
378
+ else:
379
+ assert s not in printed, printed_msg.format(
380
+ s, self.channel, printed)
381
+ return False
382
+ finally:
383
+ self.tee.close()
384
+
385
+ @contextmanager
386
+ def make_tempfile(name):
387
+ """Create an empty, named, temporary file for the duration of the context."""
388
+ open(name, "w", encoding="utf-8").close()
389
+ try:
390
+ yield
391
+ finally:
392
+ os.unlink(name)
393
+
394
+ def fake_input(inputs):
395
+ """Temporarily replace the input() function to return the given values
396
+
397
+ Use as a context manager:
398
+
399
+ with fake_input(['result1', 'result2']):
400
+ ...
401
+
402
+ Values are returned in order. If input() is called again after the last value
403
+ was used, EOFError is raised.
404
+ """
405
+ it = iter(inputs)
406
+ def mock_input(prompt=''):
407
+ try:
408
+ return next(it)
409
+ except StopIteration as e:
410
+ raise EOFError('No more inputs given') from e
411
+
412
+ return patch('builtins.input', mock_input)
413
+
414
+ def help_output_test(subcommand=''):
415
+ """test that `ipython [subcommand] -h` works"""
416
+ cmd = get_ipython_cmd() + [subcommand, '-h']
417
+ out, err, rc = get_output_error_code(cmd)
418
+ assert rc == 0, err
419
+ assert "Traceback" not in err
420
+ assert "Options" in out
421
+ assert "--help-all" in out
422
+ return out, err
423
+
424
+
425
+ def help_all_output_test(subcommand=''):
426
+ """test that `ipython [subcommand] --help-all` works"""
427
+ cmd = get_ipython_cmd() + [subcommand, '--help-all']
428
+ out, err, rc = get_output_error_code(cmd)
429
+ assert rc == 0, err
430
+ assert "Traceback" not in err
431
+ assert "Options" in out
432
+ assert "Class" in out
433
+ return out, err
434
+
lib/python3.12/site-packages/IPython/utils/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (192 Bytes). View file
 
lib/python3.12/site-packages/IPython/utils/__pycache__/capture.cpython-312.pyc ADDED
Binary file (8.16 kB). View file
 
lib/python3.12/site-packages/IPython/utils/__pycache__/decorators.cpython-312.pyc ADDED
Binary file (2.54 kB). View file
 
lib/python3.12/site-packages/IPython/utils/__pycache__/dir2.cpython-312.pyc ADDED
Binary file (2.56 kB). View file
 
lib/python3.12/site-packages/IPython/utils/__pycache__/encoding.cpython-312.pyc ADDED
Binary file (2.83 kB). View file
 
lib/python3.12/site-packages/IPython/utils/__pycache__/frame.cpython-312.pyc ADDED
Binary file (3.8 kB). View file
 
lib/python3.12/site-packages/IPython/utils/__pycache__/generics.cpython-312.pyc ADDED
Binary file (1.23 kB). View file
 
lib/python3.12/site-packages/IPython/utils/__pycache__/ipstruct.cpython-312.pyc ADDED
Binary file (12.6 kB). View file