##// END OF EJS Templates
Drop Python 3.9...
Matthias Bussonnier -
Show More
@@ -1,52 +1,52 b''
1 1 name: Run Downstream tests
2 2
3 3 on:
4 4 push:
5 5 pull_request:
6 6 # Run weekly on Monday at 1:23 UTC
7 7 schedule:
8 8 - cron: '23 1 * * 1'
9 9 workflow_dispatch:
10 10
11 11 permissions:
12 12 contents: read
13 13
14 14 jobs:
15 15 test:
16 16 runs-on: ${{ matrix.os }}
17 17 strategy:
18 18 matrix:
19 19 os: [ubuntu-latest]
20 python-version: ["3.9"]
20 python-version: ["3.10"]
21 21 include:
22 22 - os: macos-latest
23 python-version: "3.9"
23 python-version: "3.10"
24 24
25 25 steps:
26 26 - uses: actions/checkout@v3
27 27 - name: Set up Python ${{ matrix.python-version }}
28 28 uses: actions/setup-python@v4
29 29 with:
30 30 python-version: ${{ matrix.python-version }}
31 31 - name: Update Python installer
32 32 run: |
33 33 python -m pip install --upgrade pip setuptools wheel
34 34 - name: Install ipykernel
35 35 run: |
36 36 cd ..
37 37 git clone https://github.com/ipython/ipykernel
38 38 cd ipykernel
39 39 pip install -e .[test]
40 40 cd ..
41 41 - name: Install and update Python dependencies
42 42 run: |
43 43 python -m pip install --upgrade -e file://$PWD#egg=ipython[test]
44 44 # we must install IPython after ipykernel to get the right versions.
45 45 python -m pip install --upgrade --upgrade-strategy eager flaky ipyparallel
46 46 python -m pip install --upgrade 'pytest<7' 'pytest_asyncio<0.21'
47 47 - name: pytest
48 48 env:
49 49 COLUMNS: 120
50 50 run: |
51 51 cd ../ipykernel
52 52 pytest
@@ -1,98 +1,98 b''
1 1 name: Run tests
2 2
3 3 on:
4 4 push:
5 5 branches:
6 6 - main
7 7 - '*.x'
8 8 pull_request:
9 9 # Run weekly on Monday at 1:23 UTC
10 10 schedule:
11 11 - cron: '23 1 * * 1'
12 12 workflow_dispatch:
13 13
14 14
15 15 jobs:
16 16 test:
17 17 runs-on: ${{ matrix.os }}
18 18 strategy:
19 19 fail-fast: false
20 20 matrix:
21 21 os: [ubuntu-latest, windows-latest]
22 python-version: ["3.9", "3.10", "3.11"]
22 python-version: ["3.10", "3.11", "3.12"]
23 23 deps: [test_extra]
24 24 # Test all on ubuntu, test ends on macos
25 25 include:
26 26 - os: macos-latest
27 python-version: "3.9"
27 python-version: "3.10"
28 28 deps: test_extra
29 29 - os: macos-latest
30 30 python-version: "3.11"
31 31 deps: test_extra
32 32 # Tests minimal dependencies set
33 33 - os: ubuntu-latest
34 34 python-version: "3.11"
35 35 deps: test
36 36 # Tests latest development Python version
37 37 - os: ubuntu-latest
38 python-version: "3.12-dev"
38 python-version: "3.13-dev"
39 39 deps: test
40 40 # Installing optional dependencies stuff takes ages on PyPy
41 41 - os: ubuntu-latest
42 python-version: "pypy-3.9"
42 python-version: "pypy-3.10"
43 43 deps: test
44 44 - os: windows-latest
45 python-version: "pypy-3.9"
45 python-version: "pypy-3.10"
46 46 deps: test
47 47 - os: macos-latest
48 python-version: "pypy-3.9"
48 python-version: "pypy-3.10"
49 49 deps: test
50 50
51 51 steps:
52 52 - uses: actions/checkout@v3
53 53 - name: Set up Python ${{ matrix.python-version }}
54 54 uses: actions/setup-python@v4
55 55 with:
56 56 python-version: ${{ matrix.python-version }}
57 57 cache: pip
58 58 cache-dependency-path: |
59 59 setup.cfg
60 60 - name: Install latex
61 61 if: runner.os == 'Linux' && matrix.deps == 'test_extra'
62 62 run: echo "disable latex for now, issues in mirros" #sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng
63 63 - name: Install and update Python dependencies (binary only)
64 64 if: ${{ ! contains( matrix.python-version, 'dev' ) }}
65 65 run: |
66 66 python -m pip install --only-binary ':all:' --upgrade pip setuptools wheel build
67 67 python -m pip install --only-binary ':all:' --no-binary curio --upgrade -e .[${{ matrix.deps }}]
68 68 python -m pip install --only-binary ':all:' --upgrade check-manifest pytest-cov pytest-json-report
69 69 - name: Install and update Python dependencies (dev?)
70 70 if: ${{ contains( matrix.python-version, 'dev' ) }}
71 71 run: |
72 72 python -m pip install --pre --upgrade pip setuptools wheel build
73 73 python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --no-binary curio --upgrade -e .[${{ matrix.deps }}]
74 74 python -m pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple --upgrade check-manifest pytest-cov pytest-json-report
75 75 - name: Try building with Python build
76 76 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
77 77 run: |
78 78 python -m build
79 79 shasum -a 256 dist/*
80 80 - name: Check manifest
81 81 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
82 82 run: check-manifest
83 83 - name: pytest
84 84 env:
85 85 COLUMNS: 120
86 86 run: |
87 87 pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }} --json-report --json-report-file=./report-${{ matrix.python-version }}-${{runner.os}}.json
88 88 - uses: actions/upload-artifact@v3
89 89 with:
90 90 name: upload pytest timing reports as json
91 91 path: |
92 92 ./report-*.json
93 93
94 94 - name: Upload coverage to Codecov
95 95 uses: codecov/codecov-action@v3
96 96 with:
97 97 name: Test
98 98 files: /home/runner/work/ipython/ipython/coverage.xml
@@ -1,162 +1,163 b''
1 1 # PYTHON_ARGCOMPLETE_OK
2 2 """
3 3 IPython: tools for interactive and parallel computing in Python.
4 4
5 5 https://ipython.org
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (c) 2008-2011, IPython Development Team.
9 9 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
10 10 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
11 11 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
12 12 #
13 13 # Distributed under the terms of the Modified BSD License.
14 14 #
15 15 # The full license is in the file COPYING.txt, distributed with this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import sys
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Setup everything
26 26 #-----------------------------------------------------------------------------
27 27
28 28 # Don't forget to also update setup.py when this changes!
29 if sys.version_info < (3, 9):
29 if sys.version_info < (3, 10):
30 30 raise ImportError(
31 31 """
32 IPython 8.19+ supports Python 3.10 and above, following SPEC0.
32 33 IPython 8.13+ supports Python 3.9 and above, following NEP 29.
33 34 IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29.
34 35 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
35 36 Python 3.3 and 3.4 were supported up to IPython 6.x.
36 37 Python 3.5 was supported with IPython 7.0 to 7.9.
37 38 Python 3.6 was supported with IPython up to 7.16.
38 39 Python 3.7 was still supported with the 7.x branch.
39 40
40 41 See IPython `README.rst` file for more information:
41 42
42 43 https://github.com/ipython/ipython/blob/main/README.rst
43 44
44 45 """
45 46 )
46 47
47 48 #-----------------------------------------------------------------------------
48 49 # Setup the top level names
49 50 #-----------------------------------------------------------------------------
50 51
51 52 from .core.getipython import get_ipython
52 53 from .core import release
53 54 from .core.application import Application
54 55 from .terminal.embed import embed
55 56
56 57 from .core.interactiveshell import InteractiveShell
57 58 from .utils.sysinfo import sys_info
58 59 from .utils.frame import extract_module_locals
59 60
60 61 __all__ = ["start_ipython", "embed", "start_kernel", "embed_kernel"]
61 62
62 63 # Release data
63 64 __author__ = '%s <%s>' % (release.author, release.author_email)
64 65 __license__ = release.license
65 66 __version__ = release.version
66 67 version_info = release.version_info
67 68 # list of CVEs that should have been patched in this release.
68 69 # this is informational and should not be relied upon.
69 70 __patched_cves__ = {"CVE-2022-21699", "CVE-2023-24816"}
70 71
71 72
72 73 def embed_kernel(module=None, local_ns=None, **kwargs):
73 74 """Embed and start an IPython kernel in a given scope.
74 75
75 76 If you don't want the kernel to initialize the namespace
76 77 from the scope of the surrounding function,
77 78 and/or you want to load full IPython configuration,
78 79 you probably want `IPython.start_kernel()` instead.
79 80
80 81 Parameters
81 82 ----------
82 83 module : types.ModuleType, optional
83 84 The module to load into IPython globals (default: caller)
84 85 local_ns : dict, optional
85 86 The namespace to load into IPython user namespace (default: caller)
86 87 **kwargs : various, optional
87 88 Further keyword args are relayed to the IPKernelApp constructor,
88 89 such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
89 90 allowing configuration of the kernel (see :ref:`kernel_options`). Will only have an effect
90 91 on the first embed_kernel call for a given process.
91 92 """
92 93
93 94 (caller_module, caller_locals) = extract_module_locals(1)
94 95 if module is None:
95 96 module = caller_module
96 97 if local_ns is None:
97 98 local_ns = caller_locals
98 99
99 100 # Only import .zmq when we really need it
100 101 from ipykernel.embed import embed_kernel as real_embed_kernel
101 102 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
102 103
103 104 def start_ipython(argv=None, **kwargs):
104 105 """Launch a normal IPython instance (as opposed to embedded)
105 106
106 107 `IPython.embed()` puts a shell in a particular calling scope,
107 108 such as a function or method for debugging purposes,
108 109 which is often not desirable.
109 110
110 111 `start_ipython()` does full, regular IPython initialization,
111 112 including loading startup files, configuration, etc.
112 113 much of which is skipped by `embed()`.
113 114
114 115 This is a public API method, and will survive implementation changes.
115 116
116 117 Parameters
117 118 ----------
118 119 argv : list or None, optional
119 120 If unspecified or None, IPython will parse command-line options from sys.argv.
120 121 To prevent any command-line parsing, pass an empty list: `argv=[]`.
121 122 user_ns : dict, optional
122 123 specify this dictionary to initialize the IPython user namespace with particular values.
123 124 **kwargs : various, optional
124 125 Any other kwargs will be passed to the Application constructor,
125 126 such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
126 127 allowing configuration of the instance (see :ref:`terminal_options`).
127 128 """
128 129 from IPython.terminal.ipapp import launch_new_instance
129 130 return launch_new_instance(argv=argv, **kwargs)
130 131
131 132 def start_kernel(argv=None, **kwargs):
132 133 """Launch a normal IPython kernel instance (as opposed to embedded)
133 134
134 135 `IPython.embed_kernel()` puts a shell in a particular calling scope,
135 136 such as a function or method for debugging purposes,
136 137 which is often not desirable.
137 138
138 139 `start_kernel()` does full, regular IPython initialization,
139 140 including loading startup files, configuration, etc.
140 141 much of which is skipped by `embed_kernel()`.
141 142
142 143 Parameters
143 144 ----------
144 145 argv : list or None, optional
145 146 If unspecified or None, IPython will parse command-line options from sys.argv.
146 147 To prevent any command-line parsing, pass an empty list: `argv=[]`.
147 148 user_ns : dict, optional
148 149 specify this dictionary to initialize the IPython user namespace with particular values.
149 150 **kwargs : various, optional
150 151 Any other kwargs will be passed to the Application constructor,
151 152 such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
152 153 allowing configuration of the kernel (see :ref:`kernel_options`).
153 154 """
154 155 import warnings
155 156
156 157 warnings.warn(
157 158 "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`",
158 159 DeprecationWarning,
159 160 stacklevel=2,
160 161 )
161 162 from ipykernel.kernelapp import launch_new_instance
162 163 return launch_new_instance(argv=argv, **kwargs)
@@ -1,1173 +1,1170 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tools for inspecting Python objects.
3 3
4 4 Uses syntax highlighting for presenting the various information elements.
5 5
6 6 Similar in spirit to the inspect module, but all calls take a name argument to
7 7 reference the name under which an object is being read.
8 8 """
9 9
10 10 # Copyright (c) IPython Development Team.
11 11 # Distributed under the terms of the Modified BSD License.
12 12
13 13 __all__ = ['Inspector','InspectColors']
14 14
15 15 # stdlib modules
16 16 from dataclasses import dataclass
17 17 from inspect import signature
18 18 from textwrap import dedent
19 19 import ast
20 20 import html
21 21 import inspect
22 22 import io as stdlib_io
23 23 import linecache
24 24 import os
25 25 import sys
26 26 import types
27 27 import warnings
28 28
29 29 from typing import Any, Optional, Dict, Union, List, Tuple
30 30
31 if sys.version_info <= (3, 10):
32 from typing_extensions import TypeAlias
33 else:
34 31 from typing import TypeAlias
35 32
36 33 # IPython's own
37 34 from IPython.core import page
38 35 from IPython.lib.pretty import pretty
39 36 from IPython.testing.skipdoctest import skip_doctest
40 37 from IPython.utils import PyColorize
41 38 from IPython.utils import openpy
42 39 from IPython.utils.dir2 import safe_hasattr
43 40 from IPython.utils.path import compress_user
44 41 from IPython.utils.text import indent
45 42 from IPython.utils.wildcard import list_namespace
46 43 from IPython.utils.wildcard import typestr2type
47 44 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
48 45 from IPython.utils.py3compat import cast_unicode
49 46 from IPython.utils.colorable import Colorable
50 47 from IPython.utils.decorators import undoc
51 48
52 49 from pygments import highlight
53 50 from pygments.lexers import PythonLexer
54 51 from pygments.formatters import HtmlFormatter
55 52
56 53 HOOK_NAME = "__custom_documentations__"
57 54
58 55
59 56 UnformattedBundle: TypeAlias = Dict[str, List[Tuple[str, str]]] # List of (title, body)
60 57 Bundle: TypeAlias = Dict[str, str]
61 58
62 59
63 60 @dataclass
64 61 class OInfo:
65 62 ismagic: bool
66 63 isalias: bool
67 64 found: bool
68 65 namespace: Optional[str]
69 66 parent: Any
70 67 obj: Any
71 68
72 69 def get(self, field):
73 70 """Get a field from the object for backward compatibility with before 8.12
74 71
75 72 see https://github.com/h5py/h5py/issues/2253
76 73 """
77 74 # We need to deprecate this at some point, but the warning will show in completion.
78 75 # Let's comment this for now and uncomment end of 2023 ish
79 76 # warnings.warn(
80 77 # f"OInfo dataclass with fields access since IPython 8.12 please use OInfo.{field} instead."
81 78 # "OInfo used to be a dict but a dataclass provide static fields verification with mypy."
82 79 # "This warning and backward compatibility `get()` method were added in 8.13.",
83 80 # DeprecationWarning,
84 81 # stacklevel=2,
85 82 # )
86 83 return getattr(self, field)
87 84
88 85
89 86 def pylight(code):
90 87 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
91 88
92 89 # builtin docstrings to ignore
93 90 _func_call_docstring = types.FunctionType.__call__.__doc__
94 91 _object_init_docstring = object.__init__.__doc__
95 92 _builtin_type_docstrings = {
96 93 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
97 94 types.FunctionType, property)
98 95 }
99 96
100 97 _builtin_func_type = type(all)
101 98 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
102 99 #****************************************************************************
103 100 # Builtin color schemes
104 101
105 102 Colors = TermColors # just a shorthand
106 103
107 104 InspectColors = PyColorize.ANSICodeColors
108 105
109 106 #****************************************************************************
110 107 # Auxiliary functions and objects
111 108
112 109 # See the messaging spec for the definition of all these fields. This list
113 110 # effectively defines the order of display
114 111 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
115 112 'length', 'file', 'definition', 'docstring', 'source',
116 113 'init_definition', 'class_docstring', 'init_docstring',
117 114 'call_def', 'call_docstring',
118 115 # These won't be printed but will be used to determine how to
119 116 # format the object
120 117 'ismagic', 'isalias', 'isclass', 'found', 'name'
121 118 ]
122 119
123 120
124 121 def object_info(**kw):
125 122 """Make an object info dict with all fields present."""
126 123 infodict = {k:None for k in info_fields}
127 124 infodict.update(kw)
128 125 return infodict
129 126
130 127
131 128 def get_encoding(obj):
132 129 """Get encoding for python source file defining obj
133 130
134 131 Returns None if obj is not defined in a sourcefile.
135 132 """
136 133 ofile = find_file(obj)
137 134 # run contents of file through pager starting at line where the object
138 135 # is defined, as long as the file isn't binary and is actually on the
139 136 # filesystem.
140 137 if ofile is None:
141 138 return None
142 139 elif ofile.endswith(('.so', '.dll', '.pyd')):
143 140 return None
144 141 elif not os.path.isfile(ofile):
145 142 return None
146 143 else:
147 144 # Print only text files, not extension binaries. Note that
148 145 # getsourcelines returns lineno with 1-offset and page() uses
149 146 # 0-offset, so we must adjust.
150 147 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
151 148 encoding, lines = openpy.detect_encoding(buffer.readline)
152 149 return encoding
153 150
154 151 def getdoc(obj) -> Union[str,None]:
155 152 """Stable wrapper around inspect.getdoc.
156 153
157 154 This can't crash because of attribute problems.
158 155
159 156 It also attempts to call a getdoc() method on the given object. This
160 157 allows objects which provide their docstrings via non-standard mechanisms
161 158 (like Pyro proxies) to still be inspected by ipython's ? system.
162 159 """
163 160 # Allow objects to offer customized documentation via a getdoc method:
164 161 try:
165 162 ds = obj.getdoc()
166 163 except Exception:
167 164 pass
168 165 else:
169 166 if isinstance(ds, str):
170 167 return inspect.cleandoc(ds)
171 168 docstr = inspect.getdoc(obj)
172 169 return docstr
173 170
174 171
175 172 def getsource(obj, oname='') -> Union[str,None]:
176 173 """Wrapper around inspect.getsource.
177 174
178 175 This can be modified by other projects to provide customized source
179 176 extraction.
180 177
181 178 Parameters
182 179 ----------
183 180 obj : object
184 181 an object whose source code we will attempt to extract
185 182 oname : str
186 183 (optional) a name under which the object is known
187 184
188 185 Returns
189 186 -------
190 187 src : unicode or None
191 188
192 189 """
193 190
194 191 if isinstance(obj, property):
195 192 sources = []
196 193 for attrname in ['fget', 'fset', 'fdel']:
197 194 fn = getattr(obj, attrname)
198 195 if fn is not None:
199 196 encoding = get_encoding(fn)
200 197 oname_prefix = ('%s.' % oname) if oname else ''
201 198 sources.append(''.join(('# ', oname_prefix, attrname)))
202 199 if inspect.isfunction(fn):
203 200 _src = getsource(fn)
204 201 if _src:
205 202 # assert _src is not None, "please mypy"
206 203 sources.append(dedent(_src))
207 204 else:
208 205 # Default str/repr only prints function name,
209 206 # pretty.pretty prints module name too.
210 207 sources.append(
211 208 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
212 209 )
213 210 if sources:
214 211 return '\n'.join(sources)
215 212 else:
216 213 return None
217 214
218 215 else:
219 216 # Get source for non-property objects.
220 217
221 218 obj = _get_wrapped(obj)
222 219
223 220 try:
224 221 src = inspect.getsource(obj)
225 222 except TypeError:
226 223 # The object itself provided no meaningful source, try looking for
227 224 # its class definition instead.
228 225 try:
229 226 src = inspect.getsource(obj.__class__)
230 227 except (OSError, TypeError):
231 228 return None
232 229 except OSError:
233 230 return None
234 231
235 232 return src
236 233
237 234
238 235 def is_simple_callable(obj):
239 236 """True if obj is a function ()"""
240 237 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
241 238 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
242 239
243 240 @undoc
244 241 def getargspec(obj):
245 242 """Wrapper around :func:`inspect.getfullargspec`
246 243
247 244 In addition to functions and methods, this can also handle objects with a
248 245 ``__call__`` attribute.
249 246
250 247 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
251 248 """
252 249
253 250 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
254 251 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
255 252
256 253 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
257 254 obj = obj.__call__
258 255
259 256 return inspect.getfullargspec(obj)
260 257
261 258 @undoc
262 259 def format_argspec(argspec):
263 260 """Format argspect, convenience wrapper around inspect's.
264 261
265 262 This takes a dict instead of ordered arguments and calls
266 263 inspect.format_argspec with the arguments in the necessary order.
267 264
268 265 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
269 266 """
270 267
271 268 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
272 269 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
273 270
274 271
275 272 return inspect.formatargspec(argspec['args'], argspec['varargs'],
276 273 argspec['varkw'], argspec['defaults'])
277 274
278 275 @undoc
279 276 def call_tip(oinfo, format_call=True):
280 277 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
281 278 warnings.warn(
282 279 "`call_tip` function is deprecated as of IPython 6.0"
283 280 "and will be removed in future versions.",
284 281 DeprecationWarning,
285 282 stacklevel=2,
286 283 )
287 284 # Get call definition
288 285 argspec = oinfo.get('argspec')
289 286 if argspec is None:
290 287 call_line = None
291 288 else:
292 289 # Callable objects will have 'self' as their first argument, prune
293 290 # it out if it's there for clarity (since users do *not* pass an
294 291 # extra first argument explicitly).
295 292 try:
296 293 has_self = argspec['args'][0] == 'self'
297 294 except (KeyError, IndexError):
298 295 pass
299 296 else:
300 297 if has_self:
301 298 argspec['args'] = argspec['args'][1:]
302 299
303 300 call_line = oinfo['name']+format_argspec(argspec)
304 301
305 302 # Now get docstring.
306 303 # The priority is: call docstring, constructor docstring, main one.
307 304 doc = oinfo.get('call_docstring')
308 305 if doc is None:
309 306 doc = oinfo.get('init_docstring')
310 307 if doc is None:
311 308 doc = oinfo.get('docstring','')
312 309
313 310 return call_line, doc
314 311
315 312
316 313 def _get_wrapped(obj):
317 314 """Get the original object if wrapped in one or more @decorators
318 315
319 316 Some objects automatically construct similar objects on any unrecognised
320 317 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
321 318 this will arbitrarily cut off after 100 levels of obj.__wrapped__
322 319 attribute access. --TK, Jan 2016
323 320 """
324 321 orig_obj = obj
325 322 i = 0
326 323 while safe_hasattr(obj, '__wrapped__'):
327 324 obj = obj.__wrapped__
328 325 i += 1
329 326 if i > 100:
330 327 # __wrapped__ is probably a lie, so return the thing we started with
331 328 return orig_obj
332 329 return obj
333 330
334 331 def find_file(obj) -> str:
335 332 """Find the absolute path to the file where an object was defined.
336 333
337 334 This is essentially a robust wrapper around `inspect.getabsfile`.
338 335
339 336 Returns None if no file can be found.
340 337
341 338 Parameters
342 339 ----------
343 340 obj : any Python object
344 341
345 342 Returns
346 343 -------
347 344 fname : str
348 345 The absolute path to the file where the object was defined.
349 346 """
350 347 obj = _get_wrapped(obj)
351 348
352 349 fname = None
353 350 try:
354 351 fname = inspect.getabsfile(obj)
355 352 except TypeError:
356 353 # For an instance, the file that matters is where its class was
357 354 # declared.
358 355 try:
359 356 fname = inspect.getabsfile(obj.__class__)
360 357 except (OSError, TypeError):
361 358 # Can happen for builtins
362 359 pass
363 360 except OSError:
364 361 pass
365 362
366 363 return cast_unicode(fname)
367 364
368 365
369 366 def find_source_lines(obj):
370 367 """Find the line number in a file where an object was defined.
371 368
372 369 This is essentially a robust wrapper around `inspect.getsourcelines`.
373 370
374 371 Returns None if no file can be found.
375 372
376 373 Parameters
377 374 ----------
378 375 obj : any Python object
379 376
380 377 Returns
381 378 -------
382 379 lineno : int
383 380 The line number where the object definition starts.
384 381 """
385 382 obj = _get_wrapped(obj)
386 383
387 384 try:
388 385 lineno = inspect.getsourcelines(obj)[1]
389 386 except TypeError:
390 387 # For instances, try the class object like getsource() does
391 388 try:
392 389 lineno = inspect.getsourcelines(obj.__class__)[1]
393 390 except (OSError, TypeError):
394 391 return None
395 392 except OSError:
396 393 return None
397 394
398 395 return lineno
399 396
400 397 class Inspector(Colorable):
401 398
402 399 def __init__(self, color_table=InspectColors,
403 400 code_color_table=PyColorize.ANSICodeColors,
404 401 scheme=None,
405 402 str_detail_level=0,
406 403 parent=None, config=None):
407 404 super(Inspector, self).__init__(parent=parent, config=config)
408 405 self.color_table = color_table
409 406 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
410 407 self.format = self.parser.format
411 408 self.str_detail_level = str_detail_level
412 409 self.set_active_scheme(scheme)
413 410
414 411 def _getdef(self,obj,oname='') -> Union[str,None]:
415 412 """Return the call signature for any callable object.
416 413
417 414 If any exception is generated, None is returned instead and the
418 415 exception is suppressed."""
419 416 if not callable(obj):
420 417 return None
421 418 try:
422 419 return _render_signature(signature(obj), oname)
423 420 except:
424 421 return None
425 422
426 423 def __head(self,h) -> str:
427 424 """Return a header string with proper colors."""
428 425 return '%s%s%s' % (self.color_table.active_colors.header,h,
429 426 self.color_table.active_colors.normal)
430 427
431 428 def set_active_scheme(self, scheme):
432 429 if scheme is not None:
433 430 self.color_table.set_active_scheme(scheme)
434 431 self.parser.color_table.set_active_scheme(scheme)
435 432
436 433 def noinfo(self, msg, oname):
437 434 """Generic message when no information is found."""
438 435 print('No %s found' % msg, end=' ')
439 436 if oname:
440 437 print('for %s' % oname)
441 438 else:
442 439 print()
443 440
444 441 def pdef(self, obj, oname=''):
445 442 """Print the call signature for any callable object.
446 443
447 444 If the object is a class, print the constructor information."""
448 445
449 446 if not callable(obj):
450 447 print('Object is not callable.')
451 448 return
452 449
453 450 header = ''
454 451
455 452 if inspect.isclass(obj):
456 453 header = self.__head('Class constructor information:\n')
457 454
458 455
459 456 output = self._getdef(obj,oname)
460 457 if output is None:
461 458 self.noinfo('definition header',oname)
462 459 else:
463 460 print(header,self.format(output), end=' ')
464 461
465 462 # In Python 3, all classes are new-style, so they all have __init__.
466 463 @skip_doctest
467 464 def pdoc(self, obj, oname='', formatter=None):
468 465 """Print the docstring for any object.
469 466
470 467 Optional:
471 468 -formatter: a function to run the docstring through for specially
472 469 formatted docstrings.
473 470
474 471 Examples
475 472 --------
476 473 In [1]: class NoInit:
477 474 ...: pass
478 475
479 476 In [2]: class NoDoc:
480 477 ...: def __init__(self):
481 478 ...: pass
482 479
483 480 In [3]: %pdoc NoDoc
484 481 No documentation found for NoDoc
485 482
486 483 In [4]: %pdoc NoInit
487 484 No documentation found for NoInit
488 485
489 486 In [5]: obj = NoInit()
490 487
491 488 In [6]: %pdoc obj
492 489 No documentation found for obj
493 490
494 491 In [5]: obj2 = NoDoc()
495 492
496 493 In [6]: %pdoc obj2
497 494 No documentation found for obj2
498 495 """
499 496
500 497 head = self.__head # For convenience
501 498 lines = []
502 499 ds = getdoc(obj)
503 500 if formatter:
504 501 ds = formatter(ds).get('plain/text', ds)
505 502 if ds:
506 503 lines.append(head("Class docstring:"))
507 504 lines.append(indent(ds))
508 505 if inspect.isclass(obj) and hasattr(obj, '__init__'):
509 506 init_ds = getdoc(obj.__init__)
510 507 if init_ds is not None:
511 508 lines.append(head("Init docstring:"))
512 509 lines.append(indent(init_ds))
513 510 elif hasattr(obj,'__call__'):
514 511 call_ds = getdoc(obj.__call__)
515 512 if call_ds:
516 513 lines.append(head("Call docstring:"))
517 514 lines.append(indent(call_ds))
518 515
519 516 if not lines:
520 517 self.noinfo('documentation',oname)
521 518 else:
522 519 page.page('\n'.join(lines))
523 520
524 521 def psource(self, obj, oname=''):
525 522 """Print the source code for an object."""
526 523
527 524 # Flush the source cache because inspect can return out-of-date source
528 525 linecache.checkcache()
529 526 try:
530 527 src = getsource(obj, oname=oname)
531 528 except Exception:
532 529 src = None
533 530
534 531 if src is None:
535 532 self.noinfo('source', oname)
536 533 else:
537 534 page.page(self.format(src))
538 535
539 536 def pfile(self, obj, oname=''):
540 537 """Show the whole file where an object was defined."""
541 538
542 539 lineno = find_source_lines(obj)
543 540 if lineno is None:
544 541 self.noinfo('file', oname)
545 542 return
546 543
547 544 ofile = find_file(obj)
548 545 # run contents of file through pager starting at line where the object
549 546 # is defined, as long as the file isn't binary and is actually on the
550 547 # filesystem.
551 548 if ofile.endswith(('.so', '.dll', '.pyd')):
552 549 print('File %r is binary, not printing.' % ofile)
553 550 elif not os.path.isfile(ofile):
554 551 print('File %r does not exist, not printing.' % ofile)
555 552 else:
556 553 # Print only text files, not extension binaries. Note that
557 554 # getsourcelines returns lineno with 1-offset and page() uses
558 555 # 0-offset, so we must adjust.
559 556 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
560 557
561 558
562 559 def _mime_format(self, text:str, formatter=None) -> dict:
563 560 """Return a mime bundle representation of the input text.
564 561
565 562 - if `formatter` is None, the returned mime bundle has
566 563 a ``text/plain`` field, with the input text.
567 564 a ``text/html`` field with a ``<pre>`` tag containing the input text.
568 565
569 566 - if ``formatter`` is not None, it must be a callable transforming the
570 567 input text into a mime bundle. Default values for ``text/plain`` and
571 568 ``text/html`` representations are the ones described above.
572 569
573 570 Note:
574 571
575 572 Formatters returning strings are supported but this behavior is deprecated.
576 573
577 574 """
578 575 defaults = {
579 576 "text/plain": text,
580 577 "text/html": f"<pre>{html.escape(text)}</pre>",
581 578 }
582 579
583 580 if formatter is None:
584 581 return defaults
585 582 else:
586 583 formatted = formatter(text)
587 584
588 585 if not isinstance(formatted, dict):
589 586 # Handle the deprecated behavior of a formatter returning
590 587 # a string instead of a mime bundle.
591 588 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
592 589
593 590 else:
594 591 return dict(defaults, **formatted)
595 592
596 593 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
597 594 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
598 595 # Format text/plain mimetype
599 596 assert isinstance(bundle["text/plain"], list)
600 597 for item in bundle["text/plain"]:
601 598 assert isinstance(item, tuple)
602 599
603 600 new_b: Bundle = {}
604 601 lines = []
605 602 _len = max(len(h) for h, _ in bundle["text/plain"])
606 603
607 604 for head, body in bundle["text/plain"]:
608 605 body = body.strip("\n")
609 606 delim = "\n" if "\n" in body else " "
610 607 lines.append(
611 608 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
612 609 )
613 610
614 611 new_b["text/plain"] = "\n".join(lines)
615 612
616 613 if "text/html" in bundle:
617 614 assert isinstance(bundle["text/html"], list)
618 615 for item in bundle["text/html"]:
619 616 assert isinstance(item, tuple)
620 617 # Format the text/html mimetype
621 618 if isinstance(bundle["text/html"], (list, tuple)):
622 619 # bundle['text/html'] is a list of (head, formatted body) pairs
623 620 new_b["text/html"] = "\n".join(
624 621 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
625 622 )
626 623
627 624 for k in bundle.keys():
628 625 if k in ("text/html", "text/plain"):
629 626 continue
630 627 else:
631 628 new_b = bundle[k] # type:ignore
632 629 return new_b
633 630
634 631 def _append_info_field(
635 632 self,
636 633 bundle: UnformattedBundle,
637 634 title: str,
638 635 key: str,
639 636 info,
640 637 omit_sections,
641 638 formatter,
642 639 ):
643 640 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
644 641 if title in omit_sections or key in omit_sections:
645 642 return
646 643 field = info[key]
647 644 if field is not None:
648 645 formatted_field = self._mime_format(field, formatter)
649 646 bundle["text/plain"].append((title, formatted_field["text/plain"]))
650 647 bundle["text/html"].append((title, formatted_field["text/html"]))
651 648
652 649 def _make_info_unformatted(
653 650 self, obj, info, formatter, detail_level, omit_sections
654 651 ) -> UnformattedBundle:
655 652 """Assemble the mimebundle as unformatted lists of information"""
656 653 bundle: UnformattedBundle = {
657 654 "text/plain": [],
658 655 "text/html": [],
659 656 }
660 657
661 658 # A convenience function to simplify calls below
662 659 def append_field(
663 660 bundle: UnformattedBundle, title: str, key: str, formatter=None
664 661 ):
665 662 self._append_info_field(
666 663 bundle,
667 664 title=title,
668 665 key=key,
669 666 info=info,
670 667 omit_sections=omit_sections,
671 668 formatter=formatter,
672 669 )
673 670
674 671 def code_formatter(text) -> Bundle:
675 672 return {
676 673 'text/plain': self.format(text),
677 674 'text/html': pylight(text)
678 675 }
679 676
680 677 if info["isalias"]:
681 678 append_field(bundle, "Repr", "string_form")
682 679
683 680 elif info['ismagic']:
684 681 if detail_level > 0:
685 682 append_field(bundle, "Source", "source", code_formatter)
686 683 else:
687 684 append_field(bundle, "Docstring", "docstring", formatter)
688 685 append_field(bundle, "File", "file")
689 686
690 687 elif info['isclass'] or is_simple_callable(obj):
691 688 # Functions, methods, classes
692 689 append_field(bundle, "Signature", "definition", code_formatter)
693 690 append_field(bundle, "Init signature", "init_definition", code_formatter)
694 691 append_field(bundle, "Docstring", "docstring", formatter)
695 692 if detail_level > 0 and info["source"]:
696 693 append_field(bundle, "Source", "source", code_formatter)
697 694 else:
698 695 append_field(bundle, "Init docstring", "init_docstring", formatter)
699 696
700 697 append_field(bundle, "File", "file")
701 698 append_field(bundle, "Type", "type_name")
702 699 append_field(bundle, "Subclasses", "subclasses")
703 700
704 701 else:
705 702 # General Python objects
706 703 append_field(bundle, "Signature", "definition", code_formatter)
707 704 append_field(bundle, "Call signature", "call_def", code_formatter)
708 705 append_field(bundle, "Type", "type_name")
709 706 append_field(bundle, "String form", "string_form")
710 707
711 708 # Namespace
712 709 if info["namespace"] != "Interactive":
713 710 append_field(bundle, "Namespace", "namespace")
714 711
715 712 append_field(bundle, "Length", "length")
716 713 append_field(bundle, "File", "file")
717 714
718 715 # Source or docstring, depending on detail level and whether
719 716 # source found.
720 717 if detail_level > 0 and info["source"]:
721 718 append_field(bundle, "Source", "source", code_formatter)
722 719 else:
723 720 append_field(bundle, "Docstring", "docstring", formatter)
724 721
725 722 append_field(bundle, "Class docstring", "class_docstring", formatter)
726 723 append_field(bundle, "Init docstring", "init_docstring", formatter)
727 724 append_field(bundle, "Call docstring", "call_docstring", formatter)
728 725 return bundle
729 726
730 727
731 728 def _get_info(
732 729 self,
733 730 obj: Any,
734 731 oname: str = "",
735 732 formatter=None,
736 733 info: Optional[OInfo] = None,
737 734 detail_level=0,
738 735 omit_sections=(),
739 736 ) -> Bundle:
740 737 """Retrieve an info dict and format it.
741 738
742 739 Parameters
743 740 ----------
744 741 obj : any
745 742 Object to inspect and return info from
746 743 oname : str (default: ''):
747 744 Name of the variable pointing to `obj`.
748 745 formatter : callable
749 746 info
750 747 already computed information
751 748 detail_level : integer
752 749 Granularity of detail level, if set to 1, give more information.
753 750 omit_sections : container[str]
754 751 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
755 752 """
756 753
757 754 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level)
758 755 bundle = self._make_info_unformatted(
759 756 obj,
760 757 info_dict,
761 758 formatter,
762 759 detail_level=detail_level,
763 760 omit_sections=omit_sections,
764 761 )
765 762 return self.format_mime(bundle)
766 763
767 764 def pinfo(
768 765 self,
769 766 obj,
770 767 oname="",
771 768 formatter=None,
772 769 info: Optional[OInfo] = None,
773 770 detail_level=0,
774 771 enable_html_pager=True,
775 772 omit_sections=(),
776 773 ):
777 774 """Show detailed information about an object.
778 775
779 776 Optional arguments:
780 777
781 778 - oname: name of the variable pointing to the object.
782 779
783 780 - formatter: callable (optional)
784 781 A special formatter for docstrings.
785 782
786 783 The formatter is a callable that takes a string as an input
787 784 and returns either a formatted string or a mime type bundle
788 785 in the form of a dictionary.
789 786
790 787 Although the support of custom formatter returning a string
791 788 instead of a mime type bundle is deprecated.
792 789
793 790 - info: a structure with some information fields which may have been
794 791 precomputed already.
795 792
796 793 - detail_level: if set to 1, more information is given.
797 794
798 795 - omit_sections: set of section keys and titles to omit
799 796 """
800 797 assert info is not None
801 798 info_b: Bundle = self._get_info(
802 799 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
803 800 )
804 801 if not enable_html_pager:
805 802 del info_b["text/html"]
806 803 page.page(info_b)
807 804
808 805 def _info(self, obj, oname="", info=None, detail_level=0):
809 806 """
810 807 Inspector.info() was likely improperly marked as deprecated
811 808 while only a parameter was deprecated. We "un-deprecate" it.
812 809 """
813 810
814 811 warnings.warn(
815 812 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
816 813 "and the `formatter=` keyword removed. `Inspector._info` is now "
817 814 "an alias, and you can just call `.info()` directly.",
818 815 DeprecationWarning,
819 816 stacklevel=2,
820 817 )
821 818 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
822 819
823 820 def info(self, obj, oname="", info=None, detail_level=0) -> Dict[str, Any]:
824 821 """Compute a dict with detailed information about an object.
825 822
826 823 Parameters
827 824 ----------
828 825 obj : any
829 826 An object to find information about
830 827 oname : str (default: '')
831 828 Name of the variable pointing to `obj`.
832 829 info : (default: None)
833 830 A struct (dict like with attr access) with some information fields
834 831 which may have been precomputed already.
835 832 detail_level : int (default:0)
836 833 If set to 1, more information is given.
837 834
838 835 Returns
839 836 -------
840 837 An object info dict with known fields from `info_fields`. Keys are
841 838 strings, values are string or None.
842 839 """
843 840
844 841 if info is None:
845 842 ismagic = False
846 843 isalias = False
847 844 ospace = ''
848 845 else:
849 846 ismagic = info.ismagic
850 847 isalias = info.isalias
851 848 ospace = info.namespace
852 849
853 850 # Get docstring, special-casing aliases:
854 851 att_name = oname.split(".")[-1]
855 852 parents_docs = None
856 853 prelude = ""
857 854 if info and info.parent is not None and hasattr(info.parent, HOOK_NAME):
858 855 parents_docs_dict = getattr(info.parent, HOOK_NAME)
859 856 parents_docs = parents_docs_dict.get(att_name, None)
860 857 out = dict(
861 858 name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None
862 859 )
863 860
864 861 if parents_docs:
865 862 ds = parents_docs
866 863 elif isalias:
867 864 if not callable(obj):
868 865 try:
869 866 ds = "Alias to the system command:\n %s" % obj[1]
870 867 except:
871 868 ds = "Alias: " + str(obj)
872 869 else:
873 870 ds = "Alias to " + str(obj)
874 871 if obj.__doc__:
875 872 ds += "\nDocstring:\n" + obj.__doc__
876 873 else:
877 874 ds_or_None = getdoc(obj)
878 875 if ds_or_None is None:
879 876 ds = '<no docstring>'
880 877 else:
881 878 ds = ds_or_None
882 879
883 880 ds = prelude + ds
884 881
885 882 # store output in a dict, we initialize it here and fill it as we go
886 883
887 884 string_max = 200 # max size of strings to show (snipped if longer)
888 885 shalf = int((string_max - 5) / 2)
889 886
890 887 if ismagic:
891 888 out['type_name'] = 'Magic function'
892 889 elif isalias:
893 890 out['type_name'] = 'System alias'
894 891 else:
895 892 out['type_name'] = type(obj).__name__
896 893
897 894 try:
898 895 bclass = obj.__class__
899 896 out['base_class'] = str(bclass)
900 897 except:
901 898 pass
902 899
903 900 # String form, but snip if too long in ? form (full in ??)
904 901 if detail_level >= self.str_detail_level:
905 902 try:
906 903 ostr = str(obj)
907 904 str_head = 'string_form'
908 905 if not detail_level and len(ostr)>string_max:
909 906 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
910 907 ostr = ("\n" + " " * len(str_head.expandtabs())).\
911 908 join(q.strip() for q in ostr.split("\n"))
912 909 out[str_head] = ostr
913 910 except:
914 911 pass
915 912
916 913 if ospace:
917 914 out['namespace'] = ospace
918 915
919 916 # Length (for strings and lists)
920 917 try:
921 918 out['length'] = str(len(obj))
922 919 except Exception:
923 920 pass
924 921
925 922 # Filename where object was defined
926 923 binary_file = False
927 924 fname = find_file(obj)
928 925 if fname is None:
929 926 # if anything goes wrong, we don't want to show source, so it's as
930 927 # if the file was binary
931 928 binary_file = True
932 929 else:
933 930 if fname.endswith(('.so', '.dll', '.pyd')):
934 931 binary_file = True
935 932 elif fname.endswith('<string>'):
936 933 fname = 'Dynamically generated function. No source code available.'
937 934 out['file'] = compress_user(fname)
938 935
939 936 # Original source code for a callable, class or property.
940 937 if detail_level:
941 938 # Flush the source cache because inspect can return out-of-date
942 939 # source
943 940 linecache.checkcache()
944 941 try:
945 942 if isinstance(obj, property) or not binary_file:
946 943 src = getsource(obj, oname)
947 944 if src is not None:
948 945 src = src.rstrip()
949 946 out['source'] = src
950 947
951 948 except Exception:
952 949 pass
953 950
954 951 # Add docstring only if no source is to be shown (avoid repetitions).
955 952 if ds and not self._source_contains_docstring(out.get('source'), ds):
956 953 out['docstring'] = ds
957 954
958 955 # Constructor docstring for classes
959 956 if inspect.isclass(obj):
960 957 out['isclass'] = True
961 958
962 959 # get the init signature:
963 960 try:
964 961 init_def = self._getdef(obj, oname)
965 962 except AttributeError:
966 963 init_def = None
967 964
968 965 # get the __init__ docstring
969 966 try:
970 967 obj_init = obj.__init__
971 968 except AttributeError:
972 969 init_ds = None
973 970 else:
974 971 if init_def is None:
975 972 # Get signature from init if top-level sig failed.
976 973 # Can happen for built-in types (list, etc.).
977 974 try:
978 975 init_def = self._getdef(obj_init, oname)
979 976 except AttributeError:
980 977 pass
981 978 init_ds = getdoc(obj_init)
982 979 # Skip Python's auto-generated docstrings
983 980 if init_ds == _object_init_docstring:
984 981 init_ds = None
985 982
986 983 if init_def:
987 984 out['init_definition'] = init_def
988 985
989 986 if init_ds:
990 987 out['init_docstring'] = init_ds
991 988
992 989 names = [sub.__name__ for sub in type.__subclasses__(obj)]
993 990 if len(names) < 10:
994 991 all_names = ', '.join(names)
995 992 else:
996 993 all_names = ', '.join(names[:10]+['...'])
997 994 out['subclasses'] = all_names
998 995 # and class docstring for instances:
999 996 else:
1000 997 # reconstruct the function definition and print it:
1001 998 defln = self._getdef(obj, oname)
1002 999 if defln:
1003 1000 out['definition'] = defln
1004 1001
1005 1002 # First, check whether the instance docstring is identical to the
1006 1003 # class one, and print it separately if they don't coincide. In
1007 1004 # most cases they will, but it's nice to print all the info for
1008 1005 # objects which use instance-customized docstrings.
1009 1006 if ds:
1010 1007 try:
1011 1008 cls = getattr(obj,'__class__')
1012 1009 except:
1013 1010 class_ds = None
1014 1011 else:
1015 1012 class_ds = getdoc(cls)
1016 1013 # Skip Python's auto-generated docstrings
1017 1014 if class_ds in _builtin_type_docstrings:
1018 1015 class_ds = None
1019 1016 if class_ds and ds != class_ds:
1020 1017 out['class_docstring'] = class_ds
1021 1018
1022 1019 # Next, try to show constructor docstrings
1023 1020 try:
1024 1021 init_ds = getdoc(obj.__init__)
1025 1022 # Skip Python's auto-generated docstrings
1026 1023 if init_ds == _object_init_docstring:
1027 1024 init_ds = None
1028 1025 except AttributeError:
1029 1026 init_ds = None
1030 1027 if init_ds:
1031 1028 out['init_docstring'] = init_ds
1032 1029
1033 1030 # Call form docstring for callable instances
1034 1031 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1035 1032 call_def = self._getdef(obj.__call__, oname)
1036 1033 if call_def and (call_def != out.get('definition')):
1037 1034 # it may never be the case that call def and definition differ,
1038 1035 # but don't include the same signature twice
1039 1036 out['call_def'] = call_def
1040 1037 call_ds = getdoc(obj.__call__)
1041 1038 # Skip Python's auto-generated docstrings
1042 1039 if call_ds == _func_call_docstring:
1043 1040 call_ds = None
1044 1041 if call_ds:
1045 1042 out['call_docstring'] = call_ds
1046 1043
1047 1044 return object_info(**out)
1048 1045
1049 1046 @staticmethod
1050 1047 def _source_contains_docstring(src, doc):
1051 1048 """
1052 1049 Check whether the source *src* contains the docstring *doc*.
1053 1050
1054 1051 This is is helper function to skip displaying the docstring if the
1055 1052 source already contains it, avoiding repetition of information.
1056 1053 """
1057 1054 try:
1058 1055 (def_node,) = ast.parse(dedent(src)).body
1059 1056 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1060 1057 except Exception:
1061 1058 # The source can become invalid or even non-existent (because it
1062 1059 # is re-fetched from the source file) so the above code fail in
1063 1060 # arbitrary ways.
1064 1061 return False
1065 1062
1066 1063 def psearch(self,pattern,ns_table,ns_search=[],
1067 1064 ignore_case=False,show_all=False, *, list_types=False):
1068 1065 """Search namespaces with wildcards for objects.
1069 1066
1070 1067 Arguments:
1071 1068
1072 1069 - pattern: string containing shell-like wildcards to use in namespace
1073 1070 searches and optionally a type specification to narrow the search to
1074 1071 objects of that type.
1075 1072
1076 1073 - ns_table: dict of name->namespaces for search.
1077 1074
1078 1075 Optional arguments:
1079 1076
1080 1077 - ns_search: list of namespace names to include in search.
1081 1078
1082 1079 - ignore_case(False): make the search case-insensitive.
1083 1080
1084 1081 - show_all(False): show all names, including those starting with
1085 1082 underscores.
1086 1083
1087 1084 - list_types(False): list all available object types for object matching.
1088 1085 """
1089 1086 #print 'ps pattern:<%r>' % pattern # dbg
1090 1087
1091 1088 # defaults
1092 1089 type_pattern = 'all'
1093 1090 filter = ''
1094 1091
1095 1092 # list all object types
1096 1093 if list_types:
1097 1094 page.page('\n'.join(sorted(typestr2type)))
1098 1095 return
1099 1096
1100 1097 cmds = pattern.split()
1101 1098 len_cmds = len(cmds)
1102 1099 if len_cmds == 1:
1103 1100 # Only filter pattern given
1104 1101 filter = cmds[0]
1105 1102 elif len_cmds == 2:
1106 1103 # Both filter and type specified
1107 1104 filter,type_pattern = cmds
1108 1105 else:
1109 1106 raise ValueError('invalid argument string for psearch: <%s>' %
1110 1107 pattern)
1111 1108
1112 1109 # filter search namespaces
1113 1110 for name in ns_search:
1114 1111 if name not in ns_table:
1115 1112 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1116 1113 (name,ns_table.keys()))
1117 1114
1118 1115 #print 'type_pattern:',type_pattern # dbg
1119 1116 search_result, namespaces_seen = set(), set()
1120 1117 for ns_name in ns_search:
1121 1118 ns = ns_table[ns_name]
1122 1119 # Normally, locals and globals are the same, so we just check one.
1123 1120 if id(ns) in namespaces_seen:
1124 1121 continue
1125 1122 namespaces_seen.add(id(ns))
1126 1123 tmp_res = list_namespace(ns, type_pattern, filter,
1127 1124 ignore_case=ignore_case, show_all=show_all)
1128 1125 search_result.update(tmp_res)
1129 1126
1130 1127 page.page('\n'.join(sorted(search_result)))
1131 1128
1132 1129
1133 1130 def _render_signature(obj_signature, obj_name) -> str:
1134 1131 """
1135 1132 This was mostly taken from inspect.Signature.__str__.
1136 1133 Look there for the comments.
1137 1134 The only change is to add linebreaks when this gets too long.
1138 1135 """
1139 1136 result = []
1140 1137 pos_only = False
1141 1138 kw_only = True
1142 1139 for param in obj_signature.parameters.values():
1143 1140 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1144 1141 pos_only = True
1145 1142 elif pos_only:
1146 1143 result.append('/')
1147 1144 pos_only = False
1148 1145
1149 1146 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1150 1147 kw_only = False
1151 1148 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1152 1149 result.append('*')
1153 1150 kw_only = False
1154 1151
1155 1152 result.append(str(param))
1156 1153
1157 1154 if pos_only:
1158 1155 result.append('/')
1159 1156
1160 1157 # add up name, parameters, braces (2), and commas
1161 1158 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1162 1159 # This doesn’t fit behind “Signature: ” in an inspect window.
1163 1160 rendered = '{}(\n{})'.format(obj_name, ''.join(
1164 1161 ' {},\n'.format(r) for r in result)
1165 1162 )
1166 1163 else:
1167 1164 rendered = '{}({})'.format(obj_name, ', '.join(result))
1168 1165
1169 1166 if obj_signature.return_annotation is not inspect._empty:
1170 1167 anno = inspect.formatannotation(obj_signature.return_annotation)
1171 1168 rendered += ' -> {}'.format(anno)
1172 1169
1173 1170 return rendered
@@ -1,316 +1,325 b''
1 1 """
2 2 Test for async helpers.
3 3
4 4 Should only trigger on python 3.5+ or will have syntax errors.
5 5 """
6 import platform
7 6 from itertools import chain, repeat
8 7 from textwrap import dedent, indent
8 from typing import TYPE_CHECKING
9 9 from unittest import TestCase
10
11 import pytest
12
13 from IPython.core.async_helpers import _should_be_async
10 14 from IPython.testing.decorators import skip_without
11 import sys
12 from typing import TYPE_CHECKING
13 15
14 16 if TYPE_CHECKING:
15 17 from IPython import get_ipython
16 18
17 19 ip = get_ipython()
18 20
19 21
20 iprc = lambda x: ip.run_cell(dedent(x)).raise_error()
21 iprc_nr = lambda x: ip.run_cell(dedent(x))
22 def iprc(x):
23 return ip.run_cell(dedent(x)).raise_error()
24
25
26 def iprc_nr(x):
27 return ip.run_cell(dedent(x))
22 28
23 from IPython.core.async_helpers import _should_be_async
24 29
25 30 class AsyncTest(TestCase):
26 31 def test_should_be_async(self):
27 32 self.assertFalse(_should_be_async("False"))
28 33 self.assertTrue(_should_be_async("await bar()"))
29 34 self.assertTrue(_should_be_async("x = await bar()"))
30 35 self.assertFalse(
31 36 _should_be_async(
32 37 dedent(
33 38 """
34 39 async def awaitable():
35 40 pass
36 41 """
37 42 )
38 43 )
39 44 )
40 45
41 46 def _get_top_level_cases(self):
42 47 # These are test cases that should be valid in a function
43 48 # but invalid outside of a function.
44 49 test_cases = []
45 50 test_cases.append(('basic', "{val}"))
46 51
47 52 # Note, in all conditional cases, I use True instead of
48 53 # False so that the peephole optimizer won't optimize away
49 54 # the return, so CPython will see this as a syntax error:
50 55 #
51 56 # while True:
52 57 # break
53 58 # return
54 59 #
55 60 # But not this:
56 61 #
57 62 # while False:
58 63 # return
59 64 #
60 65 # See https://bugs.python.org/issue1875
61 66
62 67 test_cases.append(('if', dedent("""
63 68 if True:
64 69 {val}
65 70 """)))
66 71
67 72 test_cases.append(('while', dedent("""
68 73 while True:
69 74 {val}
70 75 break
71 76 """)))
72 77
73 78 test_cases.append(('try', dedent("""
74 79 try:
75 80 {val}
76 81 except:
77 82 pass
78 83 """)))
79 84
80 85 test_cases.append(('except', dedent("""
81 86 try:
82 87 pass
83 88 except:
84 89 {val}
85 90 """)))
86 91
87 92 test_cases.append(('finally', dedent("""
88 93 try:
89 94 pass
90 95 except:
91 96 pass
92 97 finally:
93 98 {val}
94 99 """)))
95 100
96 101 test_cases.append(('for', dedent("""
97 102 for _ in range(4):
98 103 {val}
99 104 """)))
100 105
101 106
102 107 test_cases.append(('nested', dedent("""
103 108 if True:
104 109 while True:
105 110 {val}
106 111 break
107 112 """)))
108 113
109 114 test_cases.append(('deep-nested', dedent("""
110 115 if True:
111 116 while True:
112 117 break
113 118 for x in range(3):
114 119 if True:
115 120 while True:
116 121 for x in range(3):
117 122 {val}
118 123 """)))
119 124
120 125 return test_cases
121 126
122 127 def _get_ry_syntax_errors(self):
123 128 # This is a mix of tests that should be a syntax error if
124 129 # return or yield whether or not they are in a function
125 130
126 131 test_cases = []
127 132
128 133 test_cases.append(('class', dedent("""
129 134 class V:
130 135 {val}
131 136 """)))
132 137
133 138 test_cases.append(('nested-class', dedent("""
134 139 class V:
135 140 class C:
136 141 {val}
137 142 """)))
138 143
139 144 return test_cases
140 145
141 146
142 147 def test_top_level_return_error(self):
143 148 tl_err_test_cases = self._get_top_level_cases()
144 149 tl_err_test_cases.extend(self._get_ry_syntax_errors())
145 150
146 151 vals = ('return', 'yield', 'yield from (_ for _ in range(3))',
147 152 dedent('''
148 153 def f():
149 154 pass
150 155 return
151 156 '''),
152 157 )
153 158
154 159 for test_name, test_case in tl_err_test_cases:
155 160 # This example should work if 'pass' is used as the value
156 161 with self.subTest((test_name, 'pass')):
157 162 iprc(test_case.format(val='pass'))
158 163
159 164 # It should fail with all the values
160 165 for val in vals:
161 166 with self.subTest((test_name, val)):
162 167 msg = "Syntax error not raised for %s, %s" % (test_name, val)
163 168 with self.assertRaises(SyntaxError, msg=msg):
164 169 iprc(test_case.format(val=val))
165 170
166 171 def test_in_func_no_error(self):
167 172 # Test that the implementation of top-level return/yield
168 173 # detection isn't *too* aggressive, and works inside a function
169 174 func_contexts = []
170 175
171 176 func_contexts.append(('func', False, dedent("""
172 177 def f():""")))
173 178
174 179 func_contexts.append(('method', False, dedent("""
175 180 class MyClass:
176 181 def __init__(self):
177 182 """)))
178 183
179 184 func_contexts.append(('async-func', True, dedent("""
180 185 async def f():""")))
181 186
182 187 func_contexts.append(('async-method', True, dedent("""
183 188 class MyClass:
184 189 async def f(self):""")))
185 190
186 191 func_contexts.append(('closure', False, dedent("""
187 192 def f():
188 193 def g():
189 194 """)))
190 195
191 196 def nest_case(context, case):
192 197 # Detect indentation
193 198 lines = context.strip().splitlines()
194 199 prefix_len = 0
195 200 for c in lines[-1]:
196 201 if c != ' ':
197 202 break
198 203 prefix_len += 1
199 204
200 205 indented_case = indent(case, ' ' * (prefix_len + 4))
201 206 return context + '\n' + indented_case
202 207
203 208 # Gather and run the tests
204 209
205 210 # yield is allowed in async functions, starting in Python 3.6,
206 211 # and yield from is not allowed in any version
207 212 vals = ('return', 'yield', 'yield from (_ for _ in range(3))')
208 213
209 214 success_tests = zip(self._get_top_level_cases(), repeat(False))
210 215 failure_tests = zip(self._get_ry_syntax_errors(), repeat(True))
211 216
212 217 tests = chain(success_tests, failure_tests)
213 218
214 219 for context_name, async_func, context in func_contexts:
215 220 for (test_name, test_case), should_fail in tests:
216 221 nested_case = nest_case(context, test_case)
217 222
218 223 for val in vals:
219 224 test_id = (context_name, test_name, val)
220 225 cell = nested_case.format(val=val)
221 226
222 227 with self.subTest(test_id):
223 228 if should_fail:
224 229 msg = ("SyntaxError not raised for %s" %
225 230 str(test_id))
226 231 with self.assertRaises(SyntaxError, msg=msg):
227 232 iprc(cell)
228 233
229 234 print(cell)
230 235 else:
231 236 iprc(cell)
232 237
233 238 def test_nonlocal(self):
234 239 # fails if outer scope is not a function scope or if var not defined
235 240 with self.assertRaises(SyntaxError):
236 241 iprc("nonlocal x")
237 242 iprc("""
238 243 x = 1
239 244 def f():
240 245 nonlocal x
241 246 x = 10000
242 247 yield x
243 248 """)
244 249 iprc("""
245 250 def f():
246 251 def g():
247 252 nonlocal x
248 253 x = 10000
249 254 yield x
250 255 """)
251 256
252 257 # works if outer scope is a function scope and var exists
253 258 iprc("""
254 259 def f():
255 260 x = 20
256 261 def g():
257 262 nonlocal x
258 263 x = 10000
259 264 yield x
260 265 """)
261 266
262 267
263 268 def test_execute(self):
264 269 iprc("""
265 270 import asyncio
266 271 await asyncio.sleep(0.001)
267 272 """
268 273 )
269 274
270 275 def test_autoawait(self):
271 276 iprc("%autoawait False")
272 277 iprc("%autoawait True")
273 278 iprc("""
274 279 from asyncio import sleep
275 280 await sleep(0.1)
276 281 """
277 282 )
278 283
279 284 def test_memory_error(self):
280 285 """
281 286 The pgen parser in 3.8 or before use to raise MemoryError on too many
282 287 nested parens anymore"""
283 288
284 289 iprc("(" * 200 + ")" * 200)
285 290
286 @skip_without('curio')
291 @pytest.mark.xfail(reason="fail on curio 1.6 and before on Python 3.12")
292 @pytest.mark.skip(
293 reason="skip_without(curio) fails on 3.12 for now even with other skip so must uncond skip"
294 )
295 # @skip_without("curio")
287 296 def test_autoawait_curio(self):
288 297 iprc("%autoawait curio")
289 298
290 299 @skip_without('trio')
291 300 def test_autoawait_trio(self):
292 301 iprc("%autoawait trio")
293 302
294 303 @skip_without('trio')
295 304 def test_autoawait_trio_wrong_sleep(self):
296 305 iprc("%autoawait trio")
297 306 res = iprc_nr("""
298 307 import asyncio
299 308 await asyncio.sleep(0)
300 309 """)
301 310 with self.assertRaises(TypeError):
302 311 res.raise_error()
303 312
304 313 @skip_without('trio')
305 314 def test_autoawait_asyncio_wrong_sleep(self):
306 315 iprc("%autoawait asyncio")
307 316 res = iprc_nr("""
308 317 import trio
309 318 await trio.sleep(0)
310 319 """)
311 320 with self.assertRaises(RuntimeError):
312 321 res.raise_error()
313 322
314 323
315 324 def tearDown(self):
316 325 ip.loop_runner = "asyncio"
@@ -1,643 +1,643 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for the inputsplitter module."""
3 3
4 4
5 5 # Copyright (c) IPython Development Team.
6 6 # Distributed under the terms of the Modified BSD License.
7 7
8 8 import unittest
9 9 import pytest
10 10 import sys
11 11
12 12 with pytest.warns(DeprecationWarning, match="inputsplitter"):
13 13 from IPython.core import inputsplitter as isp
14 14 from IPython.core.inputtransformer import InputTransformer
15 15 from IPython.core.tests.test_inputtransformer import syntax, syntax_ml
16 16 from IPython.testing import tools as tt
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Semi-complete examples (also used as tests)
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # Note: at the bottom, there's a slightly more complete version of this that
23 23 # can be useful during development of code here.
24 24
25 25 def mini_interactive_loop(input_func):
26 26 """Minimal example of the logic of an interactive interpreter loop.
27 27
28 28 This serves as an example, and it is used by the test system with a fake
29 29 raw_input that simulates interactive input."""
30 30
31 31 from IPython.core.inputsplitter import InputSplitter
32 32
33 33 isp = InputSplitter()
34 34 # In practice, this input loop would be wrapped in an outside loop to read
35 35 # input indefinitely, until some exit/quit command was issued. Here we
36 36 # only illustrate the basic inner loop.
37 37 while isp.push_accepts_more():
38 38 indent = ' '*isp.get_indent_spaces()
39 39 prompt = '>>> ' + indent
40 40 line = indent + input_func(prompt)
41 41 isp.push(line)
42 42
43 43 # Here we just return input so we can use it in a test suite, but a real
44 44 # interpreter would instead send it for execution somewhere.
45 45 src = isp.source_reset()
46 46 #print 'Input source was:\n', src # dbg
47 47 return src
48 48
49 49 #-----------------------------------------------------------------------------
50 50 # Test utilities, just for local use
51 51 #-----------------------------------------------------------------------------
52 52
53 53
54 54 def pseudo_input(lines):
55 55 """Return a function that acts like raw_input but feeds the input list."""
56 56 ilines = iter(lines)
57 57 def raw_in(prompt):
58 58 try:
59 59 return next(ilines)
60 60 except StopIteration:
61 61 return ''
62 62 return raw_in
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Tests
66 66 #-----------------------------------------------------------------------------
67 67 def test_spaces():
68 68 tests = [('', 0),
69 69 (' ', 1),
70 70 ('\n', 0),
71 71 (' \n', 1),
72 72 ('x', 0),
73 73 (' x', 1),
74 74 (' x',2),
75 75 (' x',4),
76 76 # Note: tabs are counted as a single whitespace!
77 77 ('\tx', 1),
78 78 ('\t x', 2),
79 79 ]
80 80 with pytest.warns(PendingDeprecationWarning):
81 81 tt.check_pairs(isp.num_ini_spaces, tests)
82 82
83 83
84 84 def test_remove_comments():
85 85 tests = [('text', 'text'),
86 86 ('text # comment', 'text '),
87 87 ('text # comment\n', 'text \n'),
88 88 ('text # comment \n', 'text \n'),
89 89 ('line # c \nline\n','line \nline\n'),
90 90 ('line # c \nline#c2 \nline\nline #c\n\n',
91 91 'line \nline\nline\nline \n\n'),
92 92 ]
93 93 tt.check_pairs(isp.remove_comments, tests)
94 94
95 95
96 96 def test_get_input_encoding():
97 97 encoding = isp.get_input_encoding()
98 98 assert isinstance(encoding, str)
99 99 # simple-minded check that at least encoding a simple string works with the
100 100 # encoding we got.
101 101 assert "test".encode(encoding) == b"test"
102 102
103 103
104 104 class NoInputEncodingTestCase(unittest.TestCase):
105 105 def setUp(self):
106 106 self.old_stdin = sys.stdin
107 107 class X: pass
108 108 fake_stdin = X()
109 109 sys.stdin = fake_stdin
110 110
111 111 def test(self):
112 112 # Verify that if sys.stdin has no 'encoding' attribute we do the right
113 113 # thing
114 114 enc = isp.get_input_encoding()
115 115 self.assertEqual(enc, 'ascii')
116 116
117 117 def tearDown(self):
118 118 sys.stdin = self.old_stdin
119 119
120 120
121 121 class InputSplitterTestCase(unittest.TestCase):
122 122 def setUp(self):
123 123 self.isp = isp.InputSplitter()
124 124
125 125 def test_reset(self):
126 126 isp = self.isp
127 127 isp.push('x=1')
128 128 isp.reset()
129 129 self.assertEqual(isp._buffer, [])
130 130 self.assertEqual(isp.get_indent_spaces(), 0)
131 131 self.assertEqual(isp.source, '')
132 132 self.assertEqual(isp.code, None)
133 133 self.assertEqual(isp._is_complete, False)
134 134
135 135 def test_source(self):
136 136 self.isp._store('1')
137 137 self.isp._store('2')
138 138 self.assertEqual(self.isp.source, '1\n2\n')
139 139 self.assertEqual(len(self.isp._buffer)>0, True)
140 140 self.assertEqual(self.isp.source_reset(), '1\n2\n')
141 141 self.assertEqual(self.isp._buffer, [])
142 142 self.assertEqual(self.isp.source, '')
143 143
144 144 def test_indent(self):
145 145 isp = self.isp # shorthand
146 146 isp.push('x=1')
147 147 self.assertEqual(isp.get_indent_spaces(), 0)
148 148 isp.push('if 1:\n x=1')
149 149 self.assertEqual(isp.get_indent_spaces(), 4)
150 150 isp.push('y=2\n')
151 151 self.assertEqual(isp.get_indent_spaces(), 0)
152 152
153 153 def test_indent2(self):
154 154 isp = self.isp
155 155 isp.push('if 1:')
156 156 self.assertEqual(isp.get_indent_spaces(), 4)
157 157 isp.push(' x=1')
158 158 self.assertEqual(isp.get_indent_spaces(), 4)
159 159 # Blank lines shouldn't change the indent level
160 160 isp.push(' '*2)
161 161 self.assertEqual(isp.get_indent_spaces(), 4)
162 162
163 163 def test_indent3(self):
164 164 isp = self.isp
165 165 # When a multiline statement contains parens or multiline strings, we
166 166 # shouldn't get confused.
167 167 isp.push("if 1:")
168 168 isp.push(" x = (1+\n 2)")
169 169 self.assertEqual(isp.get_indent_spaces(), 4)
170 170
171 171 def test_indent4(self):
172 172 isp = self.isp
173 173 # whitespace after ':' should not screw up indent level
174 174 isp.push('if 1: \n x=1')
175 175 self.assertEqual(isp.get_indent_spaces(), 4)
176 176 isp.push('y=2\n')
177 177 self.assertEqual(isp.get_indent_spaces(), 0)
178 178 isp.push('if 1:\t\n x=1')
179 179 self.assertEqual(isp.get_indent_spaces(), 4)
180 180 isp.push('y=2\n')
181 181 self.assertEqual(isp.get_indent_spaces(), 0)
182 182
183 183 def test_dedent_pass(self):
184 184 isp = self.isp # shorthand
185 185 # should NOT cause dedent
186 186 isp.push('if 1:\n passes = 5')
187 187 self.assertEqual(isp.get_indent_spaces(), 4)
188 188 isp.push('if 1:\n pass')
189 189 self.assertEqual(isp.get_indent_spaces(), 0)
190 190 isp.push('if 1:\n pass ')
191 191 self.assertEqual(isp.get_indent_spaces(), 0)
192 192
193 193 def test_dedent_break(self):
194 194 isp = self.isp # shorthand
195 195 # should NOT cause dedent
196 196 isp.push('while 1:\n breaks = 5')
197 197 self.assertEqual(isp.get_indent_spaces(), 4)
198 198 isp.push('while 1:\n break')
199 199 self.assertEqual(isp.get_indent_spaces(), 0)
200 200 isp.push('while 1:\n break ')
201 201 self.assertEqual(isp.get_indent_spaces(), 0)
202 202
203 203 def test_dedent_continue(self):
204 204 isp = self.isp # shorthand
205 205 # should NOT cause dedent
206 206 isp.push('while 1:\n continues = 5')
207 207 self.assertEqual(isp.get_indent_spaces(), 4)
208 208 isp.push('while 1:\n continue')
209 209 self.assertEqual(isp.get_indent_spaces(), 0)
210 210 isp.push('while 1:\n continue ')
211 211 self.assertEqual(isp.get_indent_spaces(), 0)
212 212
213 213 def test_dedent_raise(self):
214 214 isp = self.isp # shorthand
215 215 # should NOT cause dedent
216 216 isp.push('if 1:\n raised = 4')
217 217 self.assertEqual(isp.get_indent_spaces(), 4)
218 218 isp.push('if 1:\n raise TypeError()')
219 219 self.assertEqual(isp.get_indent_spaces(), 0)
220 220 isp.push('if 1:\n raise')
221 221 self.assertEqual(isp.get_indent_spaces(), 0)
222 222 isp.push('if 1:\n raise ')
223 223 self.assertEqual(isp.get_indent_spaces(), 0)
224 224
225 225 def test_dedent_return(self):
226 226 isp = self.isp # shorthand
227 227 # should NOT cause dedent
228 228 isp.push('if 1:\n returning = 4')
229 229 self.assertEqual(isp.get_indent_spaces(), 4)
230 230 isp.push('if 1:\n return 5 + 493')
231 231 self.assertEqual(isp.get_indent_spaces(), 0)
232 232 isp.push('if 1:\n return')
233 233 self.assertEqual(isp.get_indent_spaces(), 0)
234 234 isp.push('if 1:\n return ')
235 235 self.assertEqual(isp.get_indent_spaces(), 0)
236 236 isp.push('if 1:\n return(0)')
237 237 self.assertEqual(isp.get_indent_spaces(), 0)
238 238
239 239 def test_push(self):
240 240 isp = self.isp
241 241 self.assertEqual(isp.push('x=1'), True)
242 242
243 243 def test_push2(self):
244 244 isp = self.isp
245 245 self.assertEqual(isp.push('if 1:'), False)
246 246 for line in [' x=1', '# a comment', ' y=2']:
247 247 print(line)
248 248 self.assertEqual(isp.push(line), True)
249 249
250 250 def test_push3(self):
251 251 isp = self.isp
252 252 isp.push('if True:')
253 253 isp.push(' a = 1')
254 254 self.assertEqual(isp.push('b = [1,'), False)
255 255
256 256 def test_push_accepts_more(self):
257 257 isp = self.isp
258 258 isp.push('x=1')
259 259 self.assertEqual(isp.push_accepts_more(), False)
260 260
261 261 def test_push_accepts_more2(self):
262 262 isp = self.isp
263 263 isp.push('if 1:')
264 264 self.assertEqual(isp.push_accepts_more(), True)
265 265 isp.push(' x=1')
266 266 self.assertEqual(isp.push_accepts_more(), True)
267 267 isp.push('')
268 268 self.assertEqual(isp.push_accepts_more(), False)
269 269
270 270 def test_push_accepts_more3(self):
271 271 isp = self.isp
272 272 isp.push("x = (2+\n3)")
273 273 self.assertEqual(isp.push_accepts_more(), False)
274 274
275 275 def test_push_accepts_more4(self):
276 276 isp = self.isp
277 277 # When a multiline statement contains parens or multiline strings, we
278 278 # shouldn't get confused.
279 279 # FIXME: we should be able to better handle de-dents in statements like
280 280 # multiline strings and multiline expressions (continued with \ or
281 281 # parens). Right now we aren't handling the indentation tracking quite
282 282 # correctly with this, though in practice it may not be too much of a
283 283 # problem. We'll need to see.
284 284 isp.push("if 1:")
285 285 isp.push(" x = (2+")
286 286 isp.push(" 3)")
287 287 self.assertEqual(isp.push_accepts_more(), True)
288 288 isp.push(" y = 3")
289 289 self.assertEqual(isp.push_accepts_more(), True)
290 290 isp.push('')
291 291 self.assertEqual(isp.push_accepts_more(), False)
292 292
293 293 def test_push_accepts_more5(self):
294 294 isp = self.isp
295 295 isp.push('try:')
296 296 isp.push(' a = 5')
297 297 isp.push('except:')
298 298 isp.push(' raise')
299 299 # We want to be able to add an else: block at this point, so it should
300 300 # wait for a blank line.
301 301 self.assertEqual(isp.push_accepts_more(), True)
302 302
303 303 def test_continuation(self):
304 304 isp = self.isp
305 305 isp.push("import os, \\")
306 306 self.assertEqual(isp.push_accepts_more(), True)
307 307 isp.push("sys")
308 308 self.assertEqual(isp.push_accepts_more(), False)
309 309
310 310 def test_syntax_error(self):
311 311 isp = self.isp
312 312 # Syntax errors immediately produce a 'ready' block, so the invalid
313 313 # Python can be sent to the kernel for evaluation with possible ipython
314 314 # special-syntax conversion.
315 315 isp.push('run foo')
316 316 self.assertEqual(isp.push_accepts_more(), False)
317 317
318 318 def test_unicode(self):
319 319 self.isp.push(u"Pérez")
320 320 self.isp.push(u'\xc3\xa9')
321 321 self.isp.push(u"u'\xc3\xa9'")
322 322
323 323 @pytest.mark.xfail(
324 324 reason="Bug in python 3.9.8 – bpo 45738",
325 condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
325 condition=sys.version_info in [(3, 11, 0, "alpha", 2)],
326 326 raises=SystemError,
327 327 strict=True,
328 328 )
329 329 def test_line_continuation(self):
330 330 """ Test issue #2108."""
331 331 isp = self.isp
332 332 # A blank line after a line continuation should not accept more
333 333 isp.push("1 \\\n\n")
334 334 self.assertEqual(isp.push_accepts_more(), False)
335 335 # Whitespace after a \ is a SyntaxError. The only way to test that
336 336 # here is to test that push doesn't accept more (as with
337 337 # test_syntax_error() above).
338 338 isp.push(r"1 \ ")
339 339 self.assertEqual(isp.push_accepts_more(), False)
340 340 # Even if the line is continuable (c.f. the regular Python
341 341 # interpreter)
342 342 isp.push(r"(1 \ ")
343 343 self.assertEqual(isp.push_accepts_more(), False)
344 344
345 345 def test_check_complete(self):
346 346 isp = self.isp
347 347 self.assertEqual(isp.check_complete("a = 1"), ('complete', None))
348 348 self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4))
349 349 self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None))
350 350 self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0))
351 351 self.assertEqual(isp.check_complete("def a():\n x=1\n global x"), ('invalid', None))
352 352
353 353 class InteractiveLoopTestCase(unittest.TestCase):
354 354 """Tests for an interactive loop like a python shell.
355 355 """
356 356 def check_ns(self, lines, ns):
357 357 """Validate that the given input lines produce the resulting namespace.
358 358
359 359 Note: the input lines are given exactly as they would be typed in an
360 360 auto-indenting environment, as mini_interactive_loop above already does
361 361 auto-indenting and prepends spaces to the input.
362 362 """
363 363 src = mini_interactive_loop(pseudo_input(lines))
364 364 test_ns = {}
365 365 exec(src, test_ns)
366 366 # We can't check that the provided ns is identical to the test_ns,
367 367 # because Python fills test_ns with extra keys (copyright, etc). But
368 368 # we can check that the given dict is *contained* in test_ns
369 369 for k,v in ns.items():
370 370 self.assertEqual(test_ns[k], v)
371 371
372 372 def test_simple(self):
373 373 self.check_ns(['x=1'], dict(x=1))
374 374
375 375 def test_simple2(self):
376 376 self.check_ns(['if 1:', 'x=2'], dict(x=2))
377 377
378 378 def test_xy(self):
379 379 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
380 380
381 381 def test_abc(self):
382 382 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
383 383
384 384 def test_multi(self):
385 385 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
386 386
387 387
388 388 class IPythonInputTestCase(InputSplitterTestCase):
389 389 """By just creating a new class whose .isp is a different instance, we
390 390 re-run the same test battery on the new input splitter.
391 391
392 392 In addition, this runs the tests over the syntax and syntax_ml dicts that
393 393 were tested by individual functions, as part of the OO interface.
394 394
395 395 It also makes some checks on the raw buffer storage.
396 396 """
397 397
398 398 def setUp(self):
399 399 self.isp = isp.IPythonInputSplitter()
400 400
401 401 def test_syntax(self):
402 402 """Call all single-line syntax tests from the main object"""
403 403 isp = self.isp
404 404 for example in syntax.values():
405 405 for raw, out_t in example:
406 406 if raw.startswith(' '):
407 407 continue
408 408
409 409 isp.push(raw+'\n')
410 410 out_raw = isp.source_raw
411 411 out = isp.source_reset()
412 412 self.assertEqual(out.rstrip(), out_t,
413 413 tt.pair_fail_msg.format("inputsplitter",raw, out_t, out))
414 414 self.assertEqual(out_raw.rstrip(), raw.rstrip())
415 415
416 416 def test_syntax_multiline(self):
417 417 isp = self.isp
418 418 for example in syntax_ml.values():
419 419 for line_pairs in example:
420 420 out_t_parts = []
421 421 raw_parts = []
422 422 for lraw, out_t_part in line_pairs:
423 423 if out_t_part is not None:
424 424 out_t_parts.append(out_t_part)
425 425
426 426 if lraw is not None:
427 427 isp.push(lraw)
428 428 raw_parts.append(lraw)
429 429
430 430 out_raw = isp.source_raw
431 431 out = isp.source_reset()
432 432 out_t = '\n'.join(out_t_parts).rstrip()
433 433 raw = '\n'.join(raw_parts).rstrip()
434 434 self.assertEqual(out.rstrip(), out_t)
435 435 self.assertEqual(out_raw.rstrip(), raw)
436 436
437 437 def test_syntax_multiline_cell(self):
438 438 isp = self.isp
439 439 for example in syntax_ml.values():
440 440
441 441 out_t_parts = []
442 442 for line_pairs in example:
443 443 raw = '\n'.join(r for r, _ in line_pairs if r is not None)
444 444 out_t = '\n'.join(t for _,t in line_pairs if t is not None)
445 445 out = isp.transform_cell(raw)
446 446 # Match ignoring trailing whitespace
447 447 self.assertEqual(out.rstrip(), out_t.rstrip())
448 448
449 449 def test_cellmagic_preempt(self):
450 450 isp = self.isp
451 451 for raw, name, line, cell in [
452 452 ("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'),
453 453 ("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'),
454 454 (">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'),
455 455 ("%%cellm \n>>> hi", u'cellm', u'', u'>>> hi'),
456 456 ("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'),
457 457 ("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'),
458 458 ]:
459 459 expected = "get_ipython().run_cell_magic(%r, %r, %r)" % (
460 460 name, line, cell
461 461 )
462 462 out = isp.transform_cell(raw)
463 463 self.assertEqual(out.rstrip(), expected.rstrip())
464 464
465 465 def test_multiline_passthrough(self):
466 466 isp = self.isp
467 467 class CommentTransformer(InputTransformer):
468 468 def __init__(self):
469 469 self._lines = []
470 470
471 471 def push(self, line):
472 472 self._lines.append(line + '#')
473 473
474 474 def reset(self):
475 475 text = '\n'.join(self._lines)
476 476 self._lines = []
477 477 return text
478 478
479 479 isp.physical_line_transforms.insert(0, CommentTransformer())
480 480
481 481 for raw, expected in [
482 482 ("a=5", "a=5#"),
483 483 ("%ls foo", "get_ipython().run_line_magic(%r, %r)" % (u'ls', u'foo#')),
484 484 ("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().run_line_magic(%r, %r)" % (
485 485 u'ls foo#', u'ls', u'bar#'
486 486 )),
487 487 ("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().run_line_magic(%r, %r)\n4#\n5#" % (u'ls', u'foo#')),
488 488 ]:
489 489 out = isp.transform_cell(raw)
490 490 self.assertEqual(out.rstrip(), expected.rstrip())
491 491
492 492 #-----------------------------------------------------------------------------
493 493 # Main - use as a script, mostly for developer experiments
494 494 #-----------------------------------------------------------------------------
495 495
496 496 if __name__ == '__main__':
497 497 # A simple demo for interactive experimentation. This code will not get
498 498 # picked up by any test suite.
499 499 from IPython.core.inputsplitter import IPythonInputSplitter
500 500
501 501 # configure here the syntax to use, prompt and whether to autoindent
502 502 #isp, start_prompt = InputSplitter(), '>>> '
503 503 isp, start_prompt = IPythonInputSplitter(), 'In> '
504 504
505 505 autoindent = True
506 506 #autoindent = False
507 507
508 508 try:
509 509 while True:
510 510 prompt = start_prompt
511 511 while isp.push_accepts_more():
512 512 indent = ' '*isp.get_indent_spaces()
513 513 if autoindent:
514 514 line = indent + input(prompt+indent)
515 515 else:
516 516 line = input(prompt)
517 517 isp.push(line)
518 518 prompt = '... '
519 519
520 520 # Here we just return input so we can use it in a test suite, but a
521 521 # real interpreter would instead send it for execution somewhere.
522 522 #src = isp.source; raise EOFError # dbg
523 523 raw = isp.source_raw
524 524 src = isp.source_reset()
525 525 print('Input source was:\n', src)
526 526 print('Raw source was:\n', raw)
527 527 except EOFError:
528 528 print('Bye')
529 529
530 530 # Tests for cell magics support
531 531
532 532 def test_last_blank():
533 533 assert isp.last_blank("") is False
534 534 assert isp.last_blank("abc") is False
535 535 assert isp.last_blank("abc\n") is False
536 536 assert isp.last_blank("abc\na") is False
537 537
538 538 assert isp.last_blank("\n") is True
539 539 assert isp.last_blank("\n ") is True
540 540 assert isp.last_blank("abc\n ") is True
541 541 assert isp.last_blank("abc\n\n") is True
542 542 assert isp.last_blank("abc\nd\n\n") is True
543 543 assert isp.last_blank("abc\nd\ne\n\n") is True
544 544 assert isp.last_blank("abc \n \n \n\n") is True
545 545
546 546
547 547 def test_last_two_blanks():
548 548 assert isp.last_two_blanks("") is False
549 549 assert isp.last_two_blanks("abc") is False
550 550 assert isp.last_two_blanks("abc\n") is False
551 551 assert isp.last_two_blanks("abc\n\na") is False
552 552 assert isp.last_two_blanks("abc\n \n") is False
553 553 assert isp.last_two_blanks("abc\n\n") is False
554 554
555 555 assert isp.last_two_blanks("\n\n") is True
556 556 assert isp.last_two_blanks("\n\n ") is True
557 557 assert isp.last_two_blanks("\n \n") is True
558 558 assert isp.last_two_blanks("abc\n\n ") is True
559 559 assert isp.last_two_blanks("abc\n\n\n") is True
560 560 assert isp.last_two_blanks("abc\n\n \n") is True
561 561 assert isp.last_two_blanks("abc\n\n \n ") is True
562 562 assert isp.last_two_blanks("abc\n\n \n \n") is True
563 563 assert isp.last_two_blanks("abc\nd\n\n\n") is True
564 564 assert isp.last_two_blanks("abc\nd\ne\nf\n\n\n") is True
565 565
566 566
567 567 class CellMagicsCommon(object):
568 568
569 569 def test_whole_cell(self):
570 570 src = "%%cellm line\nbody\n"
571 571 out = self.sp.transform_cell(src)
572 572 ref = "get_ipython().run_cell_magic('cellm', 'line', 'body')\n"
573 573 assert out == ref
574 574
575 575 def test_cellmagic_help(self):
576 576 self.sp.push('%%cellm?')
577 577 assert self.sp.push_accepts_more() is False
578 578
579 579 def tearDown(self):
580 580 self.sp.reset()
581 581
582 582
583 583 class CellModeCellMagics(CellMagicsCommon, unittest.TestCase):
584 584 sp = isp.IPythonInputSplitter(line_input_checker=False)
585 585
586 586 def test_incremental(self):
587 587 sp = self.sp
588 588 sp.push("%%cellm firstline\n")
589 589 assert sp.push_accepts_more() is True # 1
590 590 sp.push("line2\n")
591 591 assert sp.push_accepts_more() is True # 2
592 592 sp.push("\n")
593 593 # This should accept a blank line and carry on until the cell is reset
594 594 assert sp.push_accepts_more() is True # 3
595 595
596 596 def test_no_strip_coding(self):
597 597 src = '\n'.join([
598 598 '%%writefile foo.py',
599 599 '# coding: utf-8',
600 600 'print(u"üñîçø∂é")',
601 601 ])
602 602 out = self.sp.transform_cell(src)
603 603 assert "# coding: utf-8" in out
604 604
605 605
606 606 class LineModeCellMagics(CellMagicsCommon, unittest.TestCase):
607 607 sp = isp.IPythonInputSplitter(line_input_checker=True)
608 608
609 609 def test_incremental(self):
610 610 sp = self.sp
611 611 sp.push("%%cellm line2\n")
612 612 assert sp.push_accepts_more() is True # 1
613 613 sp.push("\n")
614 614 # In this case, a blank line should end the cell magic
615 615 assert sp.push_accepts_more() is False # 2
616 616
617 617
618 618 indentation_samples = [
619 619 ('a = 1', 0),
620 620 ('for a in b:', 4),
621 621 ('def f():', 4),
622 622 ('def f(): #comment', 4),
623 623 ('a = ":#not a comment"', 0),
624 624 ('def f():\n a = 1', 4),
625 625 ('def f():\n return 1', 0),
626 626 ('for a in b:\n'
627 627 ' if a < 0:'
628 628 ' continue', 3),
629 629 ('a = {', 4),
630 630 ('a = {\n'
631 631 ' 1,', 5),
632 632 ('b = """123', 0),
633 633 ('', 0),
634 634 ('def f():\n pass', 0),
635 635 ('class Bar:\n def f():\n pass', 4),
636 636 ('class Bar:\n def f():\n raise', 4),
637 637 ]
638 638
639 639 def test_find_next_indent():
640 640 for code, exp in indentation_samples:
641 641 res = isp.find_next_indent(code)
642 642 msg = "{!r} != {!r} (expected)\n Code: {!r}".format(res, exp, code)
643 643 assert res == exp, msg
@@ -1,448 +1,447 b''
1 1 """Tests for the token-based transformers in IPython.core.inputtransformer2
2 2
3 3 Line-based transformers are the simpler ones; token-based transformers are
4 4 more complex. See test_inputtransformer2_line for tests for line-based
5 5 transformations.
6 6 """
7 7 import platform
8 8 import string
9 9 import sys
10 10 from textwrap import dedent
11 11
12 12 import pytest
13 13
14 14 from IPython.core import inputtransformer2 as ipt2
15 15 from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line
16 16
17 17 MULTILINE_MAGIC = (
18 18 """\
19 19 a = f()
20 20 %foo \\
21 21 bar
22 22 g()
23 23 """.splitlines(
24 24 keepends=True
25 25 ),
26 26 (2, 0),
27 27 """\
28 28 a = f()
29 29 get_ipython().run_line_magic('foo', ' bar')
30 30 g()
31 31 """.splitlines(
32 32 keepends=True
33 33 ),
34 34 )
35 35
36 36 INDENTED_MAGIC = (
37 37 """\
38 38 for a in range(5):
39 39 %ls
40 40 """.splitlines(
41 41 keepends=True
42 42 ),
43 43 (2, 4),
44 44 """\
45 45 for a in range(5):
46 46 get_ipython().run_line_magic('ls', '')
47 47 """.splitlines(
48 48 keepends=True
49 49 ),
50 50 )
51 51
52 52 CRLF_MAGIC = (
53 53 ["a = f()\n", "%ls\r\n", "g()\n"],
54 54 (2, 0),
55 55 ["a = f()\n", "get_ipython().run_line_magic('ls', '')\n", "g()\n"],
56 56 )
57 57
58 58 MULTILINE_MAGIC_ASSIGN = (
59 59 """\
60 60 a = f()
61 61 b = %foo \\
62 62 bar
63 63 g()
64 64 """.splitlines(
65 65 keepends=True
66 66 ),
67 67 (2, 4),
68 68 """\
69 69 a = f()
70 70 b = get_ipython().run_line_magic('foo', ' bar')
71 71 g()
72 72 """.splitlines(
73 73 keepends=True
74 74 ),
75 75 )
76 76
77 77 MULTILINE_SYSTEM_ASSIGN = ("""\
78 78 a = f()
79 79 b = !foo \\
80 80 bar
81 81 g()
82 82 """.splitlines(keepends=True), (2, 4), """\
83 83 a = f()
84 84 b = get_ipython().getoutput('foo bar')
85 85 g()
86 86 """.splitlines(keepends=True))
87 87
88 88 #####
89 89
90 90 MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = (
91 91 """\
92 92 def test():
93 93 for i in range(1):
94 94 print(i)
95 95 res =! ls
96 96 """.splitlines(
97 97 keepends=True
98 98 ),
99 99 (4, 7),
100 100 """\
101 101 def test():
102 102 for i in range(1):
103 103 print(i)
104 104 res =get_ipython().getoutput(\' ls\')
105 105 """.splitlines(
106 106 keepends=True
107 107 ),
108 108 )
109 109
110 110 ######
111 111
112 112 AUTOCALL_QUOTE = ([",f 1 2 3\n"], (1, 0), ['f("1", "2", "3")\n'])
113 113
114 114 AUTOCALL_QUOTE2 = ([";f 1 2 3\n"], (1, 0), ['f("1 2 3")\n'])
115 115
116 116 AUTOCALL_PAREN = (["/f 1 2 3\n"], (1, 0), ["f(1, 2, 3)\n"])
117 117
118 118 SIMPLE_HELP = (["foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', 'foo')\n"])
119 119
120 120 DETAILED_HELP = (
121 121 ["foo??\n"],
122 122 (1, 0),
123 123 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"],
124 124 )
125 125
126 126 MAGIC_HELP = (["%foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', '%foo')\n"])
127 127
128 128 HELP_IN_EXPR = (
129 129 ["a = b + c?\n"],
130 130 (1, 0),
131 131 ["get_ipython().run_line_magic('pinfo', 'c')\n"],
132 132 )
133 133
134 134 HELP_CONTINUED_LINE = (
135 135 """\
136 136 a = \\
137 137 zip?
138 138 """.splitlines(
139 139 keepends=True
140 140 ),
141 141 (1, 0),
142 142 [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"],
143 143 )
144 144
145 145 HELP_MULTILINE = (
146 146 """\
147 147 (a,
148 148 b) = zip?
149 149 """.splitlines(
150 150 keepends=True
151 151 ),
152 152 (1, 0),
153 153 [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"],
154 154 )
155 155
156 156 HELP_UNICODE = (
157 157 ["π.foo?\n"],
158 158 (1, 0),
159 159 ["get_ipython().run_line_magic('pinfo', 'π.foo')\n"],
160 160 )
161 161
162 162
163 163 def null_cleanup_transformer(lines):
164 164 """
165 165 A cleanup transform that returns an empty list.
166 166 """
167 167 return []
168 168
169 169
170 170 def test_check_make_token_by_line_never_ends_empty():
171 171 """
172 172 Check that not sequence of single or double characters ends up leading to en empty list of tokens
173 173 """
174 174 from string import printable
175 175
176 176 for c in printable:
177 177 assert make_tokens_by_line(c)[-1] != []
178 178 for k in printable:
179 179 assert make_tokens_by_line(c + k)[-1] != []
180 180
181 181
182 182 def check_find(transformer, case, match=True):
183 183 sample, expected_start, _ = case
184 184 tbl = make_tokens_by_line(sample)
185 185 res = transformer.find(tbl)
186 186 if match:
187 187 # start_line is stored 0-indexed, expected values are 1-indexed
188 188 assert (res.start_line + 1, res.start_col) == expected_start
189 189 return res
190 190 else:
191 191 assert res is None
192 192
193 193
194 194 def check_transform(transformer_cls, case):
195 195 lines, start, expected = case
196 196 transformer = transformer_cls(start)
197 197 assert transformer.transform(lines) == expected
198 198
199 199
200 200 def test_continued_line():
201 201 lines = MULTILINE_MAGIC_ASSIGN[0]
202 202 assert ipt2.find_end_of_continued_line(lines, 1) == 2
203 203
204 204 assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar"
205 205
206 206
207 207 def test_find_assign_magic():
208 208 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
209 209 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
210 210 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False)
211 211
212 212
213 213 def test_transform_assign_magic():
214 214 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
215 215
216 216
217 217 def test_find_assign_system():
218 218 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
219 219 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
220 220 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
221 221 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
222 222 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
223 223
224 224
225 225 def test_transform_assign_system():
226 226 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
227 227 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
228 228
229 229
230 230 def test_find_magic_escape():
231 231 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
232 232 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
233 233 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
234 234
235 235
236 236 def test_transform_magic_escape():
237 237 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
238 238 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
239 239 check_transform(ipt2.EscapedCommand, CRLF_MAGIC)
240 240
241 241
242 242 def test_find_autocalls():
243 243 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
244 244 print("Testing %r" % case[0])
245 245 check_find(ipt2.EscapedCommand, case)
246 246
247 247
248 248 def test_transform_autocall():
249 249 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
250 250 print("Testing %r" % case[0])
251 251 check_transform(ipt2.EscapedCommand, case)
252 252
253 253
254 254 def test_find_help():
255 255 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
256 256 check_find(ipt2.HelpEnd, case)
257 257
258 258 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
259 259 assert tf.q_line == 1
260 260 assert tf.q_col == 3
261 261
262 262 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
263 263 assert tf.q_line == 1
264 264 assert tf.q_col == 8
265 265
266 266 # ? in a comment does not trigger help
267 267 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
268 268 # Nor in a string
269 269 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
270 270
271 271
272 272 def test_transform_help():
273 273 tf = ipt2.HelpEnd((1, 0), (1, 9))
274 274 assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2]
275 275
276 276 tf = ipt2.HelpEnd((1, 0), (2, 3))
277 277 assert tf.transform(HELP_CONTINUED_LINE[0]) == HELP_CONTINUED_LINE[2]
278 278
279 279 tf = ipt2.HelpEnd((1, 0), (2, 8))
280 280 assert tf.transform(HELP_MULTILINE[0]) == HELP_MULTILINE[2]
281 281
282 282 tf = ipt2.HelpEnd((1, 0), (1, 0))
283 283 assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2]
284 284
285 285
286 286 def test_find_assign_op_dedent():
287 287 """
288 288 be careful that empty token like dedent are not counted as parens
289 289 """
290 290
291 291 class Tk:
292 292 def __init__(self, s):
293 293 self.string = s
294 294
295 295 assert _find_assign_op([Tk(s) for s in ("", "a", "=", "b")]) == 2
296 296 assert (
297 297 _find_assign_op([Tk(s) for s in ("", "(", "a", "=", "b", ")", "=", "5")]) == 6
298 298 )
299 299
300 300
301 301 extra_closing_paren_param = (
302 302 pytest.param("(\n))", "invalid", None)
303 303 if sys.version_info >= (3, 12)
304 304 else pytest.param("(\n))", "incomplete", 0)
305 305 )
306 306 examples = [
307 307 pytest.param("a = 1", "complete", None),
308 308 pytest.param("for a in range(5):", "incomplete", 4),
309 309 pytest.param("for a in range(5):\n if a > 0:", "incomplete", 8),
310 310 pytest.param("raise = 2", "invalid", None),
311 311 pytest.param("a = [1,\n2,", "incomplete", 0),
312 312 extra_closing_paren_param,
313 313 pytest.param("\\\r\n", "incomplete", 0),
314 314 pytest.param("a = '''\n hi", "incomplete", 3),
315 315 pytest.param("def a():\n x=1\n global x", "invalid", None),
316 316 pytest.param(
317 317 "a \\ ",
318 318 "invalid",
319 319 None,
320 320 marks=pytest.mark.xfail(
321 321 reason="Bug in python 3.9.8 – bpo 45738",
322 condition=sys.version_info
323 in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
322 condition=sys.version_info in [(3, 11, 0, "alpha", 2)],
324 323 raises=SystemError,
325 324 strict=True,
326 325 ),
327 326 ), # Nothing allowed after backslash,
328 327 pytest.param("1\\\n+2", "complete", None),
329 328 ]
330 329
331 330
332 331 @pytest.mark.parametrize("code, expected, number", examples)
333 332 def test_check_complete_param(code, expected, number):
334 333 cc = ipt2.TransformerManager().check_complete
335 334 assert cc(code) == (expected, number)
336 335
337 336
338 337 @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy")
339 338 @pytest.mark.xfail(
340 339 reason="Bug in python 3.9.8 – bpo 45738",
341 condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
340 condition=sys.version_info in [(3, 11, 0, "alpha", 2)],
342 341 raises=SystemError,
343 342 strict=True,
344 343 )
345 344 def test_check_complete():
346 345 cc = ipt2.TransformerManager().check_complete
347 346
348 347 example = dedent(
349 348 """
350 349 if True:
351 350 a=1"""
352 351 )
353 352
354 353 assert cc(example) == ("incomplete", 4)
355 354 assert cc(example + "\n") == ("complete", None)
356 355 assert cc(example + "\n ") == ("complete", None)
357 356
358 357 # no need to loop on all the letters/numbers.
359 358 short = "12abAB" + string.printable[62:]
360 359 for c in short:
361 360 # test does not raise:
362 361 cc(c)
363 362 for k in short:
364 363 cc(c + k)
365 364
366 365 assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2)
367 366
368 367
369 368 @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy")
370 369 @pytest.mark.parametrize(
371 370 "value, expected",
372 371 [
373 372 ('''def foo():\n """''', ("incomplete", 4)),
374 373 ("""async with example:\n pass""", ("incomplete", 4)),
375 374 ("""async with example:\n pass\n """, ("complete", None)),
376 375 ],
377 376 )
378 377 def test_check_complete_II(value, expected):
379 378 """
380 379 Test that multiple line strings are properly handled.
381 380
382 381 Separate test function for convenience
383 382
384 383 """
385 384 cc = ipt2.TransformerManager().check_complete
386 385 assert cc(value) == expected
387 386
388 387
389 388 @pytest.mark.parametrize(
390 389 "value, expected",
391 390 [
392 391 (")", ("invalid", None)),
393 392 ("]", ("invalid", None)),
394 393 ("}", ("invalid", None)),
395 394 (")(", ("invalid", None)),
396 395 ("][", ("invalid", None)),
397 396 ("}{", ("invalid", None)),
398 397 ("]()(", ("invalid", None)),
399 398 ("())(", ("invalid", None)),
400 399 (")[](", ("invalid", None)),
401 400 ("()](", ("invalid", None)),
402 401 ],
403 402 )
404 403 def test_check_complete_invalidates_sunken_brackets(value, expected):
405 404 """
406 405 Test that a single line with more closing brackets than the opening ones is
407 406 interpreted as invalid
408 407 """
409 408 cc = ipt2.TransformerManager().check_complete
410 409 assert cc(value) == expected
411 410
412 411
413 412 def test_null_cleanup_transformer():
414 413 manager = ipt2.TransformerManager()
415 414 manager.cleanup_transforms.insert(0, null_cleanup_transformer)
416 415 assert manager.transform_cell("") == ""
417 416
418 417
419 418 def test_side_effects_I():
420 419 count = 0
421 420
422 421 def counter(lines):
423 422 nonlocal count
424 423 count += 1
425 424 return lines
426 425
427 426 counter.has_side_effects = True
428 427
429 428 manager = ipt2.TransformerManager()
430 429 manager.cleanup_transforms.insert(0, counter)
431 430 assert manager.check_complete("a=1\n") == ("complete", None)
432 431 assert count == 0
433 432
434 433
435 434 def test_side_effects_II():
436 435 count = 0
437 436
438 437 def counter(lines):
439 438 nonlocal count
440 439 count += 1
441 440 return lines
442 441
443 442 counter.has_side_effects = True
444 443
445 444 manager = ipt2.TransformerManager()
446 445 manager.line_transforms.insert(0, counter)
447 446 assert manager.check_complete("b=1\n") == ("complete", None)
448 447 assert count == 0
@@ -1,579 +1,579 b''
1 1 """Tests for the object inspection functionality.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from contextlib import contextmanager
9 9 from inspect import signature, Signature, Parameter
10 10 import inspect
11 11 import os
12 12 import pytest
13 13 import re
14 14 import sys
15 15
16 16 from .. import oinspect
17 17
18 18 from decorator import decorator
19 19
20 20 from IPython.testing.tools import AssertPrints, AssertNotPrints
21 21 from IPython.utils.path import compress_user
22 22
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Globals and constants
26 26 #-----------------------------------------------------------------------------
27 27
28 28 inspector = None
29 29
30 30 def setup_module():
31 31 global inspector
32 32 inspector = oinspect.Inspector()
33 33
34 34
35 35 class SourceModuleMainTest:
36 36 __module__ = "__main__"
37 37
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Local utilities
41 41 #-----------------------------------------------------------------------------
42 42
43 43 # WARNING: since this test checks the line number where a function is
44 44 # defined, if any code is inserted above, the following line will need to be
45 45 # updated. Do NOT insert any whitespace between the next line and the function
46 46 # definition below.
47 47 THIS_LINE_NUMBER = 47 # Put here the actual number of this line
48 48
49 49
50 50 def test_find_source_lines():
51 51 assert oinspect.find_source_lines(test_find_source_lines) == THIS_LINE_NUMBER + 3
52 52 assert oinspect.find_source_lines(type) is None
53 53 assert oinspect.find_source_lines(SourceModuleMainTest) is None
54 54 assert oinspect.find_source_lines(SourceModuleMainTest()) is None
55 55
56 56
57 57 def test_getsource():
58 58 assert oinspect.getsource(type) is None
59 59 assert oinspect.getsource(SourceModuleMainTest) is None
60 60 assert oinspect.getsource(SourceModuleMainTest()) is None
61 61
62 62
63 63 def test_inspect_getfile_raises_exception():
64 64 """Check oinspect.find_file/getsource/find_source_lines expectations"""
65 65 with pytest.raises(TypeError):
66 66 inspect.getfile(type)
67 with pytest.raises(OSError if sys.version_info >= (3, 10) else TypeError):
67 with pytest.raises(OSError):
68 68 inspect.getfile(SourceModuleMainTest)
69 69
70 70
71 71 # A couple of utilities to ensure these tests work the same from a source or a
72 72 # binary install
73 73 def pyfile(fname):
74 74 return os.path.normcase(re.sub('.py[co]$', '.py', fname))
75 75
76 76
77 77 def match_pyfiles(f1, f2):
78 78 assert pyfile(f1) == pyfile(f2)
79 79
80 80
81 81 def test_find_file():
82 82 match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
83 83 assert oinspect.find_file(type) is None
84 84 assert oinspect.find_file(SourceModuleMainTest) is None
85 85 assert oinspect.find_file(SourceModuleMainTest()) is None
86 86
87 87
88 88 def test_find_file_decorated1():
89 89
90 90 @decorator
91 91 def noop1(f):
92 92 def wrapper(*a, **kw):
93 93 return f(*a, **kw)
94 94 return wrapper
95 95
96 96 @noop1
97 97 def f(x):
98 98 "My docstring"
99 99
100 100 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
101 101 assert f.__doc__ == "My docstring"
102 102
103 103
104 104 def test_find_file_decorated2():
105 105
106 106 @decorator
107 107 def noop2(f, *a, **kw):
108 108 return f(*a, **kw)
109 109
110 110 @noop2
111 111 @noop2
112 112 @noop2
113 113 def f(x):
114 114 "My docstring 2"
115 115
116 116 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
117 117 assert f.__doc__ == "My docstring 2"
118 118
119 119
120 120 def test_find_file_magic():
121 121 run = ip.find_line_magic('run')
122 122 assert oinspect.find_file(run) is not None
123 123
124 124
125 125 # A few generic objects we can then inspect in the tests below
126 126
127 127 class Call(object):
128 128 """This is the class docstring."""
129 129
130 130 def __init__(self, x, y=1):
131 131 """This is the constructor docstring."""
132 132
133 133 def __call__(self, *a, **kw):
134 134 """This is the call docstring."""
135 135
136 136 def method(self, x, z=2):
137 137 """Some method's docstring"""
138 138
139 139 class HasSignature(object):
140 140 """This is the class docstring."""
141 141 __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
142 142
143 143 def __init__(self, *args):
144 144 """This is the init docstring"""
145 145
146 146
147 147 class SimpleClass(object):
148 148 def method(self, x, z=2):
149 149 """Some method's docstring"""
150 150
151 151
152 152 class Awkward(object):
153 153 def __getattr__(self, name):
154 154 raise Exception(name)
155 155
156 156 class NoBoolCall:
157 157 """
158 158 callable with `__bool__` raising should still be inspect-able.
159 159 """
160 160
161 161 def __call__(self):
162 162 """does nothing"""
163 163 pass
164 164
165 165 def __bool__(self):
166 166 """just raise NotImplemented"""
167 167 raise NotImplementedError('Must be implemented')
168 168
169 169
170 170 class SerialLiar(object):
171 171 """Attribute accesses always get another copy of the same class.
172 172
173 173 unittest.mock.call does something similar, but it's not ideal for testing
174 174 as the failure mode is to eat all your RAM. This gives up after 10k levels.
175 175 """
176 176 def __init__(self, max_fibbing_twig, lies_told=0):
177 177 if lies_told > 10000:
178 178 raise RuntimeError('Nose too long, honesty is the best policy')
179 179 self.max_fibbing_twig = max_fibbing_twig
180 180 self.lies_told = lies_told
181 181 max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told)
182 182
183 183 def __getattr__(self, item):
184 184 return SerialLiar(self.max_fibbing_twig, self.lies_told + 1)
185 185
186 186 #-----------------------------------------------------------------------------
187 187 # Tests
188 188 #-----------------------------------------------------------------------------
189 189
190 190 def test_info():
191 191 "Check that Inspector.info fills out various fields as expected."
192 192 i = inspector.info(Call, oname="Call")
193 193 assert i["type_name"] == "type"
194 194 expected_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
195 195 assert i["base_class"] == expected_class
196 196 assert re.search(
197 197 "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>",
198 198 i["string_form"],
199 199 )
200 200 fname = __file__
201 201 if fname.endswith(".pyc"):
202 202 fname = fname[:-1]
203 203 # case-insensitive comparison needed on some filesystems
204 204 # e.g. Windows:
205 205 assert i["file"].lower() == compress_user(fname).lower()
206 206 assert i["definition"] == None
207 207 assert i["docstring"] == Call.__doc__
208 208 assert i["source"] == None
209 209 assert i["isclass"] is True
210 210 assert i["init_definition"] == "Call(x, y=1)"
211 211 assert i["init_docstring"] == Call.__init__.__doc__
212 212
213 213 i = inspector.info(Call, detail_level=1)
214 214 assert i["source"] is not None
215 215 assert i["docstring"] == None
216 216
217 217 c = Call(1)
218 218 c.__doc__ = "Modified instance docstring"
219 219 i = inspector.info(c)
220 220 assert i["type_name"] == "Call"
221 221 assert i["docstring"] == "Modified instance docstring"
222 222 assert i["class_docstring"] == Call.__doc__
223 223 assert i["init_docstring"] == Call.__init__.__doc__
224 224 assert i["call_docstring"] == Call.__call__.__doc__
225 225
226 226
227 227 def test_class_signature():
228 228 info = inspector.info(HasSignature, "HasSignature")
229 229 assert info["init_definition"] == "HasSignature(test)"
230 230 assert info["init_docstring"] == HasSignature.__init__.__doc__
231 231
232 232
233 233 def test_info_awkward():
234 234 # Just test that this doesn't throw an error.
235 235 inspector.info(Awkward())
236 236
237 237 def test_bool_raise():
238 238 inspector.info(NoBoolCall())
239 239
240 240 def test_info_serialliar():
241 241 fib_tracker = [0]
242 242 inspector.info(SerialLiar(fib_tracker))
243 243
244 244 # Nested attribute access should be cut off at 100 levels deep to avoid
245 245 # infinite loops: https://github.com/ipython/ipython/issues/9122
246 246 assert fib_tracker[0] < 9000
247 247
248 248 def support_function_one(x, y=2, *a, **kw):
249 249 """A simple function."""
250 250
251 251 def test_calldef_none():
252 252 # We should ignore __call__ for all of these.
253 253 for obj in [support_function_one, SimpleClass().method, any, str.upper]:
254 254 i = inspector.info(obj)
255 255 assert i["call_def"] is None
256 256
257 257
258 258 def f_kwarg(pos, *, kwonly):
259 259 pass
260 260
261 261 def test_definition_kwonlyargs():
262 262 i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore
263 263 assert i["definition"] == "f_kwarg(pos, *, kwonly)"
264 264
265 265
266 266 def test_getdoc():
267 267 class A(object):
268 268 """standard docstring"""
269 269 pass
270 270
271 271 class B(object):
272 272 """standard docstring"""
273 273 def getdoc(self):
274 274 return "custom docstring"
275 275
276 276 class C(object):
277 277 """standard docstring"""
278 278 def getdoc(self):
279 279 return None
280 280
281 281 a = A()
282 282 b = B()
283 283 c = C()
284 284
285 285 assert oinspect.getdoc(a) == "standard docstring"
286 286 assert oinspect.getdoc(b) == "custom docstring"
287 287 assert oinspect.getdoc(c) == "standard docstring"
288 288
289 289
290 290 def test_empty_property_has_no_source():
291 291 i = inspector.info(property(), detail_level=1)
292 292 assert i["source"] is None
293 293
294 294
295 295 def test_property_sources():
296 296 # A simple adder whose source and signature stays
297 297 # the same across Python distributions
298 298 def simple_add(a, b):
299 299 "Adds two numbers"
300 300 return a + b
301 301
302 302 class A(object):
303 303 @property
304 304 def foo(self):
305 305 return 'bar'
306 306
307 307 foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
308 308
309 309 dname = property(oinspect.getdoc)
310 310 adder = property(simple_add)
311 311
312 312 i = inspector.info(A.foo, detail_level=1)
313 313 assert "def foo(self):" in i["source"]
314 314 assert "lambda self, v:" in i["source"]
315 315
316 316 i = inspector.info(A.dname, detail_level=1)
317 317 assert "def getdoc(obj)" in i["source"]
318 318
319 319 i = inspector.info(A.adder, detail_level=1)
320 320 assert "def simple_add(a, b)" in i["source"]
321 321
322 322
323 323 def test_property_docstring_is_in_info_for_detail_level_0():
324 324 class A(object):
325 325 @property
326 326 def foobar(self):
327 327 """This is `foobar` property."""
328 328 pass
329 329
330 330 ip.user_ns["a_obj"] = A()
331 331 assert (
332 332 "This is `foobar` property."
333 333 == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"]
334 334 )
335 335
336 336 ip.user_ns["a_cls"] = A
337 337 assert (
338 338 "This is `foobar` property."
339 339 == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"]
340 340 )
341 341
342 342
343 343 def test_pdef():
344 344 # See gh-1914
345 345 def foo(): pass
346 346 inspector.pdef(foo, 'foo')
347 347
348 348
349 349 @contextmanager
350 350 def cleanup_user_ns(**kwargs):
351 351 """
352 352 On exit delete all the keys that were not in user_ns before entering.
353 353
354 354 It does not restore old values !
355 355
356 356 Parameters
357 357 ----------
358 358
359 359 **kwargs
360 360 used to update ip.user_ns
361 361
362 362 """
363 363 try:
364 364 known = set(ip.user_ns.keys())
365 365 ip.user_ns.update(kwargs)
366 366 yield
367 367 finally:
368 368 added = set(ip.user_ns.keys()) - known
369 369 for k in added:
370 370 del ip.user_ns[k]
371 371
372 372
373 373 def test_pinfo_bool_raise():
374 374 """
375 375 Test that bool method is not called on parent.
376 376 """
377 377
378 378 class RaiseBool:
379 379 attr = None
380 380
381 381 def __bool__(self):
382 382 raise ValueError("pinfo should not access this method")
383 383
384 384 raise_bool = RaiseBool()
385 385
386 386 with cleanup_user_ns(raise_bool=raise_bool):
387 387 ip._inspect("pinfo", "raise_bool.attr", detail_level=0)
388 388
389 389
390 390 def test_pinfo_getindex():
391 391 def dummy():
392 392 """
393 393 MARKER
394 394 """
395 395
396 396 container = [dummy]
397 397 with cleanup_user_ns(container=container):
398 398 with AssertPrints("MARKER"):
399 399 ip._inspect("pinfo", "container[0]", detail_level=0)
400 400 assert "container" not in ip.user_ns.keys()
401 401
402 402
403 403 def test_qmark_getindex():
404 404 def dummy():
405 405 """
406 406 MARKER 2
407 407 """
408 408
409 409 container = [dummy]
410 410 with cleanup_user_ns(container=container):
411 411 with AssertPrints("MARKER 2"):
412 412 ip.run_cell("container[0]?")
413 413 assert "container" not in ip.user_ns.keys()
414 414
415 415
416 416 def test_qmark_getindex_negatif():
417 417 def dummy():
418 418 """
419 419 MARKER 3
420 420 """
421 421
422 422 container = [dummy]
423 423 with cleanup_user_ns(container=container):
424 424 with AssertPrints("MARKER 3"):
425 425 ip.run_cell("container[-1]?")
426 426 assert "container" not in ip.user_ns.keys()
427 427
428 428
429 429
430 430 def test_pinfo_nonascii():
431 431 # See gh-1177
432 432 from . import nonascii2
433 433 ip.user_ns['nonascii2'] = nonascii2
434 434 ip._inspect('pinfo', 'nonascii2', detail_level=1)
435 435
436 436 def test_pinfo_type():
437 437 """
438 438 type can fail in various edge case, for example `type.__subclass__()`
439 439 """
440 440 ip._inspect('pinfo', 'type')
441 441
442 442
443 443 def test_pinfo_docstring_no_source():
444 444 """Docstring should be included with detail_level=1 if there is no source"""
445 445 with AssertPrints('Docstring:'):
446 446 ip._inspect('pinfo', 'str.format', detail_level=0)
447 447 with AssertPrints('Docstring:'):
448 448 ip._inspect('pinfo', 'str.format', detail_level=1)
449 449
450 450
451 451 def test_pinfo_no_docstring_if_source():
452 452 """Docstring should not be included with detail_level=1 if source is found"""
453 453 def foo():
454 454 """foo has a docstring"""
455 455
456 456 ip.user_ns['foo'] = foo
457 457
458 458 with AssertPrints('Docstring:'):
459 459 ip._inspect('pinfo', 'foo', detail_level=0)
460 460 with AssertPrints('Source:'):
461 461 ip._inspect('pinfo', 'foo', detail_level=1)
462 462 with AssertNotPrints('Docstring:'):
463 463 ip._inspect('pinfo', 'foo', detail_level=1)
464 464
465 465
466 466 def test_pinfo_docstring_if_detail_and_no_source():
467 467 """ Docstring should be displayed if source info not available """
468 468 obj_def = '''class Foo(object):
469 469 """ This is a docstring for Foo """
470 470 def bar(self):
471 471 """ This is a docstring for Foo.bar """
472 472 pass
473 473 '''
474 474
475 475 ip.run_cell(obj_def)
476 476 ip.run_cell('foo = Foo()')
477 477
478 478 with AssertNotPrints("Source:"):
479 479 with AssertPrints('Docstring:'):
480 480 ip._inspect('pinfo', 'foo', detail_level=0)
481 481 with AssertPrints('Docstring:'):
482 482 ip._inspect('pinfo', 'foo', detail_level=1)
483 483 with AssertPrints('Docstring:'):
484 484 ip._inspect('pinfo', 'foo.bar', detail_level=0)
485 485
486 486 with AssertNotPrints('Docstring:'):
487 487 with AssertPrints('Source:'):
488 488 ip._inspect('pinfo', 'foo.bar', detail_level=1)
489 489
490 490
491 491 def test_pinfo_docstring_dynamic():
492 492 obj_def = """class Bar:
493 493 __custom_documentations__ = {
494 494 "prop" : "cdoc for prop",
495 495 "non_exist" : "cdoc for non_exist",
496 496 }
497 497 @property
498 498 def prop(self):
499 499 '''
500 500 Docstring for prop
501 501 '''
502 502 return self._prop
503 503
504 504 @prop.setter
505 505 def prop(self, v):
506 506 self._prop = v
507 507 """
508 508 ip.run_cell(obj_def)
509 509
510 510 ip.run_cell("b = Bar()")
511 511
512 512 with AssertPrints("Docstring: cdoc for prop"):
513 513 ip.run_line_magic("pinfo", "b.prop")
514 514
515 515 with AssertPrints("Docstring: cdoc for non_exist"):
516 516 ip.run_line_magic("pinfo", "b.non_exist")
517 517
518 518 with AssertPrints("Docstring: cdoc for prop"):
519 519 ip.run_cell("b.prop?")
520 520
521 521 with AssertPrints("Docstring: cdoc for non_exist"):
522 522 ip.run_cell("b.non_exist?")
523 523
524 524 with AssertPrints("Docstring: <no docstring>"):
525 525 ip.run_cell("b.undefined?")
526 526
527 527
528 528 def test_pinfo_magic():
529 529 with AssertPrints("Docstring:"):
530 530 ip._inspect("pinfo", "lsmagic", detail_level=0)
531 531
532 532 with AssertPrints("Source:"):
533 533 ip._inspect("pinfo", "lsmagic", detail_level=1)
534 534
535 535
536 536 def test_init_colors():
537 537 # ensure colors are not present in signature info
538 538 info = inspector.info(HasSignature)
539 539 init_def = info["init_definition"]
540 540 assert "[0m" not in init_def
541 541
542 542
543 543 def test_builtin_init():
544 544 info = inspector.info(list)
545 545 init_def = info['init_definition']
546 546 assert init_def is not None
547 547
548 548
549 549 def test_render_signature_short():
550 550 def short_fun(a=1): pass
551 551 sig = oinspect._render_signature(
552 552 signature(short_fun),
553 553 short_fun.__name__,
554 554 )
555 555 assert sig == "short_fun(a=1)"
556 556
557 557
558 558 def test_render_signature_long():
559 559 from typing import Optional
560 560
561 561 def long_function(
562 562 a_really_long_parameter: int,
563 563 and_another_long_one: bool = False,
564 564 let_us_make_sure_this_is_looong: Optional[str] = None,
565 565 ) -> bool: pass
566 566
567 567 sig = oinspect._render_signature(
568 568 signature(long_function),
569 569 long_function.__name__,
570 570 )
571 571 expected = """\
572 572 long_function(
573 573 a_really_long_parameter: int,
574 574 and_another_long_one: bool = False,
575 575 let_us_make_sure_this_is_looong: Optional[str] = None,
576 576 ) -> bool\
577 577 """
578 578
579 579 assert sig == expected
@@ -1,540 +1,540 b''
1 1 # coding: utf-8
2 2 """Tests for IPython.lib.pretty."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from collections import Counter, defaultdict, deque, OrderedDict, UserList
9 9 import os
10 10 import pytest
11 11 import types
12 12 import string
13 13 import sys
14 14 import unittest
15 15
16 16 import pytest
17 17
18 18 from IPython.lib import pretty
19 19
20 20 from io import StringIO
21 21
22 22
23 23 class MyList(object):
24 24 def __init__(self, content):
25 25 self.content = content
26 26 def _repr_pretty_(self, p, cycle):
27 27 if cycle:
28 28 p.text("MyList(...)")
29 29 else:
30 30 with p.group(3, "MyList(", ")"):
31 31 for (i, child) in enumerate(self.content):
32 32 if i:
33 33 p.text(",")
34 34 p.breakable()
35 35 else:
36 36 p.breakable("")
37 37 p.pretty(child)
38 38
39 39
40 40 class MyDict(dict):
41 41 def _repr_pretty_(self, p, cycle):
42 42 p.text("MyDict(...)")
43 43
44 44 class MyObj(object):
45 45 def somemethod(self):
46 46 pass
47 47
48 48
49 49 class Dummy1(object):
50 50 def _repr_pretty_(self, p, cycle):
51 51 p.text("Dummy1(...)")
52 52
53 53 class Dummy2(Dummy1):
54 54 _repr_pretty_ = None
55 55
56 56 class NoModule(object):
57 57 pass
58 58
59 59 NoModule.__module__ = None
60 60
61 61 class Breaking(object):
62 62 def _repr_pretty_(self, p, cycle):
63 63 with p.group(4,"TG: ",":"):
64 64 p.text("Breaking(")
65 65 p.break_()
66 66 p.text(")")
67 67
68 68 class BreakingRepr(object):
69 69 def __repr__(self):
70 70 return "Breaking(\n)"
71 71
72 72 class BadRepr(object):
73 73 def __repr__(self):
74 74 return 1/0
75 75
76 76
77 77 def test_indentation():
78 78 """Test correct indentation in groups"""
79 79 count = 40
80 80 gotoutput = pretty.pretty(MyList(range(count)))
81 81 expectedoutput = "MyList(\n" + ",\n".join(" %d" % i for i in range(count)) + ")"
82 82
83 83 assert gotoutput == expectedoutput
84 84
85 85
86 86 def test_dispatch():
87 87 """
88 88 Test correct dispatching: The _repr_pretty_ method for MyDict
89 89 must be found before the registered printer for dict.
90 90 """
91 91 gotoutput = pretty.pretty(MyDict())
92 92 expectedoutput = "MyDict(...)"
93 93
94 94 assert gotoutput == expectedoutput
95 95
96 96
97 97 def test_callability_checking():
98 98 """
99 99 Test that the _repr_pretty_ method is tested for callability and skipped if
100 100 not.
101 101 """
102 102 gotoutput = pretty.pretty(Dummy2())
103 103 expectedoutput = "Dummy1(...)"
104 104
105 105 assert gotoutput == expectedoutput
106 106
107 107
108 108 @pytest.mark.parametrize(
109 109 "obj,expected_output",
110 110 zip(
111 111 [
112 112 set(),
113 113 frozenset(),
114 114 set([1]),
115 115 frozenset([1]),
116 116 set([1, 2]),
117 117 frozenset([1, 2]),
118 118 set([-1, -2, -3]),
119 119 ],
120 120 [
121 121 "set()",
122 122 "frozenset()",
123 123 "{1}",
124 124 "frozenset({1})",
125 125 "{1, 2}",
126 126 "frozenset({1, 2})",
127 127 "{-3, -2, -1}",
128 128 ],
129 129 ),
130 130 )
131 131 def test_sets(obj, expected_output):
132 132 """
133 133 Test that set and frozenset use Python 3 formatting.
134 134 """
135 135 got_output = pretty.pretty(obj)
136 136 assert got_output == expected_output
137 137
138 138
139 139 def test_pprint_heap_allocated_type():
140 140 """
141 141 Test that pprint works for heap allocated types.
142 142 """
143 module_name = "xxlimited" if sys.version_info < (3, 10) else "xxlimited_35"
143 module_name = "xxlimited_35"
144 144 expected_output = (
145 145 "xxlimited.Null" if sys.version_info < (3, 10, 6) else "xxlimited_35.Null"
146 146 )
147 147 xxlimited = pytest.importorskip(module_name)
148 148 output = pretty.pretty(xxlimited.Null)
149 149 assert output == expected_output
150 150
151 151
152 152 def test_pprint_nomod():
153 153 """
154 154 Test that pprint works for classes with no __module__.
155 155 """
156 156 output = pretty.pretty(NoModule)
157 157 assert output == "NoModule"
158 158
159 159
160 160 def test_pprint_break():
161 161 """
162 162 Test that p.break_ produces expected output
163 163 """
164 164 output = pretty.pretty(Breaking())
165 165 expected = "TG: Breaking(\n ):"
166 166 assert output == expected
167 167
168 168 def test_pprint_break_repr():
169 169 """
170 170 Test that p.break_ is used in repr
171 171 """
172 172 output = pretty.pretty([[BreakingRepr()]])
173 173 expected = "[[Breaking(\n )]]"
174 174 assert output == expected
175 175
176 176 output = pretty.pretty([[BreakingRepr()]*2])
177 177 expected = "[[Breaking(\n ),\n Breaking(\n )]]"
178 178 assert output == expected
179 179
180 180 def test_bad_repr():
181 181 """Don't catch bad repr errors"""
182 182 with pytest.raises(ZeroDivisionError):
183 183 pretty.pretty(BadRepr())
184 184
185 185 class BadException(Exception):
186 186 def __str__(self):
187 187 return -1
188 188
189 189 class ReallyBadRepr(object):
190 190 __module__ = 1
191 191 @property
192 192 def __class__(self):
193 193 raise ValueError("I am horrible")
194 194
195 195 def __repr__(self):
196 196 raise BadException()
197 197
198 198 def test_really_bad_repr():
199 199 with pytest.raises(BadException):
200 200 pretty.pretty(ReallyBadRepr())
201 201
202 202
203 203 class SA(object):
204 204 pass
205 205
206 206 class SB(SA):
207 207 pass
208 208
209 209 class TestsPretty(unittest.TestCase):
210 210
211 211 def test_super_repr(self):
212 212 # "<super: module_name.SA, None>"
213 213 output = pretty.pretty(super(SA))
214 214 self.assertRegex(output, r"<super: \S+.SA, None>")
215 215
216 216 # "<super: module_name.SA, <module_name.SB at 0x...>>"
217 217 sb = SB()
218 218 output = pretty.pretty(super(SA, sb))
219 219 self.assertRegex(output, r"<super: \S+.SA,\s+<\S+.SB at 0x\S+>>")
220 220
221 221
222 222 def test_long_list(self):
223 223 lis = list(range(10000))
224 224 p = pretty.pretty(lis)
225 225 last2 = p.rsplit('\n', 2)[-2:]
226 226 self.assertEqual(last2, [' 999,', ' ...]'])
227 227
228 228 def test_long_set(self):
229 229 s = set(range(10000))
230 230 p = pretty.pretty(s)
231 231 last2 = p.rsplit('\n', 2)[-2:]
232 232 self.assertEqual(last2, [' 999,', ' ...}'])
233 233
234 234 def test_long_tuple(self):
235 235 tup = tuple(range(10000))
236 236 p = pretty.pretty(tup)
237 237 last2 = p.rsplit('\n', 2)[-2:]
238 238 self.assertEqual(last2, [' 999,', ' ...)'])
239 239
240 240 def test_long_dict(self):
241 241 d = { n:n for n in range(10000) }
242 242 p = pretty.pretty(d)
243 243 last2 = p.rsplit('\n', 2)[-2:]
244 244 self.assertEqual(last2, [' 999: 999,', ' ...}'])
245 245
246 246 def test_unbound_method(self):
247 247 output = pretty.pretty(MyObj.somemethod)
248 248 self.assertIn('MyObj.somemethod', output)
249 249
250 250
251 251 class MetaClass(type):
252 252 def __new__(cls, name):
253 253 return type.__new__(cls, name, (object,), {'name': name})
254 254
255 255 def __repr__(self):
256 256 return "[CUSTOM REPR FOR CLASS %s]" % self.name
257 257
258 258
259 259 ClassWithMeta = MetaClass('ClassWithMeta')
260 260
261 261
262 262 def test_metaclass_repr():
263 263 output = pretty.pretty(ClassWithMeta)
264 264 assert output == "[CUSTOM REPR FOR CLASS ClassWithMeta]"
265 265
266 266
267 267 def test_unicode_repr():
268 268 u = u"üniçodé"
269 269 ustr = u
270 270
271 271 class C(object):
272 272 def __repr__(self):
273 273 return ustr
274 274
275 275 c = C()
276 276 p = pretty.pretty(c)
277 277 assert p == u
278 278 p = pretty.pretty([c])
279 279 assert p == "[%s]" % u
280 280
281 281
282 282 def test_basic_class():
283 283 def type_pprint_wrapper(obj, p, cycle):
284 284 if obj is MyObj:
285 285 type_pprint_wrapper.called = True
286 286 return pretty._type_pprint(obj, p, cycle)
287 287 type_pprint_wrapper.called = False
288 288
289 289 stream = StringIO()
290 290 printer = pretty.RepresentationPrinter(stream)
291 291 printer.type_pprinters[type] = type_pprint_wrapper
292 292 printer.pretty(MyObj)
293 293 printer.flush()
294 294 output = stream.getvalue()
295 295
296 296 assert output == "%s.MyObj" % __name__
297 297 assert type_pprint_wrapper.called is True
298 298
299 299
300 300 def test_collections_userlist():
301 301 # Create userlist with cycle
302 302 a = UserList()
303 303 a.append(a)
304 304
305 305 cases = [
306 306 (UserList(), "UserList([])"),
307 307 (
308 308 UserList(i for i in range(1000, 1020)),
309 309 "UserList([1000,\n"
310 310 " 1001,\n"
311 311 " 1002,\n"
312 312 " 1003,\n"
313 313 " 1004,\n"
314 314 " 1005,\n"
315 315 " 1006,\n"
316 316 " 1007,\n"
317 317 " 1008,\n"
318 318 " 1009,\n"
319 319 " 1010,\n"
320 320 " 1011,\n"
321 321 " 1012,\n"
322 322 " 1013,\n"
323 323 " 1014,\n"
324 324 " 1015,\n"
325 325 " 1016,\n"
326 326 " 1017,\n"
327 327 " 1018,\n"
328 328 " 1019])",
329 329 ),
330 330 (a, "UserList([UserList(...)])"),
331 331 ]
332 332 for obj, expected in cases:
333 333 assert pretty.pretty(obj) == expected
334 334
335 335
336 336 # TODO : pytest.mark.parametrise once nose is gone.
337 337 def test_collections_defaultdict():
338 338 # Create defaultdicts with cycles
339 339 a = defaultdict()
340 340 a.default_factory = a
341 341 b = defaultdict(list)
342 342 b['key'] = b
343 343
344 344 # Dictionary order cannot be relied on, test against single keys.
345 345 cases = [
346 346 (defaultdict(list), 'defaultdict(list, {})'),
347 347 (defaultdict(list, {'key': '-' * 50}),
348 348 "defaultdict(list,\n"
349 349 " {'key': '--------------------------------------------------'})"),
350 350 (a, 'defaultdict(defaultdict(...), {})'),
351 351 (b, "defaultdict(list, {'key': defaultdict(...)})"),
352 352 ]
353 353 for obj, expected in cases:
354 354 assert pretty.pretty(obj) == expected
355 355
356 356
357 357 # TODO : pytest.mark.parametrise once nose is gone.
358 358 def test_collections_ordereddict():
359 359 # Create OrderedDict with cycle
360 360 a = OrderedDict()
361 361 a['key'] = a
362 362
363 363 cases = [
364 364 (OrderedDict(), 'OrderedDict()'),
365 365 (OrderedDict((i, i) for i in range(1000, 1010)),
366 366 'OrderedDict([(1000, 1000),\n'
367 367 ' (1001, 1001),\n'
368 368 ' (1002, 1002),\n'
369 369 ' (1003, 1003),\n'
370 370 ' (1004, 1004),\n'
371 371 ' (1005, 1005),\n'
372 372 ' (1006, 1006),\n'
373 373 ' (1007, 1007),\n'
374 374 ' (1008, 1008),\n'
375 375 ' (1009, 1009)])'),
376 376 (a, "OrderedDict([('key', OrderedDict(...))])"),
377 377 ]
378 378 for obj, expected in cases:
379 379 assert pretty.pretty(obj) == expected
380 380
381 381
382 382 # TODO : pytest.mark.parametrise once nose is gone.
383 383 def test_collections_deque():
384 384 # Create deque with cycle
385 385 a = deque()
386 386 a.append(a)
387 387
388 388 cases = [
389 389 (deque(), 'deque([])'),
390 390 (deque(i for i in range(1000, 1020)),
391 391 'deque([1000,\n'
392 392 ' 1001,\n'
393 393 ' 1002,\n'
394 394 ' 1003,\n'
395 395 ' 1004,\n'
396 396 ' 1005,\n'
397 397 ' 1006,\n'
398 398 ' 1007,\n'
399 399 ' 1008,\n'
400 400 ' 1009,\n'
401 401 ' 1010,\n'
402 402 ' 1011,\n'
403 403 ' 1012,\n'
404 404 ' 1013,\n'
405 405 ' 1014,\n'
406 406 ' 1015,\n'
407 407 ' 1016,\n'
408 408 ' 1017,\n'
409 409 ' 1018,\n'
410 410 ' 1019])'),
411 411 (a, 'deque([deque(...)])'),
412 412 ]
413 413 for obj, expected in cases:
414 414 assert pretty.pretty(obj) == expected
415 415
416 416
417 417 # TODO : pytest.mark.parametrise once nose is gone.
418 418 def test_collections_counter():
419 419 class MyCounter(Counter):
420 420 pass
421 421 cases = [
422 422 (Counter(), 'Counter()'),
423 423 (Counter(a=1), "Counter({'a': 1})"),
424 424 (MyCounter(a=1), "MyCounter({'a': 1})"),
425 425 (Counter(a=1, c=22), "Counter({'c': 22, 'a': 1})"),
426 426 ]
427 427 for obj, expected in cases:
428 428 assert pretty.pretty(obj) == expected
429 429
430 430 # TODO : pytest.mark.parametrise once nose is gone.
431 431 def test_mappingproxy():
432 432 MP = types.MappingProxyType
433 433 underlying_dict = {}
434 434 mp_recursive = MP(underlying_dict)
435 435 underlying_dict[2] = mp_recursive
436 436 underlying_dict[3] = underlying_dict
437 437
438 438 cases = [
439 439 (MP({}), "mappingproxy({})"),
440 440 (MP({None: MP({})}), "mappingproxy({None: mappingproxy({})})"),
441 441 (MP({k: k.upper() for k in string.ascii_lowercase}),
442 442 "mappingproxy({'a': 'A',\n"
443 443 " 'b': 'B',\n"
444 444 " 'c': 'C',\n"
445 445 " 'd': 'D',\n"
446 446 " 'e': 'E',\n"
447 447 " 'f': 'F',\n"
448 448 " 'g': 'G',\n"
449 449 " 'h': 'H',\n"
450 450 " 'i': 'I',\n"
451 451 " 'j': 'J',\n"
452 452 " 'k': 'K',\n"
453 453 " 'l': 'L',\n"
454 454 " 'm': 'M',\n"
455 455 " 'n': 'N',\n"
456 456 " 'o': 'O',\n"
457 457 " 'p': 'P',\n"
458 458 " 'q': 'Q',\n"
459 459 " 'r': 'R',\n"
460 460 " 's': 'S',\n"
461 461 " 't': 'T',\n"
462 462 " 'u': 'U',\n"
463 463 " 'v': 'V',\n"
464 464 " 'w': 'W',\n"
465 465 " 'x': 'X',\n"
466 466 " 'y': 'Y',\n"
467 467 " 'z': 'Z'})"),
468 468 (mp_recursive, "mappingproxy({2: {...}, 3: {2: {...}, 3: {...}}})"),
469 469 (underlying_dict,
470 470 "{2: mappingproxy({2: {...}, 3: {...}}), 3: {...}}"),
471 471 ]
472 472 for obj, expected in cases:
473 473 assert pretty.pretty(obj) == expected
474 474
475 475
476 476 # TODO : pytest.mark.parametrise once nose is gone.
477 477 def test_simplenamespace():
478 478 SN = types.SimpleNamespace
479 479
480 480 sn_recursive = SN()
481 481 sn_recursive.first = sn_recursive
482 482 sn_recursive.second = sn_recursive
483 483 cases = [
484 484 (SN(), "namespace()"),
485 485 (SN(x=SN()), "namespace(x=namespace())"),
486 486 (SN(a_long_name=[SN(s=string.ascii_lowercase)]*3, a_short_name=None),
487 487 "namespace(a_long_name=[namespace(s='abcdefghijklmnopqrstuvwxyz'),\n"
488 488 " namespace(s='abcdefghijklmnopqrstuvwxyz'),\n"
489 489 " namespace(s='abcdefghijklmnopqrstuvwxyz')],\n"
490 490 " a_short_name=None)"),
491 491 (sn_recursive, "namespace(first=namespace(...), second=namespace(...))"),
492 492 ]
493 493 for obj, expected in cases:
494 494 assert pretty.pretty(obj) == expected
495 495
496 496
497 497 def test_pretty_environ():
498 498 dict_repr = pretty.pretty(dict(os.environ))
499 499 # reindent to align with 'environ' prefix
500 500 dict_indented = dict_repr.replace('\n', '\n' + (' ' * len('environ')))
501 501 env_repr = pretty.pretty(os.environ)
502 502 assert env_repr == "environ" + dict_indented
503 503
504 504
505 505 def test_function_pretty():
506 506 "Test pretty print of function"
507 507 # posixpath is a pure python module, its interface is consistent
508 508 # across Python distributions
509 509 import posixpath
510 510
511 511 assert pretty.pretty(posixpath.join) == "<function posixpath.join(a, *p)>"
512 512
513 513 # custom function
514 514 def meaning_of_life(question=None):
515 515 if question:
516 516 return 42
517 517 return "Don't panic"
518 518
519 519 assert "meaning_of_life(question=None)" in pretty.pretty(meaning_of_life)
520 520
521 521
522 522 class OrderedCounter(Counter, OrderedDict):
523 523 'Counter that remembers the order elements are first encountered'
524 524
525 525 def __repr__(self):
526 526 return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))
527 527
528 528 def __reduce__(self):
529 529 return self.__class__, (OrderedDict(self),)
530 530
531 531 class MySet(set): # Override repr of a basic type
532 532 def __repr__(self):
533 533 return 'mine'
534 534
535 535 def test_custom_repr():
536 536 """A custom repr should override a pretty printer for a parent type"""
537 537 oc = OrderedCounter("abracadabra")
538 538 assert "OrderedCounter(OrderedDict" in pretty.pretty(oc)
539 539
540 540 assert pretty.pretty(MySet()) == "mine"
@@ -1,81 +1,81 b''
1 1 [build-system]
2 2 requires = ["setuptools >= 51.0.0"]
3 3 build-backend = "setuptools.build_meta"
4 4
5 5 [tool.mypy]
6 python_version = 3.9
6 python_version = 3.10
7 7 ignore_missing_imports = true
8 8 follow_imports = 'silent'
9 9 exclude = [
10 10 'test_\.+\.py',
11 11 'IPython.utils.tests.test_wildcard',
12 12 'testing',
13 13 'tests',
14 14 'PyColorize.py',
15 15 '_process_win32_controller.py',
16 16 'IPython/core/application.py',
17 17 'IPython/core/completerlib.py',
18 18 'IPython/core/displaypub.py',
19 19 'IPython/core/historyapp.py',
20 20 #'IPython/core/interactiveshell.py',
21 21 'IPython/core/magic.py',
22 22 'IPython/core/profileapp.py',
23 23 # 'IPython/core/ultratb.py',
24 24 'IPython/lib/deepreload.py',
25 25 'IPython/lib/pretty.py',
26 26 'IPython/sphinxext/ipython_directive.py',
27 27 'IPython/terminal/ipapp.py',
28 28 'IPython/utils/_process_win32.py',
29 29 'IPython/utils/path.py',
30 30 'IPython/utils/timing.py',
31 31 'IPython/utils/text.py'
32 32 ]
33 33
34 34 [tool.pytest.ini_options]
35 35 addopts = [
36 36 "--durations=10",
37 37 "-pIPython.testing.plugin.pytest_ipdoctest",
38 38 "--ipdoctest-modules",
39 39 "--ignore=docs",
40 40 "--ignore=examples",
41 41 "--ignore=htmlcov",
42 42 "--ignore=ipython_kernel",
43 43 "--ignore=ipython_parallel",
44 44 "--ignore=results",
45 45 "--ignore=tmp",
46 46 "--ignore=tools",
47 47 "--ignore=traitlets",
48 48 "--ignore=IPython/core/tests/daft_extension",
49 49 "--ignore=IPython/sphinxext",
50 50 "--ignore=IPython/terminal/pt_inputhooks",
51 51 "--ignore=IPython/__main__.py",
52 52 "--ignore=IPython/external/qt_for_kernel.py",
53 53 "--ignore=IPython/html/widgets/widget_link.py",
54 54 "--ignore=IPython/html/widgets/widget_output.py",
55 55 "--ignore=IPython/terminal/console.py",
56 56 "--ignore=IPython/utils/_process_cli.py",
57 57 "--ignore=IPython/utils/_process_posix.py",
58 58 "--ignore=IPython/utils/_process_win32.py",
59 59 "--ignore=IPython/utils/_process_win32_controller.py",
60 60 "--ignore=IPython/utils/daemonize.py",
61 61 "--ignore=IPython/utils/eventful.py",
62 62 "--ignore=IPython/kernel",
63 63 "--ignore=IPython/consoleapp.py",
64 64 "--ignore=IPython/core/inputsplitter.py",
65 65 "--ignore=IPython/lib/kernel.py",
66 66 "--ignore=IPython/utils/jsonutil.py",
67 67 "--ignore=IPython/utils/localinterfaces.py",
68 68 "--ignore=IPython/utils/log.py",
69 69 "--ignore=IPython/utils/signatures.py",
70 70 "--ignore=IPython/utils/traitlets.py",
71 71 "--ignore=IPython/utils/version.py"
72 72 ]
73 73 doctest_optionflags = [
74 74 "NORMALIZE_WHITESPACE",
75 75 "ELLIPSIS"
76 76 ]
77 77 ipdoctest_optionflags = [
78 78 "NORMALIZE_WHITESPACE",
79 79 "ELLIPSIS"
80 80 ]
81 81 asyncio_mode = "strict"
@@ -1,116 +1,116 b''
1 1 [metadata]
2 2 name = ipython
3 3 version = attr: IPython.core.release.__version__
4 4 url = https://ipython.org
5 5 description = IPython: Productive Interactive Computing
6 6 long_description_content_type = text/x-rst
7 7 long_description = file: long_description.rst
8 8 license_file = LICENSE
9 9 project_urls =
10 10 Documentation = https://ipython.readthedocs.io/
11 11 Funding = https://numfocus.org/
12 12 Source = https://github.com/ipython/ipython
13 13 Tracker = https://github.com/ipython/ipython/issues
14 14 keywords = Interactive, Interpreter, Shell, Embedding
15 15 platforms = Linux, Mac OSX, Windows
16 16 classifiers =
17 17 Framework :: IPython
18 18 Framework :: Jupyter
19 19 Intended Audience :: Developers
20 20 Intended Audience :: Science/Research
21 21 License :: OSI Approved :: BSD License
22 22 Programming Language :: Python
23 23 Programming Language :: Python :: 3
24 24 Programming Language :: Python :: 3 :: Only
25 25 Topic :: System :: Shells
26 26
27 27 [options]
28 28 packages = find:
29 python_requires = >=3.9
29 python_requires = >=3.10
30 30 zip_safe = False
31 31 install_requires =
32 32 colorama; sys_platform == "win32"
33 33 decorator
34 34 exceptiongroup; python_version<'3.11'
35 35 jedi>=0.16
36 36 matplotlib-inline
37 37 pexpect>4.3; sys_platform != "win32"
38 38 prompt_toolkit>=3.0.30,<3.1.0,!=3.0.37
39 39 pygments>=2.4.0
40 40 stack_data
41 41 traitlets>=5
42 42 typing_extensions ; python_version<'3.10'
43 43
44 44 [options.extras_require]
45 45 black =
46 46 black
47 47 doc =
48 48 ipykernel
49 49 setuptools>=18.5
50 50 sphinx>=1.3
51 51 sphinx-rtd-theme
52 52 docrepr
53 53 matplotlib
54 54 stack_data
55 55 pytest<7
56 56 typing_extensions
57 57 exceptiongroup
58 58 %(test)s
59 59 kernel =
60 60 ipykernel
61 61 nbconvert =
62 62 nbconvert
63 63 nbformat =
64 64 nbformat
65 65 notebook =
66 66 ipywidgets
67 67 notebook
68 68 parallel =
69 69 ipyparallel
70 70 qtconsole =
71 71 qtconsole
72 72 terminal =
73 73 test =
74 74 pytest<7.1
75 75 pytest-asyncio<0.22
76 76 testpath
77 77 pickleshare
78 78 test_extra =
79 79 %(test)s
80 80 curio
81 81 matplotlib!=3.2.0
82 82 nbformat
83 numpy>=1.22
83 numpy>=1.23
84 84 pandas
85 85 trio
86 86 all =
87 87 %(black)s
88 88 %(doc)s
89 89 %(kernel)s
90 90 %(nbconvert)s
91 91 %(nbformat)s
92 92 %(notebook)s
93 93 %(parallel)s
94 94 %(qtconsole)s
95 95 %(terminal)s
96 96 %(test_extra)s
97 97 %(test)s
98 98
99 99 [options.packages.find]
100 100 exclude =
101 101 setupext
102 102
103 103 [options.package_data]
104 104 IPython = py.typed
105 105 IPython.core = profile/README*
106 106 IPython.core.tests = *.png, *.jpg, daft_extension/*.py
107 107 IPython.lib.tests = *.wav
108 108 IPython.testing.plugin = *.txt
109 109
110 110 [velin]
111 111 ignore_patterns =
112 112 IPython/core/tests
113 113 IPython/testing
114 114
115 115 [tool.black]
116 116 exclude = 'timing\.py'
@@ -1,158 +1,159 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Setup script for IPython.
3 3
4 4 Under Posix environments it works like a typical setup.py script.
5 5 Under Windows, the command sdist is not supported, since IPython
6 6 requires utilities which are not available under Windows."""
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (c) 2008-2011, IPython Development Team.
10 10 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
11 11 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
12 12 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
13 13 #
14 14 # Distributed under the terms of the Modified BSD License.
15 15 #
16 16 # The full license is in the file COPYING.rst, distributed with this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import os
20 20 import sys
21 21
22 22 # **Python version check**
23 23 #
24 24 # This check is also made in IPython/__init__, don't forget to update both when
25 25 # changing Python version requirements.
26 if sys.version_info < (3, 9):
26 if sys.version_info < (3, 10):
27 27 pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.'
28 28 try:
29 29 import pip
30 30 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
31 31 if pip_version < (9, 0, 1) :
32 32 pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\
33 33 'pip {} detected.'.format(pip.__version__)
34 34 else:
35 35 # pip is new enough - it must be something else
36 36 pip_message = ''
37 37 except Exception:
38 38 pass
39 39
40 40
41 41 error = """
42 IPython 8.19+ supports Python 3.10 and above, following SPEC0
42 43 IPython 8.13+ supports Python 3.9 and above, following NEP 29.
43 44 IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29.
44 45 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
45 46 Python 3.3 and 3.4 were supported up to IPython 6.x.
46 47 Python 3.5 was supported with IPython 7.0 to 7.9.
47 48 Python 3.6 was supported with IPython up to 7.16.
48 49 Python 3.7 was still supported with the 7.x branch.
49 50
50 51 See IPython `README.rst` file for more information:
51 52
52 53 https://github.com/ipython/ipython/blob/main/README.rst
53 54
54 55 Python {py} detected.
55 56 {pip}
56 57 """.format(
57 58 py=sys.version_info, pip=pip_message
58 59 )
59 60
60 61 print(error, file=sys.stderr)
61 62 sys.exit(1)
62 63
63 64 # At least we're on the python version we need, move on.
64 65
65 66 from setuptools import setup
66 67
67 68 # Our own imports
68 69 sys.path.insert(0, ".")
69 70
70 71 from setupbase import target_update, find_entry_points
71 72
72 73 from setupbase import (
73 74 setup_args,
74 75 check_package_data_first,
75 76 find_data_files,
76 77 git_prebuild,
77 78 install_symlinked,
78 79 install_lib_symlink,
79 80 install_scripts_for_symlink,
80 81 unsymlink,
81 82 )
82 83
83 84 #-------------------------------------------------------------------------------
84 85 # Handle OS specific things
85 86 #-------------------------------------------------------------------------------
86 87
87 88 if os.name in ('nt','dos'):
88 89 os_name = 'windows'
89 90 else:
90 91 os_name = os.name
91 92
92 93 # Under Windows, 'sdist' has not been supported. Now that the docs build with
93 94 # Sphinx it might work, but let's not turn it on until someone confirms that it
94 95 # actually works.
95 96 if os_name == 'windows' and 'sdist' in sys.argv:
96 97 print('The sdist command is not available under Windows. Exiting.')
97 98 sys.exit(1)
98 99
99 100
100 101 #-------------------------------------------------------------------------------
101 102 # Things related to the IPython documentation
102 103 #-------------------------------------------------------------------------------
103 104
104 105 # update the manuals when building a source dist
105 106 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
106 107
107 108 # List of things to be updated. Each entry is a triplet of args for
108 109 # target_update()
109 110 to_update = [
110 111 (
111 112 "docs/man/ipython.1.gz",
112 113 ["docs/man/ipython.1"],
113 114 "cd docs/man && python -m gzip --best ipython.1",
114 115 ),
115 116 ]
116 117
117 118
118 119 [ target_update(*t) for t in to_update ]
119 120
120 121 #---------------------------------------------------------------------------
121 122 # Find all the packages, package data, and data_files
122 123 #---------------------------------------------------------------------------
123 124
124 125 data_files = find_data_files()
125 126
126 127 setup_args['data_files'] = data_files
127 128
128 129 #---------------------------------------------------------------------------
129 130 # custom distutils commands
130 131 #---------------------------------------------------------------------------
131 132 # imports here, so they are after setuptools import if there was one
132 133 from setuptools.command.sdist import sdist
133 134
134 135 setup_args['cmdclass'] = {
135 136 'build_py': \
136 137 check_package_data_first(git_prebuild('IPython')),
137 138 'sdist' : git_prebuild('IPython', sdist),
138 139 'symlink': install_symlinked,
139 140 'install_lib_symlink': install_lib_symlink,
140 141 'install_scripts_sym': install_scripts_for_symlink,
141 142 'unsymlink': unsymlink,
142 143 }
143 144
144 145 setup_args["entry_points"] = {
145 146 "console_scripts": find_entry_points(),
146 147 "pygments.lexers": [
147 148 "ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer",
148 149 "ipython = IPython.lib.lexers:IPythonLexer",
149 150 "ipython3 = IPython.lib.lexers:IPython3Lexer",
150 151 ],
151 152 }
152 153
153 154 #---------------------------------------------------------------------------
154 155 # Do the actual setup now
155 156 #---------------------------------------------------------------------------
156 157
157 158 if __name__ == "__main__":
158 159 setup(**setup_args)
General Comments 0
You need to be logged in to leave comments. Login now