Show More
@@ -1,50 +1,51 b'' | |||
|
1 | 1 | name: Run 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 | |
|
12 | 12 | jobs: |
|
13 | 13 | test: |
|
14 | 14 | runs-on: ${{ matrix.os }} |
|
15 | 15 | strategy: |
|
16 | 16 | matrix: |
|
17 | 17 | os: [ubuntu-latest] |
|
18 | python-version: ["3.7", "3.8", "3.9"] | |
|
18 | python-version: ["3.7", "3.8", "3.9", "3.10"] | |
|
19 | 19 | # Test all on ubuntu, test ends on macos |
|
20 | 20 | include: |
|
21 | 21 | - os: macos-latest |
|
22 | 22 | python-version: "3.7" |
|
23 | 23 | - os: macos-latest |
|
24 | 24 | python-version: "3.9" |
|
25 | 25 | |
|
26 | 26 | steps: |
|
27 | 27 | - uses: actions/checkout@v2 |
|
28 | 28 | - name: Set up Python ${{ matrix.python-version }} |
|
29 | 29 | uses: actions/setup-python@v2 |
|
30 | 30 | with: |
|
31 | 31 | python-version: ${{ matrix.python-version }} |
|
32 | 32 | - name: Install and update Python dependencies |
|
33 | 33 | run: | |
|
34 | 34 | python -m pip install --upgrade pip setuptools wheel |
|
35 | 35 | python -m pip install --upgrade -e file://$PWD#egg=ipython[test] |
|
36 | 36 | python -m pip install --upgrade --upgrade-strategy eager trio curio |
|
37 | 37 | python -m pip install --upgrade pytest pytest-trio 'matplotlib!=3.2.0' |
|
38 | 38 | python -m pip install --upgrade check-manifest pytest-cov anyio |
|
39 | 39 | - name: Check manifest |
|
40 | 40 | run: check-manifest |
|
41 | 41 | - name: iptest |
|
42 | if: matrix.python-version != '3.10' | |
|
42 | 43 | run: | |
|
43 | 44 | cd /tmp && iptest --coverage xml && cd - |
|
44 | 45 | cp /tmp/ipy_coverage.xml ./ |
|
45 | 46 | cp /tmp/.coverage ./ |
|
46 | 47 | - name: pytest |
|
47 | 48 | run: | |
|
48 | 49 | pytest |
|
49 | 50 | - name: Upload coverage to Codecov |
|
50 | 51 | uses: codecov/codecov-action@v2 |
@@ -1,1031 +1,1031 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 | import ast |
|
17 | 17 | import inspect |
|
18 | 18 | from inspect import signature |
|
19 | 19 | import linecache |
|
20 | 20 | import warnings |
|
21 | 21 | import os |
|
22 | 22 | from textwrap import dedent |
|
23 | 23 | import types |
|
24 | 24 | import io as stdlib_io |
|
25 | 25 | |
|
26 | 26 | from typing import Union |
|
27 | 27 | |
|
28 | 28 | # IPython's own |
|
29 | 29 | from IPython.core import page |
|
30 | 30 | from IPython.lib.pretty import pretty |
|
31 | 31 | from IPython.testing.skipdoctest import skip_doctest |
|
32 | 32 | from IPython.utils import PyColorize |
|
33 | 33 | from IPython.utils import openpy |
|
34 | 34 | from IPython.utils import py3compat |
|
35 | 35 | from IPython.utils.dir2 import safe_hasattr |
|
36 | 36 | from IPython.utils.path import compress_user |
|
37 | 37 | from IPython.utils.text import indent |
|
38 | 38 | from IPython.utils.wildcard import list_namespace |
|
39 | 39 | from IPython.utils.wildcard import typestr2type |
|
40 | 40 | from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable |
|
41 | 41 | from IPython.utils.py3compat import cast_unicode |
|
42 | 42 | from IPython.utils.colorable import Colorable |
|
43 | 43 | from IPython.utils.decorators import undoc |
|
44 | 44 | |
|
45 | 45 | from pygments import highlight |
|
46 | 46 | from pygments.lexers import PythonLexer |
|
47 | 47 | from pygments.formatters import HtmlFormatter |
|
48 | 48 | |
|
49 | 49 | def pylight(code): |
|
50 | 50 | return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True)) |
|
51 | 51 | |
|
52 | 52 | # builtin docstrings to ignore |
|
53 | 53 | _func_call_docstring = types.FunctionType.__call__.__doc__ |
|
54 | 54 | _object_init_docstring = object.__init__.__doc__ |
|
55 | 55 | _builtin_type_docstrings = { |
|
56 | 56 | inspect.getdoc(t) for t in (types.ModuleType, types.MethodType, |
|
57 | 57 | types.FunctionType, property) |
|
58 | 58 | } |
|
59 | 59 | |
|
60 | 60 | _builtin_func_type = type(all) |
|
61 | 61 | _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions |
|
62 | 62 | #**************************************************************************** |
|
63 | 63 | # Builtin color schemes |
|
64 | 64 | |
|
65 | 65 | Colors = TermColors # just a shorthand |
|
66 | 66 | |
|
67 | 67 | InspectColors = PyColorize.ANSICodeColors |
|
68 | 68 | |
|
69 | 69 | #**************************************************************************** |
|
70 | 70 | # Auxiliary functions and objects |
|
71 | 71 | |
|
72 | 72 | # See the messaging spec for the definition of all these fields. This list |
|
73 | 73 | # effectively defines the order of display |
|
74 | 74 | info_fields = ['type_name', 'base_class', 'string_form', 'namespace', |
|
75 | 75 | 'length', 'file', 'definition', 'docstring', 'source', |
|
76 | 76 | 'init_definition', 'class_docstring', 'init_docstring', |
|
77 | 77 | 'call_def', 'call_docstring', |
|
78 | 78 | # These won't be printed but will be used to determine how to |
|
79 | 79 | # format the object |
|
80 | 80 | 'ismagic', 'isalias', 'isclass', 'found', 'name' |
|
81 | 81 | ] |
|
82 | 82 | |
|
83 | 83 | |
|
84 | 84 | def object_info(**kw): |
|
85 | 85 | """Make an object info dict with all fields present.""" |
|
86 | 86 | infodict = {k:None for k in info_fields} |
|
87 | 87 | infodict.update(kw) |
|
88 | 88 | return infodict |
|
89 | 89 | |
|
90 | 90 | |
|
91 | 91 | def get_encoding(obj): |
|
92 | 92 | """Get encoding for python source file defining obj |
|
93 | 93 | |
|
94 | 94 | Returns None if obj is not defined in a sourcefile. |
|
95 | 95 | """ |
|
96 | 96 | ofile = find_file(obj) |
|
97 | 97 | # run contents of file through pager starting at line where the object |
|
98 | 98 | # is defined, as long as the file isn't binary and is actually on the |
|
99 | 99 | # filesystem. |
|
100 | 100 | if ofile is None: |
|
101 | 101 | return None |
|
102 | 102 | elif ofile.endswith(('.so', '.dll', '.pyd')): |
|
103 | 103 | return None |
|
104 | 104 | elif not os.path.isfile(ofile): |
|
105 | 105 | return None |
|
106 | 106 | else: |
|
107 | 107 | # Print only text files, not extension binaries. Note that |
|
108 | 108 | # getsourcelines returns lineno with 1-offset and page() uses |
|
109 | 109 | # 0-offset, so we must adjust. |
|
110 | 110 | with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2 |
|
111 | 111 | encoding, lines = openpy.detect_encoding(buffer.readline) |
|
112 | 112 | return encoding |
|
113 | 113 | |
|
114 | 114 | def getdoc(obj) -> Union[str,None]: |
|
115 | 115 | """Stable wrapper around inspect.getdoc. |
|
116 | 116 | |
|
117 | 117 | This can't crash because of attribute problems. |
|
118 | 118 | |
|
119 | 119 | It also attempts to call a getdoc() method on the given object. This |
|
120 | 120 | allows objects which provide their docstrings via non-standard mechanisms |
|
121 | 121 | (like Pyro proxies) to still be inspected by ipython's ? system. |
|
122 | 122 | """ |
|
123 | 123 | # Allow objects to offer customized documentation via a getdoc method: |
|
124 | 124 | try: |
|
125 | 125 | ds = obj.getdoc() |
|
126 | 126 | except Exception: |
|
127 | 127 | pass |
|
128 | 128 | else: |
|
129 | 129 | if isinstance(ds, str): |
|
130 | 130 | return inspect.cleandoc(ds) |
|
131 | 131 | docstr = inspect.getdoc(obj) |
|
132 | 132 | return docstr |
|
133 | 133 | |
|
134 | 134 | |
|
135 | 135 | def getsource(obj, oname='') -> Union[str,None]: |
|
136 | 136 | """Wrapper around inspect.getsource. |
|
137 | 137 | |
|
138 | 138 | This can be modified by other projects to provide customized source |
|
139 | 139 | extraction. |
|
140 | 140 | |
|
141 | 141 | Parameters |
|
142 | 142 | ---------- |
|
143 | 143 | obj : object |
|
144 | 144 | an object whose source code we will attempt to extract |
|
145 | 145 | oname : str |
|
146 | 146 | (optional) a name under which the object is known |
|
147 | 147 | |
|
148 | 148 | Returns |
|
149 | 149 | ------- |
|
150 | 150 | src : unicode or None |
|
151 | 151 | |
|
152 | 152 | """ |
|
153 | 153 | |
|
154 | 154 | if isinstance(obj, property): |
|
155 | 155 | sources = [] |
|
156 | 156 | for attrname in ['fget', 'fset', 'fdel']: |
|
157 | 157 | fn = getattr(obj, attrname) |
|
158 | 158 | if fn is not None: |
|
159 | 159 | encoding = get_encoding(fn) |
|
160 | 160 | oname_prefix = ('%s.' % oname) if oname else '' |
|
161 | 161 | sources.append(''.join(('# ', oname_prefix, attrname))) |
|
162 | 162 | if inspect.isfunction(fn): |
|
163 | 163 | sources.append(dedent(getsource(fn))) |
|
164 | 164 | else: |
|
165 | 165 | # Default str/repr only prints function name, |
|
166 | 166 | # pretty.pretty prints module name too. |
|
167 | 167 | sources.append( |
|
168 | 168 | '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn)) |
|
169 | 169 | ) |
|
170 | 170 | if sources: |
|
171 | 171 | return '\n'.join(sources) |
|
172 | 172 | else: |
|
173 | 173 | return None |
|
174 | 174 | |
|
175 | 175 | else: |
|
176 | 176 | # Get source for non-property objects. |
|
177 | 177 | |
|
178 | 178 | obj = _get_wrapped(obj) |
|
179 | 179 | |
|
180 | 180 | try: |
|
181 | 181 | src = inspect.getsource(obj) |
|
182 | 182 | except TypeError: |
|
183 | 183 | # The object itself provided no meaningful source, try looking for |
|
184 | 184 | # its class definition instead. |
|
185 | if hasattr(obj, '__class__'): | |
|
186 | try: | |
|
187 | src = inspect.getsource(obj.__class__) | |
|
188 |
|
|
|
189 | return None | |
|
185 | try: | |
|
186 | src = inspect.getsource(obj.__class__) | |
|
187 | except (OSError, TypeError): | |
|
188 | return None | |
|
189 | except OSError: | |
|
190 | return None | |
|
190 | 191 | |
|
191 | 192 | return src |
|
192 | 193 | |
|
193 | 194 | |
|
194 | 195 | def is_simple_callable(obj): |
|
195 | 196 | """True if obj is a function ()""" |
|
196 | 197 | return (inspect.isfunction(obj) or inspect.ismethod(obj) or \ |
|
197 | 198 | isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type)) |
|
198 | 199 | |
|
199 | 200 | @undoc |
|
200 | 201 | def getargspec(obj): |
|
201 | 202 | """Wrapper around :func:`inspect.getfullargspec` |
|
202 | 203 | |
|
203 | 204 | In addition to functions and methods, this can also handle objects with a |
|
204 | 205 | ``__call__`` attribute. |
|
205 | 206 | |
|
206 | 207 | DEPRECATED: Deprecated since 7.10. Do not use, will be removed. |
|
207 | 208 | """ |
|
208 | 209 | |
|
209 | 210 | warnings.warn('`getargspec` function is deprecated as of IPython 7.10' |
|
210 | 211 | 'and will be removed in future versions.', DeprecationWarning, stacklevel=2) |
|
211 | 212 | |
|
212 | 213 | if safe_hasattr(obj, '__call__') and not is_simple_callable(obj): |
|
213 | 214 | obj = obj.__call__ |
|
214 | 215 | |
|
215 | 216 | return inspect.getfullargspec(obj) |
|
216 | 217 | |
|
217 | 218 | @undoc |
|
218 | 219 | def format_argspec(argspec): |
|
219 | 220 | """Format argspect, convenience wrapper around inspect's. |
|
220 | 221 | |
|
221 | 222 | This takes a dict instead of ordered arguments and calls |
|
222 | 223 | inspect.format_argspec with the arguments in the necessary order. |
|
223 | 224 | |
|
224 | 225 | DEPRECATED: Do not use; will be removed in future versions. |
|
225 | 226 | """ |
|
226 | 227 | |
|
227 | 228 | warnings.warn('`format_argspec` function is deprecated as of IPython 7.10' |
|
228 | 229 | 'and will be removed in future versions.', DeprecationWarning, stacklevel=2) |
|
229 | 230 | |
|
230 | 231 | |
|
231 | 232 | return inspect.formatargspec(argspec['args'], argspec['varargs'], |
|
232 | 233 | argspec['varkw'], argspec['defaults']) |
|
233 | 234 | |
|
234 | 235 | @undoc |
|
235 | 236 | def call_tip(oinfo, format_call=True): |
|
236 | 237 | """DEPRECATED. Extract call tip data from an oinfo dict. |
|
237 | 238 | """ |
|
238 | 239 | warnings.warn('`call_tip` function is deprecated as of IPython 6.0' |
|
239 | 240 | 'and will be removed in future versions.', DeprecationWarning, stacklevel=2) |
|
240 | 241 | # Get call definition |
|
241 | 242 | argspec = oinfo.get('argspec') |
|
242 | 243 | if argspec is None: |
|
243 | 244 | call_line = None |
|
244 | 245 | else: |
|
245 | 246 | # Callable objects will have 'self' as their first argument, prune |
|
246 | 247 | # it out if it's there for clarity (since users do *not* pass an |
|
247 | 248 | # extra first argument explicitly). |
|
248 | 249 | try: |
|
249 | 250 | has_self = argspec['args'][0] == 'self' |
|
250 | 251 | except (KeyError, IndexError): |
|
251 | 252 | pass |
|
252 | 253 | else: |
|
253 | 254 | if has_self: |
|
254 | 255 | argspec['args'] = argspec['args'][1:] |
|
255 | 256 | |
|
256 | 257 | call_line = oinfo['name']+format_argspec(argspec) |
|
257 | 258 | |
|
258 | 259 | # Now get docstring. |
|
259 | 260 | # The priority is: call docstring, constructor docstring, main one. |
|
260 | 261 | doc = oinfo.get('call_docstring') |
|
261 | 262 | if doc is None: |
|
262 | 263 | doc = oinfo.get('init_docstring') |
|
263 | 264 | if doc is None: |
|
264 | 265 | doc = oinfo.get('docstring','') |
|
265 | 266 | |
|
266 | 267 | return call_line, doc |
|
267 | 268 | |
|
268 | 269 | |
|
269 | 270 | def _get_wrapped(obj): |
|
270 | 271 | """Get the original object if wrapped in one or more @decorators |
|
271 | 272 | |
|
272 | 273 | Some objects automatically construct similar objects on any unrecognised |
|
273 | 274 | attribute access (e.g. unittest.mock.call). To protect against infinite loops, |
|
274 | 275 | this will arbitrarily cut off after 100 levels of obj.__wrapped__ |
|
275 | 276 | attribute access. --TK, Jan 2016 |
|
276 | 277 | """ |
|
277 | 278 | orig_obj = obj |
|
278 | 279 | i = 0 |
|
279 | 280 | while safe_hasattr(obj, '__wrapped__'): |
|
280 | 281 | obj = obj.__wrapped__ |
|
281 | 282 | i += 1 |
|
282 | 283 | if i > 100: |
|
283 | 284 | # __wrapped__ is probably a lie, so return the thing we started with |
|
284 | 285 | return orig_obj |
|
285 | 286 | return obj |
|
286 | 287 | |
|
287 | 288 | def find_file(obj) -> str: |
|
288 | 289 | """Find the absolute path to the file where an object was defined. |
|
289 | 290 | |
|
290 | 291 | This is essentially a robust wrapper around `inspect.getabsfile`. |
|
291 | 292 | |
|
292 | 293 | Returns None if no file can be found. |
|
293 | 294 | |
|
294 | 295 | Parameters |
|
295 | 296 | ---------- |
|
296 | 297 | obj : any Python object |
|
297 | 298 | |
|
298 | 299 | Returns |
|
299 | 300 | ------- |
|
300 | 301 | fname : str |
|
301 | 302 | The absolute path to the file where the object was defined. |
|
302 | 303 | """ |
|
303 | 304 | obj = _get_wrapped(obj) |
|
304 | 305 | |
|
305 | 306 | fname = None |
|
306 | 307 | try: |
|
307 | 308 | fname = inspect.getabsfile(obj) |
|
308 | 309 | except TypeError: |
|
309 | 310 | # For an instance, the file that matters is where its class was |
|
310 | 311 | # declared. |
|
311 | if hasattr(obj, '__class__'): | |
|
312 | try: | |
|
313 | fname = inspect.getabsfile(obj.__class__) | |
|
314 | except TypeError: | |
|
315 | # Can happen for builtins | |
|
316 | pass | |
|
317 | except: | |
|
312 | try: | |
|
313 | fname = inspect.getabsfile(obj.__class__) | |
|
314 | except (OSError, TypeError): | |
|
315 | # Can happen for builtins | |
|
316 | pass | |
|
317 | except OSError: | |
|
318 | 318 | pass |
|
319 | ||
|
319 | 320 | return cast_unicode(fname) |
|
320 | 321 | |
|
321 | 322 | |
|
322 | 323 | def find_source_lines(obj): |
|
323 | 324 | """Find the line number in a file where an object was defined. |
|
324 | 325 | |
|
325 | 326 | This is essentially a robust wrapper around `inspect.getsourcelines`. |
|
326 | 327 | |
|
327 | 328 | Returns None if no file can be found. |
|
328 | 329 | |
|
329 | 330 | Parameters |
|
330 | 331 | ---------- |
|
331 | 332 | obj : any Python object |
|
332 | 333 | |
|
333 | 334 | Returns |
|
334 | 335 | ------- |
|
335 | 336 | lineno : int |
|
336 | 337 | The line number where the object definition starts. |
|
337 | 338 | """ |
|
338 | 339 | obj = _get_wrapped(obj) |
|
339 | 340 | |
|
340 | 341 | try: |
|
342 | lineno = inspect.getsourcelines(obj)[1] | |
|
343 | except TypeError: | |
|
344 | # For instances, try the class object like getsource() does | |
|
341 | 345 | try: |
|
342 | lineno = inspect.getsourcelines(obj)[1] | |
|
343 | except TypeError: | |
|
344 | # For instances, try the class object like getsource() does | |
|
345 | if hasattr(obj, '__class__'): | |
|
346 | lineno = inspect.getsourcelines(obj.__class__)[1] | |
|
347 | else: | |
|
348 | lineno = None | |
|
349 | except: | |
|
346 | lineno = inspect.getsourcelines(obj.__class__)[1] | |
|
347 | except (OSError, TypeError): | |
|
348 | return None | |
|
349 | except OSError: | |
|
350 | 350 | return None |
|
351 | 351 | |
|
352 | 352 | return lineno |
|
353 | 353 | |
|
354 | 354 | class Inspector(Colorable): |
|
355 | 355 | |
|
356 | 356 | def __init__(self, color_table=InspectColors, |
|
357 | 357 | code_color_table=PyColorize.ANSICodeColors, |
|
358 | 358 | scheme=None, |
|
359 | 359 | str_detail_level=0, |
|
360 | 360 | parent=None, config=None): |
|
361 | 361 | super(Inspector, self).__init__(parent=parent, config=config) |
|
362 | 362 | self.color_table = color_table |
|
363 | 363 | self.parser = PyColorize.Parser(out='str', parent=self, style=scheme) |
|
364 | 364 | self.format = self.parser.format |
|
365 | 365 | self.str_detail_level = str_detail_level |
|
366 | 366 | self.set_active_scheme(scheme) |
|
367 | 367 | |
|
368 | 368 | def _getdef(self,obj,oname='') -> Union[str,None]: |
|
369 | 369 | """Return the call signature for any callable object. |
|
370 | 370 | |
|
371 | 371 | If any exception is generated, None is returned instead and the |
|
372 | 372 | exception is suppressed.""" |
|
373 | 373 | try: |
|
374 | 374 | return _render_signature(signature(obj), oname) |
|
375 | 375 | except: |
|
376 | 376 | return None |
|
377 | 377 | |
|
378 | 378 | def __head(self,h) -> str: |
|
379 | 379 | """Return a header string with proper colors.""" |
|
380 | 380 | return '%s%s%s' % (self.color_table.active_colors.header,h, |
|
381 | 381 | self.color_table.active_colors.normal) |
|
382 | 382 | |
|
383 | 383 | def set_active_scheme(self, scheme): |
|
384 | 384 | if scheme is not None: |
|
385 | 385 | self.color_table.set_active_scheme(scheme) |
|
386 | 386 | self.parser.color_table.set_active_scheme(scheme) |
|
387 | 387 | |
|
388 | 388 | def noinfo(self, msg, oname): |
|
389 | 389 | """Generic message when no information is found.""" |
|
390 | 390 | print('No %s found' % msg, end=' ') |
|
391 | 391 | if oname: |
|
392 | 392 | print('for %s' % oname) |
|
393 | 393 | else: |
|
394 | 394 | print() |
|
395 | 395 | |
|
396 | 396 | def pdef(self, obj, oname=''): |
|
397 | 397 | """Print the call signature for any callable object. |
|
398 | 398 | |
|
399 | 399 | If the object is a class, print the constructor information.""" |
|
400 | 400 | |
|
401 | 401 | if not callable(obj): |
|
402 | 402 | print('Object is not callable.') |
|
403 | 403 | return |
|
404 | 404 | |
|
405 | 405 | header = '' |
|
406 | 406 | |
|
407 | 407 | if inspect.isclass(obj): |
|
408 | 408 | header = self.__head('Class constructor information:\n') |
|
409 | 409 | |
|
410 | 410 | |
|
411 | 411 | output = self._getdef(obj,oname) |
|
412 | 412 | if output is None: |
|
413 | 413 | self.noinfo('definition header',oname) |
|
414 | 414 | else: |
|
415 | 415 | print(header,self.format(output), end=' ') |
|
416 | 416 | |
|
417 | 417 | # In Python 3, all classes are new-style, so they all have __init__. |
|
418 | 418 | @skip_doctest |
|
419 | 419 | def pdoc(self, obj, oname='', formatter=None): |
|
420 | 420 | """Print the docstring for any object. |
|
421 | 421 | |
|
422 | 422 | Optional: |
|
423 | 423 | -formatter: a function to run the docstring through for specially |
|
424 | 424 | formatted docstrings. |
|
425 | 425 | |
|
426 | 426 | Examples |
|
427 | 427 | -------- |
|
428 | 428 | |
|
429 | 429 | In [1]: class NoInit: |
|
430 | 430 | ...: pass |
|
431 | 431 | |
|
432 | 432 | In [2]: class NoDoc: |
|
433 | 433 | ...: def __init__(self): |
|
434 | 434 | ...: pass |
|
435 | 435 | |
|
436 | 436 | In [3]: %pdoc NoDoc |
|
437 | 437 | No documentation found for NoDoc |
|
438 | 438 | |
|
439 | 439 | In [4]: %pdoc NoInit |
|
440 | 440 | No documentation found for NoInit |
|
441 | 441 | |
|
442 | 442 | In [5]: obj = NoInit() |
|
443 | 443 | |
|
444 | 444 | In [6]: %pdoc obj |
|
445 | 445 | No documentation found for obj |
|
446 | 446 | |
|
447 | 447 | In [5]: obj2 = NoDoc() |
|
448 | 448 | |
|
449 | 449 | In [6]: %pdoc obj2 |
|
450 | 450 | No documentation found for obj2 |
|
451 | 451 | """ |
|
452 | 452 | |
|
453 | 453 | head = self.__head # For convenience |
|
454 | 454 | lines = [] |
|
455 | 455 | ds = getdoc(obj) |
|
456 | 456 | if formatter: |
|
457 | 457 | ds = formatter(ds).get('plain/text', ds) |
|
458 | 458 | if ds: |
|
459 | 459 | lines.append(head("Class docstring:")) |
|
460 | 460 | lines.append(indent(ds)) |
|
461 | 461 | if inspect.isclass(obj) and hasattr(obj, '__init__'): |
|
462 | 462 | init_ds = getdoc(obj.__init__) |
|
463 | 463 | if init_ds is not None: |
|
464 | 464 | lines.append(head("Init docstring:")) |
|
465 | 465 | lines.append(indent(init_ds)) |
|
466 | 466 | elif hasattr(obj,'__call__'): |
|
467 | 467 | call_ds = getdoc(obj.__call__) |
|
468 | 468 | if call_ds: |
|
469 | 469 | lines.append(head("Call docstring:")) |
|
470 | 470 | lines.append(indent(call_ds)) |
|
471 | 471 | |
|
472 | 472 | if not lines: |
|
473 | 473 | self.noinfo('documentation',oname) |
|
474 | 474 | else: |
|
475 | 475 | page.page('\n'.join(lines)) |
|
476 | 476 | |
|
477 | 477 | def psource(self, obj, oname=''): |
|
478 | 478 | """Print the source code for an object.""" |
|
479 | 479 | |
|
480 | 480 | # Flush the source cache because inspect can return out-of-date source |
|
481 | 481 | linecache.checkcache() |
|
482 | 482 | try: |
|
483 | 483 | src = getsource(obj, oname=oname) |
|
484 | 484 | except Exception: |
|
485 | 485 | src = None |
|
486 | 486 | |
|
487 | 487 | if src is None: |
|
488 | 488 | self.noinfo('source', oname) |
|
489 | 489 | else: |
|
490 | 490 | page.page(self.format(src)) |
|
491 | 491 | |
|
492 | 492 | def pfile(self, obj, oname=''): |
|
493 | 493 | """Show the whole file where an object was defined.""" |
|
494 | 494 | |
|
495 | 495 | lineno = find_source_lines(obj) |
|
496 | 496 | if lineno is None: |
|
497 | 497 | self.noinfo('file', oname) |
|
498 | 498 | return |
|
499 | 499 | |
|
500 | 500 | ofile = find_file(obj) |
|
501 | 501 | # run contents of file through pager starting at line where the object |
|
502 | 502 | # is defined, as long as the file isn't binary and is actually on the |
|
503 | 503 | # filesystem. |
|
504 | 504 | if ofile.endswith(('.so', '.dll', '.pyd')): |
|
505 | 505 | print('File %r is binary, not printing.' % ofile) |
|
506 | 506 | elif not os.path.isfile(ofile): |
|
507 | 507 | print('File %r does not exist, not printing.' % ofile) |
|
508 | 508 | else: |
|
509 | 509 | # Print only text files, not extension binaries. Note that |
|
510 | 510 | # getsourcelines returns lineno with 1-offset and page() uses |
|
511 | 511 | # 0-offset, so we must adjust. |
|
512 | 512 | page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1) |
|
513 | 513 | |
|
514 | 514 | |
|
515 | 515 | def _mime_format(self, text:str, formatter=None) -> dict: |
|
516 | 516 | """Return a mime bundle representation of the input text. |
|
517 | 517 | |
|
518 | 518 | - if `formatter` is None, the returned mime bundle has |
|
519 | 519 | a `text/plain` field, with the input text. |
|
520 | 520 | a `text/html` field with a `<pre>` tag containing the input text. |
|
521 | 521 | |
|
522 | 522 | - if `formatter` is not None, it must be a callable transforming the |
|
523 | 523 | input text into a mime bundle. Default values for `text/plain` and |
|
524 | 524 | `text/html` representations are the ones described above. |
|
525 | 525 | |
|
526 | 526 | Note: |
|
527 | 527 | |
|
528 | 528 | Formatters returning strings are supported but this behavior is deprecated. |
|
529 | 529 | |
|
530 | 530 | """ |
|
531 | 531 | defaults = { |
|
532 | 532 | 'text/plain': text, |
|
533 | 533 | 'text/html': '<pre>' + text + '</pre>' |
|
534 | 534 | } |
|
535 | 535 | |
|
536 | 536 | if formatter is None: |
|
537 | 537 | return defaults |
|
538 | 538 | else: |
|
539 | 539 | formatted = formatter(text) |
|
540 | 540 | |
|
541 | 541 | if not isinstance(formatted, dict): |
|
542 | 542 | # Handle the deprecated behavior of a formatter returning |
|
543 | 543 | # a string instead of a mime bundle. |
|
544 | 544 | return { |
|
545 | 545 | 'text/plain': formatted, |
|
546 | 546 | 'text/html': '<pre>' + formatted + '</pre>' |
|
547 | 547 | } |
|
548 | 548 | |
|
549 | 549 | else: |
|
550 | 550 | return dict(defaults, **formatted) |
|
551 | 551 | |
|
552 | 552 | |
|
553 | 553 | def format_mime(self, bundle): |
|
554 | 554 | |
|
555 | 555 | text_plain = bundle['text/plain'] |
|
556 | 556 | |
|
557 | 557 | text = '' |
|
558 | 558 | heads, bodies = list(zip(*text_plain)) |
|
559 | 559 | _len = max(len(h) for h in heads) |
|
560 | 560 | |
|
561 | 561 | for head, body in zip(heads, bodies): |
|
562 | 562 | body = body.strip('\n') |
|
563 | 563 | delim = '\n' if '\n' in body else ' ' |
|
564 | 564 | text += self.__head(head+':') + (_len - len(head))*' ' +delim + body +'\n' |
|
565 | 565 | |
|
566 | 566 | bundle['text/plain'] = text |
|
567 | 567 | return bundle |
|
568 | 568 | |
|
569 | 569 | def _get_info(self, obj, oname='', formatter=None, info=None, detail_level=0): |
|
570 | 570 | """Retrieve an info dict and format it. |
|
571 | 571 | |
|
572 | 572 | Parameters |
|
573 | 573 | ========== |
|
574 | 574 | |
|
575 | 575 | obj: any |
|
576 | 576 | Object to inspect and return info from |
|
577 | 577 | oname: str (default: ''): |
|
578 | 578 | Name of the variable pointing to `obj`. |
|
579 | 579 | formatter: callable |
|
580 | 580 | info: |
|
581 | 581 | already computed information |
|
582 | 582 | detail_level: integer |
|
583 | 583 | Granularity of detail level, if set to 1, give more information. |
|
584 | 584 | """ |
|
585 | 585 | |
|
586 | 586 | info = self._info(obj, oname=oname, info=info, detail_level=detail_level) |
|
587 | 587 | |
|
588 | 588 | _mime = { |
|
589 | 589 | 'text/plain': [], |
|
590 | 590 | 'text/html': '', |
|
591 | 591 | } |
|
592 | 592 | |
|
593 | 593 | def append_field(bundle, title:str, key:str, formatter=None): |
|
594 | 594 | field = info[key] |
|
595 | 595 | if field is not None: |
|
596 | 596 | formatted_field = self._mime_format(field, formatter) |
|
597 | 597 | bundle['text/plain'].append((title, formatted_field['text/plain'])) |
|
598 | 598 | bundle['text/html'] += '<h1>' + title + '</h1>\n' + formatted_field['text/html'] + '\n' |
|
599 | 599 | |
|
600 | 600 | def code_formatter(text): |
|
601 | 601 | return { |
|
602 | 602 | 'text/plain': self.format(text), |
|
603 | 603 | 'text/html': pylight(text) |
|
604 | 604 | } |
|
605 | 605 | |
|
606 | 606 | if info['isalias']: |
|
607 | 607 | append_field(_mime, 'Repr', 'string_form') |
|
608 | 608 | |
|
609 | 609 | elif info['ismagic']: |
|
610 | 610 | if detail_level > 0: |
|
611 | 611 | append_field(_mime, 'Source', 'source', code_formatter) |
|
612 | 612 | else: |
|
613 | 613 | append_field(_mime, 'Docstring', 'docstring', formatter) |
|
614 | 614 | append_field(_mime, 'File', 'file') |
|
615 | 615 | |
|
616 | 616 | elif info['isclass'] or is_simple_callable(obj): |
|
617 | 617 | # Functions, methods, classes |
|
618 | 618 | append_field(_mime, 'Signature', 'definition', code_formatter) |
|
619 | 619 | append_field(_mime, 'Init signature', 'init_definition', code_formatter) |
|
620 | 620 | append_field(_mime, 'Docstring', 'docstring', formatter) |
|
621 | 621 | if detail_level > 0 and info['source']: |
|
622 | 622 | append_field(_mime, 'Source', 'source', code_formatter) |
|
623 | 623 | else: |
|
624 | 624 | append_field(_mime, 'Init docstring', 'init_docstring', formatter) |
|
625 | 625 | |
|
626 | 626 | append_field(_mime, 'File', 'file') |
|
627 | 627 | append_field(_mime, 'Type', 'type_name') |
|
628 | 628 | append_field(_mime, 'Subclasses', 'subclasses') |
|
629 | 629 | |
|
630 | 630 | else: |
|
631 | 631 | # General Python objects |
|
632 | 632 | append_field(_mime, 'Signature', 'definition', code_formatter) |
|
633 | 633 | append_field(_mime, 'Call signature', 'call_def', code_formatter) |
|
634 | 634 | append_field(_mime, 'Type', 'type_name') |
|
635 | 635 | append_field(_mime, 'String form', 'string_form') |
|
636 | 636 | |
|
637 | 637 | # Namespace |
|
638 | 638 | if info['namespace'] != 'Interactive': |
|
639 | 639 | append_field(_mime, 'Namespace', 'namespace') |
|
640 | 640 | |
|
641 | 641 | append_field(_mime, 'Length', 'length') |
|
642 | 642 | append_field(_mime, 'File', 'file') |
|
643 | 643 | |
|
644 | 644 | # Source or docstring, depending on detail level and whether |
|
645 | 645 | # source found. |
|
646 | 646 | if detail_level > 0 and info['source']: |
|
647 | 647 | append_field(_mime, 'Source', 'source', code_formatter) |
|
648 | 648 | else: |
|
649 | 649 | append_field(_mime, 'Docstring', 'docstring', formatter) |
|
650 | 650 | |
|
651 | 651 | append_field(_mime, 'Class docstring', 'class_docstring', formatter) |
|
652 | 652 | append_field(_mime, 'Init docstring', 'init_docstring', formatter) |
|
653 | 653 | append_field(_mime, 'Call docstring', 'call_docstring', formatter) |
|
654 | 654 | |
|
655 | 655 | |
|
656 | 656 | return self.format_mime(_mime) |
|
657 | 657 | |
|
658 | 658 | def pinfo(self, obj, oname='', formatter=None, info=None, detail_level=0, enable_html_pager=True): |
|
659 | 659 | """Show detailed information about an object. |
|
660 | 660 | |
|
661 | 661 | Optional arguments: |
|
662 | 662 | |
|
663 | 663 | - oname: name of the variable pointing to the object. |
|
664 | 664 | |
|
665 | 665 | - formatter: callable (optional) |
|
666 | 666 | A special formatter for docstrings. |
|
667 | 667 | |
|
668 | 668 | The formatter is a callable that takes a string as an input |
|
669 | 669 | and returns either a formatted string or a mime type bundle |
|
670 | 670 | in the form of a dictionary. |
|
671 | 671 | |
|
672 | 672 | Although the support of custom formatter returning a string |
|
673 | 673 | instead of a mime type bundle is deprecated. |
|
674 | 674 | |
|
675 | 675 | - info: a structure with some information fields which may have been |
|
676 | 676 | precomputed already. |
|
677 | 677 | |
|
678 | 678 | - detail_level: if set to 1, more information is given. |
|
679 | 679 | """ |
|
680 | 680 | info = self._get_info(obj, oname, formatter, info, detail_level) |
|
681 | 681 | if not enable_html_pager: |
|
682 | 682 | del info['text/html'] |
|
683 | 683 | page.page(info) |
|
684 | 684 | |
|
685 | 685 | def info(self, obj, oname='', formatter=None, info=None, detail_level=0): |
|
686 | 686 | """DEPRECATED. Compute a dict with detailed information about an object. |
|
687 | 687 | """ |
|
688 | 688 | if formatter is not None: |
|
689 | 689 | warnings.warn('The `formatter` keyword argument to `Inspector.info`' |
|
690 | 690 | 'is deprecated as of IPython 5.0 and will have no effects.', |
|
691 | 691 | DeprecationWarning, stacklevel=2) |
|
692 | 692 | return self._info(obj, oname=oname, info=info, detail_level=detail_level) |
|
693 | 693 | |
|
694 | 694 | def _info(self, obj, oname='', info=None, detail_level=0) -> dict: |
|
695 | 695 | """Compute a dict with detailed information about an object. |
|
696 | 696 | |
|
697 | 697 | Parameters |
|
698 | 698 | ========== |
|
699 | 699 | |
|
700 | 700 | obj: any |
|
701 | 701 | An object to find information about |
|
702 | 702 | oname: str (default: ''): |
|
703 | 703 | Name of the variable pointing to `obj`. |
|
704 | 704 | info: (default: None) |
|
705 | 705 | A struct (dict like with attr access) with some information fields |
|
706 | 706 | which may have been precomputed already. |
|
707 | 707 | detail_level: int (default:0) |
|
708 | 708 | If set to 1, more information is given. |
|
709 | 709 | |
|
710 | 710 | Returns |
|
711 | 711 | ======= |
|
712 | 712 | |
|
713 | 713 | An object info dict with known fields from `info_fields`. Keys are |
|
714 | 714 | strings, values are string or None. |
|
715 | 715 | """ |
|
716 | 716 | |
|
717 | 717 | if info is None: |
|
718 | 718 | ismagic = False |
|
719 | 719 | isalias = False |
|
720 | 720 | ospace = '' |
|
721 | 721 | else: |
|
722 | 722 | ismagic = info.ismagic |
|
723 | 723 | isalias = info.isalias |
|
724 | 724 | ospace = info.namespace |
|
725 | 725 | |
|
726 | 726 | # Get docstring, special-casing aliases: |
|
727 | 727 | if isalias: |
|
728 | 728 | if not callable(obj): |
|
729 | 729 | try: |
|
730 | 730 | ds = "Alias to the system command:\n %s" % obj[1] |
|
731 | 731 | except: |
|
732 | 732 | ds = "Alias: " + str(obj) |
|
733 | 733 | else: |
|
734 | 734 | ds = "Alias to " + str(obj) |
|
735 | 735 | if obj.__doc__: |
|
736 | 736 | ds += "\nDocstring:\n" + obj.__doc__ |
|
737 | 737 | else: |
|
738 | 738 | ds = getdoc(obj) |
|
739 | 739 | if ds is None: |
|
740 | 740 | ds = '<no docstring>' |
|
741 | 741 | |
|
742 | 742 | # store output in a dict, we initialize it here and fill it as we go |
|
743 | 743 | out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None) |
|
744 | 744 | |
|
745 | 745 | string_max = 200 # max size of strings to show (snipped if longer) |
|
746 | 746 | shalf = int((string_max - 5) / 2) |
|
747 | 747 | |
|
748 | 748 | if ismagic: |
|
749 | 749 | out['type_name'] = 'Magic function' |
|
750 | 750 | elif isalias: |
|
751 | 751 | out['type_name'] = 'System alias' |
|
752 | 752 | else: |
|
753 | 753 | out['type_name'] = type(obj).__name__ |
|
754 | 754 | |
|
755 | 755 | try: |
|
756 | 756 | bclass = obj.__class__ |
|
757 | 757 | out['base_class'] = str(bclass) |
|
758 | 758 | except: |
|
759 | 759 | pass |
|
760 | 760 | |
|
761 | 761 | # String form, but snip if too long in ? form (full in ??) |
|
762 | 762 | if detail_level >= self.str_detail_level: |
|
763 | 763 | try: |
|
764 | 764 | ostr = str(obj) |
|
765 | 765 | str_head = 'string_form' |
|
766 | 766 | if not detail_level and len(ostr)>string_max: |
|
767 | 767 | ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:] |
|
768 | 768 | ostr = ("\n" + " " * len(str_head.expandtabs())).\ |
|
769 | 769 | join(q.strip() for q in ostr.split("\n")) |
|
770 | 770 | out[str_head] = ostr |
|
771 | 771 | except: |
|
772 | 772 | pass |
|
773 | 773 | |
|
774 | 774 | if ospace: |
|
775 | 775 | out['namespace'] = ospace |
|
776 | 776 | |
|
777 | 777 | # Length (for strings and lists) |
|
778 | 778 | try: |
|
779 | 779 | out['length'] = str(len(obj)) |
|
780 | 780 | except Exception: |
|
781 | 781 | pass |
|
782 | 782 | |
|
783 | 783 | # Filename where object was defined |
|
784 | 784 | binary_file = False |
|
785 | 785 | fname = find_file(obj) |
|
786 | 786 | if fname is None: |
|
787 | 787 | # if anything goes wrong, we don't want to show source, so it's as |
|
788 | 788 | # if the file was binary |
|
789 | 789 | binary_file = True |
|
790 | 790 | else: |
|
791 | 791 | if fname.endswith(('.so', '.dll', '.pyd')): |
|
792 | 792 | binary_file = True |
|
793 | 793 | elif fname.endswith('<string>'): |
|
794 | 794 | fname = 'Dynamically generated function. No source code available.' |
|
795 | 795 | out['file'] = compress_user(fname) |
|
796 | 796 | |
|
797 | 797 | # Original source code for a callable, class or property. |
|
798 | 798 | if detail_level: |
|
799 | 799 | # Flush the source cache because inspect can return out-of-date |
|
800 | 800 | # source |
|
801 | 801 | linecache.checkcache() |
|
802 | 802 | try: |
|
803 | 803 | if isinstance(obj, property) or not binary_file: |
|
804 | 804 | src = getsource(obj, oname) |
|
805 | 805 | if src is not None: |
|
806 | 806 | src = src.rstrip() |
|
807 | 807 | out['source'] = src |
|
808 | 808 | |
|
809 | 809 | except Exception: |
|
810 | 810 | pass |
|
811 | 811 | |
|
812 | 812 | # Add docstring only if no source is to be shown (avoid repetitions). |
|
813 | 813 | if ds and not self._source_contains_docstring(out.get('source'), ds): |
|
814 | 814 | out['docstring'] = ds |
|
815 | 815 | |
|
816 | 816 | # Constructor docstring for classes |
|
817 | 817 | if inspect.isclass(obj): |
|
818 | 818 | out['isclass'] = True |
|
819 | 819 | |
|
820 | 820 | # get the init signature: |
|
821 | 821 | try: |
|
822 | 822 | init_def = self._getdef(obj, oname) |
|
823 | 823 | except AttributeError: |
|
824 | 824 | init_def = None |
|
825 | 825 | |
|
826 | 826 | # get the __init__ docstring |
|
827 | 827 | try: |
|
828 | 828 | obj_init = obj.__init__ |
|
829 | 829 | except AttributeError: |
|
830 | 830 | init_ds = None |
|
831 | 831 | else: |
|
832 | 832 | if init_def is None: |
|
833 | 833 | # Get signature from init if top-level sig failed. |
|
834 | 834 | # Can happen for built-in types (list, etc.). |
|
835 | 835 | try: |
|
836 | 836 | init_def = self._getdef(obj_init, oname) |
|
837 | 837 | except AttributeError: |
|
838 | 838 | pass |
|
839 | 839 | init_ds = getdoc(obj_init) |
|
840 | 840 | # Skip Python's auto-generated docstrings |
|
841 | 841 | if init_ds == _object_init_docstring: |
|
842 | 842 | init_ds = None |
|
843 | 843 | |
|
844 | 844 | if init_def: |
|
845 | 845 | out['init_definition'] = init_def |
|
846 | 846 | |
|
847 | 847 | if init_ds: |
|
848 | 848 | out['init_docstring'] = init_ds |
|
849 | 849 | |
|
850 | 850 | names = [sub.__name__ for sub in type.__subclasses__(obj)] |
|
851 | 851 | if len(names) < 10: |
|
852 | 852 | all_names = ', '.join(names) |
|
853 | 853 | else: |
|
854 | 854 | all_names = ', '.join(names[:10]+['...']) |
|
855 | 855 | out['subclasses'] = all_names |
|
856 | 856 | # and class docstring for instances: |
|
857 | 857 | else: |
|
858 | 858 | # reconstruct the function definition and print it: |
|
859 | 859 | defln = self._getdef(obj, oname) |
|
860 | 860 | if defln: |
|
861 | 861 | out['definition'] = defln |
|
862 | 862 | |
|
863 | 863 | # First, check whether the instance docstring is identical to the |
|
864 | 864 | # class one, and print it separately if they don't coincide. In |
|
865 | 865 | # most cases they will, but it's nice to print all the info for |
|
866 | 866 | # objects which use instance-customized docstrings. |
|
867 | 867 | if ds: |
|
868 | 868 | try: |
|
869 | 869 | cls = getattr(obj,'__class__') |
|
870 | 870 | except: |
|
871 | 871 | class_ds = None |
|
872 | 872 | else: |
|
873 | 873 | class_ds = getdoc(cls) |
|
874 | 874 | # Skip Python's auto-generated docstrings |
|
875 | 875 | if class_ds in _builtin_type_docstrings: |
|
876 | 876 | class_ds = None |
|
877 | 877 | if class_ds and ds != class_ds: |
|
878 | 878 | out['class_docstring'] = class_ds |
|
879 | 879 | |
|
880 | 880 | # Next, try to show constructor docstrings |
|
881 | 881 | try: |
|
882 | 882 | init_ds = getdoc(obj.__init__) |
|
883 | 883 | # Skip Python's auto-generated docstrings |
|
884 | 884 | if init_ds == _object_init_docstring: |
|
885 | 885 | init_ds = None |
|
886 | 886 | except AttributeError: |
|
887 | 887 | init_ds = None |
|
888 | 888 | if init_ds: |
|
889 | 889 | out['init_docstring'] = init_ds |
|
890 | 890 | |
|
891 | 891 | # Call form docstring for callable instances |
|
892 | 892 | if safe_hasattr(obj, '__call__') and not is_simple_callable(obj): |
|
893 | 893 | call_def = self._getdef(obj.__call__, oname) |
|
894 | 894 | if call_def and (call_def != out.get('definition')): |
|
895 | 895 | # it may never be the case that call def and definition differ, |
|
896 | 896 | # but don't include the same signature twice |
|
897 | 897 | out['call_def'] = call_def |
|
898 | 898 | call_ds = getdoc(obj.__call__) |
|
899 | 899 | # Skip Python's auto-generated docstrings |
|
900 | 900 | if call_ds == _func_call_docstring: |
|
901 | 901 | call_ds = None |
|
902 | 902 | if call_ds: |
|
903 | 903 | out['call_docstring'] = call_ds |
|
904 | 904 | |
|
905 | 905 | return object_info(**out) |
|
906 | 906 | |
|
907 | 907 | @staticmethod |
|
908 | 908 | def _source_contains_docstring(src, doc): |
|
909 | 909 | """ |
|
910 | 910 | Check whether the source *src* contains the docstring *doc*. |
|
911 | 911 | |
|
912 | 912 | This is is helper function to skip displaying the docstring if the |
|
913 | 913 | source already contains it, avoiding repetition of information. |
|
914 | 914 | """ |
|
915 | 915 | try: |
|
916 | 916 | def_node, = ast.parse(dedent(src)).body |
|
917 | 917 | return ast.get_docstring(def_node) == doc |
|
918 | 918 | except Exception: |
|
919 | 919 | # The source can become invalid or even non-existent (because it |
|
920 | 920 | # is re-fetched from the source file) so the above code fail in |
|
921 | 921 | # arbitrary ways. |
|
922 | 922 | return False |
|
923 | 923 | |
|
924 | 924 | def psearch(self,pattern,ns_table,ns_search=[], |
|
925 | 925 | ignore_case=False,show_all=False, *, list_types=False): |
|
926 | 926 | """Search namespaces with wildcards for objects. |
|
927 | 927 | |
|
928 | 928 | Arguments: |
|
929 | 929 | |
|
930 | 930 | - pattern: string containing shell-like wildcards to use in namespace |
|
931 | 931 | searches and optionally a type specification to narrow the search to |
|
932 | 932 | objects of that type. |
|
933 | 933 | |
|
934 | 934 | - ns_table: dict of name->namespaces for search. |
|
935 | 935 | |
|
936 | 936 | Optional arguments: |
|
937 | 937 | |
|
938 | 938 | - ns_search: list of namespace names to include in search. |
|
939 | 939 | |
|
940 | 940 | - ignore_case(False): make the search case-insensitive. |
|
941 | 941 | |
|
942 | 942 | - show_all(False): show all names, including those starting with |
|
943 | 943 | underscores. |
|
944 | 944 | |
|
945 | 945 | - list_types(False): list all available object types for object matching. |
|
946 | 946 | """ |
|
947 | 947 | #print 'ps pattern:<%r>' % pattern # dbg |
|
948 | 948 | |
|
949 | 949 | # defaults |
|
950 | 950 | type_pattern = 'all' |
|
951 | 951 | filter = '' |
|
952 | 952 | |
|
953 | 953 | # list all object types |
|
954 | 954 | if list_types: |
|
955 | 955 | page.page('\n'.join(sorted(typestr2type))) |
|
956 | 956 | return |
|
957 | 957 | |
|
958 | 958 | cmds = pattern.split() |
|
959 | 959 | len_cmds = len(cmds) |
|
960 | 960 | if len_cmds == 1: |
|
961 | 961 | # Only filter pattern given |
|
962 | 962 | filter = cmds[0] |
|
963 | 963 | elif len_cmds == 2: |
|
964 | 964 | # Both filter and type specified |
|
965 | 965 | filter,type_pattern = cmds |
|
966 | 966 | else: |
|
967 | 967 | raise ValueError('invalid argument string for psearch: <%s>' % |
|
968 | 968 | pattern) |
|
969 | 969 | |
|
970 | 970 | # filter search namespaces |
|
971 | 971 | for name in ns_search: |
|
972 | 972 | if name not in ns_table: |
|
973 | 973 | raise ValueError('invalid namespace <%s>. Valid names: %s' % |
|
974 | 974 | (name,ns_table.keys())) |
|
975 | 975 | |
|
976 | 976 | #print 'type_pattern:',type_pattern # dbg |
|
977 | 977 | search_result, namespaces_seen = set(), set() |
|
978 | 978 | for ns_name in ns_search: |
|
979 | 979 | ns = ns_table[ns_name] |
|
980 | 980 | # Normally, locals and globals are the same, so we just check one. |
|
981 | 981 | if id(ns) in namespaces_seen: |
|
982 | 982 | continue |
|
983 | 983 | namespaces_seen.add(id(ns)) |
|
984 | 984 | tmp_res = list_namespace(ns, type_pattern, filter, |
|
985 | 985 | ignore_case=ignore_case, show_all=show_all) |
|
986 | 986 | search_result.update(tmp_res) |
|
987 | 987 | |
|
988 | 988 | page.page('\n'.join(sorted(search_result))) |
|
989 | 989 | |
|
990 | 990 | |
|
991 | 991 | def _render_signature(obj_signature, obj_name) -> str: |
|
992 | 992 | """ |
|
993 | 993 | This was mostly taken from inspect.Signature.__str__. |
|
994 | 994 | Look there for the comments. |
|
995 | 995 | The only change is to add linebreaks when this gets too long. |
|
996 | 996 | """ |
|
997 | 997 | result = [] |
|
998 | 998 | pos_only = False |
|
999 | 999 | kw_only = True |
|
1000 | 1000 | for param in obj_signature.parameters.values(): |
|
1001 | 1001 | if param.kind == inspect._POSITIONAL_ONLY: |
|
1002 | 1002 | pos_only = True |
|
1003 | 1003 | elif pos_only: |
|
1004 | 1004 | result.append('/') |
|
1005 | 1005 | pos_only = False |
|
1006 | 1006 | |
|
1007 | 1007 | if param.kind == inspect._VAR_POSITIONAL: |
|
1008 | 1008 | kw_only = False |
|
1009 | 1009 | elif param.kind == inspect._KEYWORD_ONLY and kw_only: |
|
1010 | 1010 | result.append('*') |
|
1011 | 1011 | kw_only = False |
|
1012 | 1012 | |
|
1013 | 1013 | result.append(str(param)) |
|
1014 | 1014 | |
|
1015 | 1015 | if pos_only: |
|
1016 | 1016 | result.append('/') |
|
1017 | 1017 | |
|
1018 | 1018 | # add up name, parameters, braces (2), and commas |
|
1019 | 1019 | if len(obj_name) + sum(len(r) + 2 for r in result) > 75: |
|
1020 | 1020 | # This doesnβt fit behind βSignature: β in an inspect window. |
|
1021 | 1021 | rendered = '{}(\n{})'.format(obj_name, ''.join( |
|
1022 | 1022 | ' {},\n'.format(r) for r in result) |
|
1023 | 1023 | ) |
|
1024 | 1024 | else: |
|
1025 | 1025 | rendered = '{}({})'.format(obj_name, ', '.join(result)) |
|
1026 | 1026 | |
|
1027 | 1027 | if obj_signature.return_annotation is not inspect._empty: |
|
1028 | 1028 | anno = inspect.formatannotation(obj_signature.return_annotation) |
|
1029 | 1029 | rendered += ' -> {}'.format(anno) |
|
1030 | 1030 | |
|
1031 | 1031 | return rendered |
@@ -1,1111 +1,1124 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """Tests for the IPython tab-completion machinery.""" |
|
3 | 3 | |
|
4 | 4 | # Copyright (c) IPython Development Team. |
|
5 | 5 | # Distributed under the terms of the Modified BSD License. |
|
6 | 6 | |
|
7 | 7 | import os |
|
8 | 8 | import sys |
|
9 | 9 | import textwrap |
|
10 | 10 | import unittest |
|
11 | 11 | |
|
12 | 12 | from contextlib import contextmanager |
|
13 | 13 | |
|
14 | 14 | import nose.tools as nt |
|
15 | import pytest | |
|
15 | 16 | |
|
16 | 17 | from traitlets.config.loader import Config |
|
17 | 18 | from IPython import get_ipython |
|
18 | 19 | from IPython.core import completer |
|
19 | 20 | from IPython.external import decorators |
|
20 | 21 | from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory |
|
21 | 22 | from IPython.utils.generics import complete_object |
|
22 | 23 | from IPython.testing import decorators as dec |
|
23 | 24 | |
|
24 | 25 | from IPython.core.completer import ( |
|
25 | 26 | Completion, |
|
26 | 27 | provisionalcompleter, |
|
27 | 28 | match_dict_keys, |
|
28 | 29 | _deduplicate_completions, |
|
29 | 30 | ) |
|
30 | 31 | from nose.tools import assert_in, assert_not_in |
|
31 | 32 | |
|
33 | if sys.version_info >= (3, 10): | |
|
34 | import jedi | |
|
35 | from pkg_resources import parse_version | |
|
36 | ||
|
37 | # Requires https://github.com/davidhalter/jedi/pull/1795 | |
|
38 | jedi_issue = parse_version(jedi.__version__) <= parse_version("0.18.0") | |
|
39 | else: | |
|
40 | jedi_issue = False | |
|
41 | ||
|
32 | 42 | # ----------------------------------------------------------------------------- |
|
33 | 43 | # Test functions |
|
34 | 44 | # ----------------------------------------------------------------------------- |
|
35 | 45 | |
|
36 | 46 | |
|
37 | 47 | @contextmanager |
|
38 | 48 | def greedy_completion(): |
|
39 | 49 | ip = get_ipython() |
|
40 | 50 | greedy_original = ip.Completer.greedy |
|
41 | 51 | try: |
|
42 | 52 | ip.Completer.greedy = True |
|
43 | 53 | yield |
|
44 | 54 | finally: |
|
45 | 55 | ip.Completer.greedy = greedy_original |
|
46 | 56 | |
|
47 | 57 | |
|
48 | 58 | def test_protect_filename(): |
|
49 | 59 | if sys.platform == "win32": |
|
50 | 60 | pairs = [ |
|
51 | 61 | ("abc", "abc"), |
|
52 | 62 | (" abc", '" abc"'), |
|
53 | 63 | ("a bc", '"a bc"'), |
|
54 | 64 | ("a bc", '"a bc"'), |
|
55 | 65 | (" bc", '" bc"'), |
|
56 | 66 | ] |
|
57 | 67 | else: |
|
58 | 68 | pairs = [ |
|
59 | 69 | ("abc", "abc"), |
|
60 | 70 | (" abc", r"\ abc"), |
|
61 | 71 | ("a bc", r"a\ bc"), |
|
62 | 72 | ("a bc", r"a\ \ bc"), |
|
63 | 73 | (" bc", r"\ \ bc"), |
|
64 | 74 | # On posix, we also protect parens and other special characters. |
|
65 | 75 | ("a(bc", r"a\(bc"), |
|
66 | 76 | ("a)bc", r"a\)bc"), |
|
67 | 77 | ("a( )bc", r"a\(\ \)bc"), |
|
68 | 78 | ("a[1]bc", r"a\[1\]bc"), |
|
69 | 79 | ("a{1}bc", r"a\{1\}bc"), |
|
70 | 80 | ("a#bc", r"a\#bc"), |
|
71 | 81 | ("a?bc", r"a\?bc"), |
|
72 | 82 | ("a=bc", r"a\=bc"), |
|
73 | 83 | ("a\\bc", r"a\\bc"), |
|
74 | 84 | ("a|bc", r"a\|bc"), |
|
75 | 85 | ("a;bc", r"a\;bc"), |
|
76 | 86 | ("a:bc", r"a\:bc"), |
|
77 | 87 | ("a'bc", r"a\'bc"), |
|
78 | 88 | ("a*bc", r"a\*bc"), |
|
79 | 89 | ('a"bc', r"a\"bc"), |
|
80 | 90 | ("a^bc", r"a\^bc"), |
|
81 | 91 | ("a&bc", r"a\&bc"), |
|
82 | 92 | ] |
|
83 | 93 | # run the actual tests |
|
84 | 94 | for s1, s2 in pairs: |
|
85 | 95 | s1p = completer.protect_filename(s1) |
|
86 | 96 | nt.assert_equal(s1p, s2) |
|
87 | 97 | |
|
88 | 98 | |
|
89 | 99 | def check_line_split(splitter, test_specs): |
|
90 | 100 | for part1, part2, split in test_specs: |
|
91 | 101 | cursor_pos = len(part1) |
|
92 | 102 | line = part1 + part2 |
|
93 | 103 | out = splitter.split_line(line, cursor_pos) |
|
94 | 104 | nt.assert_equal(out, split) |
|
95 | 105 | |
|
96 | 106 | |
|
97 | 107 | def test_line_split(): |
|
98 | 108 | """Basic line splitter test with default specs.""" |
|
99 | 109 | sp = completer.CompletionSplitter() |
|
100 | 110 | # The format of the test specs is: part1, part2, expected answer. Parts 1 |
|
101 | 111 | # and 2 are joined into the 'line' sent to the splitter, as if the cursor |
|
102 | 112 | # was at the end of part1. So an empty part2 represents someone hitting |
|
103 | 113 | # tab at the end of the line, the most common case. |
|
104 | 114 | t = [ |
|
105 | 115 | ("run some/scrip", "", "some/scrip"), |
|
106 | 116 | ("run scripts/er", "ror.py foo", "scripts/er"), |
|
107 | 117 | ("echo $HOM", "", "HOM"), |
|
108 | 118 | ("print sys.pa", "", "sys.pa"), |
|
109 | 119 | ("print(sys.pa", "", "sys.pa"), |
|
110 | 120 | ("execfile('scripts/er", "", "scripts/er"), |
|
111 | 121 | ("a[x.", "", "x."), |
|
112 | 122 | ("a[x.", "y", "x."), |
|
113 | 123 | ('cd "some_file/', "", "some_file/"), |
|
114 | 124 | ] |
|
115 | 125 | check_line_split(sp, t) |
|
116 | 126 | # Ensure splitting works OK with unicode by re-running the tests with |
|
117 | 127 | # all inputs turned into unicode |
|
118 | 128 | check_line_split(sp, [map(str, p) for p in t]) |
|
119 | 129 | |
|
120 | 130 | |
|
121 | 131 | class NamedInstanceMetaclass(type): |
|
122 | 132 | def __getitem__(cls, item): |
|
123 | 133 | return cls.get_instance(item) |
|
124 | 134 | |
|
125 | 135 | |
|
126 | 136 | class NamedInstanceClass(metaclass=NamedInstanceMetaclass): |
|
127 | 137 | def __init__(self, name): |
|
128 | 138 | if not hasattr(self.__class__, "instances"): |
|
129 | 139 | self.__class__.instances = {} |
|
130 | 140 | self.__class__.instances[name] = self |
|
131 | 141 | |
|
132 | 142 | @classmethod |
|
133 | 143 | def _ipython_key_completions_(cls): |
|
134 | 144 | return cls.instances.keys() |
|
135 | 145 | |
|
136 | 146 | @classmethod |
|
137 | 147 | def get_instance(cls, name): |
|
138 | 148 | return cls.instances[name] |
|
139 | 149 | |
|
140 | 150 | |
|
141 | 151 | class KeyCompletable: |
|
142 | 152 | def __init__(self, things=()): |
|
143 | 153 | self.things = things |
|
144 | 154 | |
|
145 | 155 | def _ipython_key_completions_(self): |
|
146 | 156 | return list(self.things) |
|
147 | 157 | |
|
148 | 158 | |
|
149 | 159 | class TestCompleter(unittest.TestCase): |
|
150 | 160 | def setUp(self): |
|
151 | 161 | """ |
|
152 | 162 | We want to silence all PendingDeprecationWarning when testing the completer |
|
153 | 163 | """ |
|
154 | 164 | self._assertwarns = self.assertWarns(PendingDeprecationWarning) |
|
155 | 165 | self._assertwarns.__enter__() |
|
156 | 166 | |
|
157 | 167 | def tearDown(self): |
|
158 | 168 | try: |
|
159 | 169 | self._assertwarns.__exit__(None, None, None) |
|
160 | 170 | except AssertionError: |
|
161 | 171 | pass |
|
162 | 172 | |
|
163 | 173 | def test_custom_completion_error(self): |
|
164 | 174 | """Test that errors from custom attribute completers are silenced.""" |
|
165 | 175 | ip = get_ipython() |
|
166 | 176 | |
|
167 | 177 | class A: |
|
168 | 178 | pass |
|
169 | 179 | |
|
170 | 180 | ip.user_ns["x"] = A() |
|
171 | 181 | |
|
172 | 182 | @complete_object.register(A) |
|
173 | 183 | def complete_A(a, existing_completions): |
|
174 | 184 | raise TypeError("this should be silenced") |
|
175 | 185 | |
|
176 | 186 | ip.complete("x.") |
|
177 | 187 | |
|
178 | 188 | def test_custom_completion_ordering(self): |
|
179 | 189 | """Test that errors from custom attribute completers are silenced.""" |
|
180 | 190 | ip = get_ipython() |
|
181 | 191 | |
|
182 | 192 | _, matches = ip.complete('in') |
|
183 | 193 | assert matches.index('input') < matches.index('int') |
|
184 | 194 | |
|
185 | 195 | def complete_example(a): |
|
186 | 196 | return ['example2', 'example1'] |
|
187 | 197 | |
|
188 | 198 | ip.Completer.custom_completers.add_re('ex*', complete_example) |
|
189 | 199 | _, matches = ip.complete('ex') |
|
190 | 200 | assert matches.index('example2') < matches.index('example1') |
|
191 | 201 | |
|
192 | 202 | def test_unicode_completions(self): |
|
193 | 203 | ip = get_ipython() |
|
194 | 204 | # Some strings that trigger different types of completion. Check them both |
|
195 | 205 | # in str and unicode forms |
|
196 | 206 | s = ["ru", "%ru", "cd /", "floa", "float(x)/"] |
|
197 | 207 | for t in s + list(map(str, s)): |
|
198 | 208 | # We don't need to check exact completion values (they may change |
|
199 | 209 | # depending on the state of the namespace, but at least no exceptions |
|
200 | 210 | # should be thrown and the return value should be a pair of text, list |
|
201 | 211 | # values. |
|
202 | 212 | text, matches = ip.complete(t) |
|
203 | 213 | nt.assert_true(isinstance(text, str)) |
|
204 | 214 | nt.assert_true(isinstance(matches, list)) |
|
205 | 215 | |
|
206 | 216 | def test_latex_completions(self): |
|
207 | 217 | from IPython.core.latex_symbols import latex_symbols |
|
208 | 218 | import random |
|
209 | 219 | |
|
210 | 220 | ip = get_ipython() |
|
211 | 221 | # Test some random unicode symbols |
|
212 | 222 | keys = random.sample(latex_symbols.keys(), 10) |
|
213 | 223 | for k in keys: |
|
214 | 224 | text, matches = ip.complete(k) |
|
215 | 225 | nt.assert_equal(len(matches), 1) |
|
216 | 226 | nt.assert_equal(text, k) |
|
217 | 227 | nt.assert_equal(matches[0], latex_symbols[k]) |
|
218 | 228 | # Test a more complex line |
|
219 | 229 | text, matches = ip.complete("print(\\alpha") |
|
220 | 230 | nt.assert_equal(text, "\\alpha") |
|
221 | 231 | nt.assert_equal(matches[0], latex_symbols["\\alpha"]) |
|
222 | 232 | # Test multiple matching latex symbols |
|
223 | 233 | text, matches = ip.complete("\\al") |
|
224 | 234 | nt.assert_in("\\alpha", matches) |
|
225 | 235 | nt.assert_in("\\aleph", matches) |
|
226 | 236 | |
|
227 | 237 | def test_latex_no_results(self): |
|
228 | 238 | """ |
|
229 | 239 | forward latex should really return nothing in either field if nothing is found. |
|
230 | 240 | """ |
|
231 | 241 | ip = get_ipython() |
|
232 | 242 | text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing") |
|
233 | 243 | nt.assert_equal(text, "") |
|
234 | 244 | nt.assert_equal(matches, []) |
|
235 | 245 | |
|
236 | 246 | def test_back_latex_completion(self): |
|
237 | 247 | ip = get_ipython() |
|
238 | 248 | |
|
239 | 249 | # do not return more than 1 matches fro \beta, only the latex one. |
|
240 | 250 | name, matches = ip.complete("\\Ξ²") |
|
241 | 251 | nt.assert_equal(matches, ['\\beta']) |
|
242 | 252 | |
|
243 | 253 | def test_back_unicode_completion(self): |
|
244 | 254 | ip = get_ipython() |
|
245 | 255 | |
|
246 | 256 | name, matches = ip.complete("\\β €") |
|
247 | 257 | nt.assert_equal(matches, ["\\ROMAN NUMERAL FIVE"]) |
|
248 | 258 | |
|
249 | 259 | def test_forward_unicode_completion(self): |
|
250 | 260 | ip = get_ipython() |
|
251 | 261 | |
|
252 | 262 | name, matches = ip.complete("\\ROMAN NUMERAL FIVE") |
|
253 | 263 | nt.assert_equal(len(matches), 1) |
|
254 | 264 | nt.assert_equal(matches[0], "β €") |
|
255 | 265 | |
|
256 | 266 | @nt.nottest # now we have a completion for \jmath |
|
257 | 267 | @decorators.knownfailureif( |
|
258 | 268 | sys.platform == "win32", "Fails if there is a C:\\j... path" |
|
259 | 269 | ) |
|
260 | 270 | def test_no_ascii_back_completion(self): |
|
261 | 271 | ip = get_ipython() |
|
262 | 272 | with TemporaryWorkingDirectory(): # Avoid any filename completions |
|
263 | 273 | # single ascii letter that don't have yet completions |
|
264 | 274 | for letter in "jJ": |
|
265 | 275 | name, matches = ip.complete("\\" + letter) |
|
266 | 276 | nt.assert_equal(matches, []) |
|
267 | 277 | |
|
268 | 278 | class CompletionSplitterTestCase(unittest.TestCase): |
|
269 | 279 | def setUp(self): |
|
270 | 280 | self.sp = completer.CompletionSplitter() |
|
271 | 281 | |
|
272 | 282 | def test_delim_setting(self): |
|
273 | 283 | self.sp.delims = " " |
|
274 | 284 | nt.assert_equal(self.sp.delims, " ") |
|
275 | 285 | nt.assert_equal(self.sp._delim_expr, r"[\ ]") |
|
276 | 286 | |
|
277 | 287 | def test_spaces(self): |
|
278 | 288 | """Test with only spaces as split chars.""" |
|
279 | 289 | self.sp.delims = " " |
|
280 | 290 | t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")] |
|
281 | 291 | check_line_split(self.sp, t) |
|
282 | 292 | |
|
283 | 293 | def test_has_open_quotes1(self): |
|
284 | 294 | for s in ["'", "'''", "'hi' '"]: |
|
285 | 295 | nt.assert_equal(completer.has_open_quotes(s), "'") |
|
286 | 296 | |
|
287 | 297 | def test_has_open_quotes2(self): |
|
288 | 298 | for s in ['"', '"""', '"hi" "']: |
|
289 | 299 | nt.assert_equal(completer.has_open_quotes(s), '"') |
|
290 | 300 | |
|
291 | 301 | def test_has_open_quotes3(self): |
|
292 | 302 | for s in ["''", "''' '''", "'hi' 'ipython'"]: |
|
293 | 303 | nt.assert_false(completer.has_open_quotes(s)) |
|
294 | 304 | |
|
295 | 305 | def test_has_open_quotes4(self): |
|
296 | 306 | for s in ['""', '""" """', '"hi" "ipython"']: |
|
297 | 307 | nt.assert_false(completer.has_open_quotes(s)) |
|
298 | 308 | |
|
299 | 309 | @decorators.knownfailureif( |
|
300 | 310 | sys.platform == "win32", "abspath completions fail on Windows" |
|
301 | 311 | ) |
|
302 | 312 | def test_abspath_file_completions(self): |
|
303 | 313 | ip = get_ipython() |
|
304 | 314 | with TemporaryDirectory() as tmpdir: |
|
305 | 315 | prefix = os.path.join(tmpdir, "foo") |
|
306 | 316 | suffixes = ["1", "2"] |
|
307 | 317 | names = [prefix + s for s in suffixes] |
|
308 | 318 | for n in names: |
|
309 | 319 | open(n, "w").close() |
|
310 | 320 | |
|
311 | 321 | # Check simple completion |
|
312 | 322 | c = ip.complete(prefix)[1] |
|
313 | 323 | nt.assert_equal(c, names) |
|
314 | 324 | |
|
315 | 325 | # Now check with a function call |
|
316 | 326 | cmd = 'a = f("%s' % prefix |
|
317 | 327 | c = ip.complete(prefix, cmd)[1] |
|
318 | 328 | comp = [prefix + s for s in suffixes] |
|
319 | 329 | nt.assert_equal(c, comp) |
|
320 | 330 | |
|
321 | 331 | def test_local_file_completions(self): |
|
322 | 332 | ip = get_ipython() |
|
323 | 333 | with TemporaryWorkingDirectory(): |
|
324 | 334 | prefix = "./foo" |
|
325 | 335 | suffixes = ["1", "2"] |
|
326 | 336 | names = [prefix + s for s in suffixes] |
|
327 | 337 | for n in names: |
|
328 | 338 | open(n, "w").close() |
|
329 | 339 | |
|
330 | 340 | # Check simple completion |
|
331 | 341 | c = ip.complete(prefix)[1] |
|
332 | 342 | nt.assert_equal(c, names) |
|
333 | 343 | |
|
334 | 344 | # Now check with a function call |
|
335 | 345 | cmd = 'a = f("%s' % prefix |
|
336 | 346 | c = ip.complete(prefix, cmd)[1] |
|
337 | 347 | comp = {prefix + s for s in suffixes} |
|
338 | 348 | nt.assert_true(comp.issubset(set(c))) |
|
339 | 349 | |
|
340 | 350 | def test_quoted_file_completions(self): |
|
341 | 351 | ip = get_ipython() |
|
342 | 352 | with TemporaryWorkingDirectory(): |
|
343 | 353 | name = "foo'bar" |
|
344 | 354 | open(name, "w").close() |
|
345 | 355 | |
|
346 | 356 | # Don't escape Windows |
|
347 | 357 | escaped = name if sys.platform == "win32" else "foo\\'bar" |
|
348 | 358 | |
|
349 | 359 | # Single quote matches embedded single quote |
|
350 | 360 | text = "open('foo" |
|
351 | 361 | c = ip.Completer._complete( |
|
352 | 362 | cursor_line=0, cursor_pos=len(text), full_text=text |
|
353 | 363 | )[1] |
|
354 | 364 | nt.assert_equal(c, [escaped]) |
|
355 | 365 | |
|
356 | 366 | # Double quote requires no escape |
|
357 | 367 | text = 'open("foo' |
|
358 | 368 | c = ip.Completer._complete( |
|
359 | 369 | cursor_line=0, cursor_pos=len(text), full_text=text |
|
360 | 370 | )[1] |
|
361 | 371 | nt.assert_equal(c, [name]) |
|
362 | 372 | |
|
363 | 373 | # No quote requires an escape |
|
364 | 374 | text = "%ls foo" |
|
365 | 375 | c = ip.Completer._complete( |
|
366 | 376 | cursor_line=0, cursor_pos=len(text), full_text=text |
|
367 | 377 | )[1] |
|
368 | 378 | nt.assert_equal(c, [escaped]) |
|
369 | 379 | |
|
370 | 380 | def test_all_completions_dups(self): |
|
371 | 381 | """ |
|
372 | 382 | Make sure the output of `IPCompleter.all_completions` does not have |
|
373 | 383 | duplicated prefixes. |
|
374 | 384 | """ |
|
375 | 385 | ip = get_ipython() |
|
376 | 386 | c = ip.Completer |
|
377 | 387 | ip.ex("class TestClass():\n\ta=1\n\ta1=2") |
|
378 | 388 | for jedi_status in [True, False]: |
|
379 | 389 | with provisionalcompleter(): |
|
380 | 390 | ip.Completer.use_jedi = jedi_status |
|
381 | 391 | matches = c.all_completions("TestCl") |
|
382 | 392 | assert matches == ['TestClass'], jedi_status |
|
383 | 393 | matches = c.all_completions("TestClass.") |
|
394 | if jedi_status and jedi_issue: | |
|
395 | continue | |
|
384 | 396 | assert len(matches) > 2, jedi_status |
|
385 | 397 | matches = c.all_completions("TestClass.a") |
|
386 | 398 | assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status |
|
387 | 399 | |
|
388 | 400 | def test_jedi(self): |
|
389 | 401 | """ |
|
390 | 402 | A couple of issue we had with Jedi |
|
391 | 403 | """ |
|
392 | 404 | ip = get_ipython() |
|
393 | 405 | |
|
394 | 406 | def _test_complete(reason, s, comp, start=None, end=None): |
|
395 | 407 | l = len(s) |
|
396 | 408 | start = start if start is not None else l |
|
397 | 409 | end = end if end is not None else l |
|
398 | 410 | with provisionalcompleter(): |
|
399 | 411 | ip.Completer.use_jedi = True |
|
400 | 412 | completions = set(ip.Completer.completions(s, l)) |
|
401 | 413 | ip.Completer.use_jedi = False |
|
402 | 414 | assert_in(Completion(start, end, comp), completions, reason) |
|
403 | 415 | |
|
404 | 416 | def _test_not_complete(reason, s, comp): |
|
405 | 417 | l = len(s) |
|
406 | 418 | with provisionalcompleter(): |
|
407 | 419 | ip.Completer.use_jedi = True |
|
408 | 420 | completions = set(ip.Completer.completions(s, l)) |
|
409 | 421 | ip.Completer.use_jedi = False |
|
410 | 422 | assert_not_in(Completion(l, l, comp), completions, reason) |
|
411 | 423 | |
|
412 | 424 | import jedi |
|
413 | 425 | |
|
414 | 426 | jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3]) |
|
415 | 427 | if jedi_version > (0, 10): |
|
416 | 428 | yield _test_complete, "jedi >0.9 should complete and not crash", "a=1;a.", "real" |
|
417 | 429 | yield _test_complete, "can infer first argument", 'a=(1,"foo");a[0].', "real" |
|
418 | 430 | yield _test_complete, "can infer second argument", 'a=(1,"foo");a[1].', "capitalize" |
|
419 | 431 | yield _test_complete, "cover duplicate completions", "im", "import", 0, 2 |
|
420 | 432 | |
|
421 | 433 | yield _test_not_complete, "does not mix types", 'a=(1,"foo");a[0].', "capitalize" |
|
422 | 434 | |
|
423 | 435 | def test_completion_have_signature(self): |
|
424 | 436 | """ |
|
425 | 437 | Lets make sure jedi is capable of pulling out the signature of the function we are completing. |
|
426 | 438 | """ |
|
427 | 439 | ip = get_ipython() |
|
428 | 440 | with provisionalcompleter(): |
|
429 | 441 | ip.Completer.use_jedi = True |
|
430 | 442 | completions = ip.Completer.completions("ope", 3) |
|
431 | 443 | c = next(completions) # should be `open` |
|
432 | 444 | ip.Completer.use_jedi = False |
|
433 | 445 | assert "file" in c.signature, "Signature of function was not found by completer" |
|
434 | 446 | assert ( |
|
435 | 447 | "encoding" in c.signature |
|
436 | 448 | ), "Signature of function was not found by completer" |
|
437 | 449 | |
|
450 | @pytest.mark.xfail(jedi_issue, reason="Known failure on jedi<=0.18.0") | |
|
438 | 451 | def test_deduplicate_completions(self): |
|
439 | 452 | """ |
|
440 | 453 | Test that completions are correctly deduplicated (even if ranges are not the same) |
|
441 | 454 | """ |
|
442 | 455 | ip = get_ipython() |
|
443 | 456 | ip.ex( |
|
444 | 457 | textwrap.dedent( |
|
445 | 458 | """ |
|
446 | 459 | class Z: |
|
447 | 460 | zoo = 1 |
|
448 | 461 | """ |
|
449 | 462 | ) |
|
450 | 463 | ) |
|
451 | 464 | with provisionalcompleter(): |
|
452 | 465 | ip.Completer.use_jedi = True |
|
453 | 466 | l = list( |
|
454 | 467 | _deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3)) |
|
455 | 468 | ) |
|
456 | 469 | ip.Completer.use_jedi = False |
|
457 | 470 | |
|
458 | 471 | assert len(l) == 1, "Completions (Z.z<tab>) correctly deduplicate: %s " % l |
|
459 | 472 | assert l[0].text == "zoo" # and not `it.accumulate` |
|
460 | 473 | |
|
461 | 474 | def test_greedy_completions(self): |
|
462 | 475 | """ |
|
463 | 476 | Test the capability of the Greedy completer. |
|
464 | 477 | |
|
465 | 478 | Most of the test here does not really show off the greedy completer, for proof |
|
466 | 479 | each of the text below now pass with Jedi. The greedy completer is capable of more. |
|
467 | 480 | |
|
468 | 481 | See the :any:`test_dict_key_completion_contexts` |
|
469 | 482 | |
|
470 | 483 | """ |
|
471 | 484 | ip = get_ipython() |
|
472 | 485 | ip.ex("a=list(range(5))") |
|
473 | 486 | _, c = ip.complete(".", line="a[0].") |
|
474 | 487 | nt.assert_false(".real" in c, "Shouldn't have completed on a[0]: %s" % c) |
|
475 | 488 | |
|
476 | 489 | def _(line, cursor_pos, expect, message, completion): |
|
477 | 490 | with greedy_completion(), provisionalcompleter(): |
|
478 | 491 | ip.Completer.use_jedi = False |
|
479 | 492 | _, c = ip.complete(".", line=line, cursor_pos=cursor_pos) |
|
480 | 493 | nt.assert_in(expect, c, message % c) |
|
481 | 494 | |
|
482 | 495 | ip.Completer.use_jedi = True |
|
483 | 496 | with provisionalcompleter(): |
|
484 | 497 | completions = ip.Completer.completions(line, cursor_pos) |
|
485 | 498 | nt.assert_in(completion, completions) |
|
486 | 499 | |
|
487 | 500 | with provisionalcompleter(): |
|
488 | 501 | yield _, "a[0].", 5, "a[0].real", "Should have completed on a[0].: %s", Completion( |
|
489 | 502 | 5, 5, "real" |
|
490 | 503 | ) |
|
491 | 504 | yield _, "a[0].r", 6, "a[0].real", "Should have completed on a[0].r: %s", Completion( |
|
492 | 505 | 5, 6, "real" |
|
493 | 506 | ) |
|
494 | 507 | |
|
495 | 508 | yield _, "a[0].from_", 10, "a[0].from_bytes", "Should have completed on a[0].from_: %s", Completion( |
|
496 | 509 | 5, 10, "from_bytes" |
|
497 | 510 | ) |
|
498 | 511 | |
|
499 | 512 | def test_omit__names(self): |
|
500 | 513 | # also happens to test IPCompleter as a configurable |
|
501 | 514 | ip = get_ipython() |
|
502 | 515 | ip._hidden_attr = 1 |
|
503 | 516 | ip._x = {} |
|
504 | 517 | c = ip.Completer |
|
505 | 518 | ip.ex("ip=get_ipython()") |
|
506 | 519 | cfg = Config() |
|
507 | 520 | cfg.IPCompleter.omit__names = 0 |
|
508 | 521 | c.update_config(cfg) |
|
509 | 522 | with provisionalcompleter(): |
|
510 | 523 | c.use_jedi = False |
|
511 | 524 | s, matches = c.complete("ip.") |
|
512 | 525 | nt.assert_in("ip.__str__", matches) |
|
513 | 526 | nt.assert_in("ip._hidden_attr", matches) |
|
514 | 527 | |
|
515 | 528 | # c.use_jedi = True |
|
516 | 529 | # completions = set(c.completions('ip.', 3)) |
|
517 | 530 | # nt.assert_in(Completion(3, 3, '__str__'), completions) |
|
518 | 531 | # nt.assert_in(Completion(3,3, "_hidden_attr"), completions) |
|
519 | 532 | |
|
520 | 533 | cfg = Config() |
|
521 | 534 | cfg.IPCompleter.omit__names = 1 |
|
522 | 535 | c.update_config(cfg) |
|
523 | 536 | with provisionalcompleter(): |
|
524 | 537 | c.use_jedi = False |
|
525 | 538 | s, matches = c.complete("ip.") |
|
526 | 539 | nt.assert_not_in("ip.__str__", matches) |
|
527 | 540 | # nt.assert_in('ip._hidden_attr', matches) |
|
528 | 541 | |
|
529 | 542 | # c.use_jedi = True |
|
530 | 543 | # completions = set(c.completions('ip.', 3)) |
|
531 | 544 | # nt.assert_not_in(Completion(3,3,'__str__'), completions) |
|
532 | 545 | # nt.assert_in(Completion(3,3, "_hidden_attr"), completions) |
|
533 | 546 | |
|
534 | 547 | cfg = Config() |
|
535 | 548 | cfg.IPCompleter.omit__names = 2 |
|
536 | 549 | c.update_config(cfg) |
|
537 | 550 | with provisionalcompleter(): |
|
538 | 551 | c.use_jedi = False |
|
539 | 552 | s, matches = c.complete("ip.") |
|
540 | 553 | nt.assert_not_in("ip.__str__", matches) |
|
541 | 554 | nt.assert_not_in("ip._hidden_attr", matches) |
|
542 | 555 | |
|
543 | 556 | # c.use_jedi = True |
|
544 | 557 | # completions = set(c.completions('ip.', 3)) |
|
545 | 558 | # nt.assert_not_in(Completion(3,3,'__str__'), completions) |
|
546 | 559 | # nt.assert_not_in(Completion(3,3, "_hidden_attr"), completions) |
|
547 | 560 | |
|
548 | 561 | with provisionalcompleter(): |
|
549 | 562 | c.use_jedi = False |
|
550 | 563 | s, matches = c.complete("ip._x.") |
|
551 | 564 | nt.assert_in("ip._x.keys", matches) |
|
552 | 565 | |
|
553 | 566 | # c.use_jedi = True |
|
554 | 567 | # completions = set(c.completions('ip._x.', 6)) |
|
555 | 568 | # nt.assert_in(Completion(6,6, "keys"), completions) |
|
556 | 569 | |
|
557 | 570 | del ip._hidden_attr |
|
558 | 571 | del ip._x |
|
559 | 572 | |
|
560 | 573 | def test_limit_to__all__False_ok(self): |
|
561 | 574 | """ |
|
562 | 575 | Limit to all is deprecated, once we remove it this test can go away. |
|
563 | 576 | """ |
|
564 | 577 | ip = get_ipython() |
|
565 | 578 | c = ip.Completer |
|
566 | 579 | c.use_jedi = False |
|
567 | 580 | ip.ex("class D: x=24") |
|
568 | 581 | ip.ex("d=D()") |
|
569 | 582 | cfg = Config() |
|
570 | 583 | cfg.IPCompleter.limit_to__all__ = False |
|
571 | 584 | c.update_config(cfg) |
|
572 | 585 | s, matches = c.complete("d.") |
|
573 | 586 | nt.assert_in("d.x", matches) |
|
574 | 587 | |
|
575 | 588 | def test_get__all__entries_ok(self): |
|
576 | 589 | class A: |
|
577 | 590 | __all__ = ["x", 1] |
|
578 | 591 | |
|
579 | 592 | words = completer.get__all__entries(A()) |
|
580 | 593 | nt.assert_equal(words, ["x"]) |
|
581 | 594 | |
|
582 | 595 | def test_get__all__entries_no__all__ok(self): |
|
583 | 596 | class A: |
|
584 | 597 | pass |
|
585 | 598 | |
|
586 | 599 | words = completer.get__all__entries(A()) |
|
587 | 600 | nt.assert_equal(words, []) |
|
588 | 601 | |
|
589 | 602 | def test_func_kw_completions(self): |
|
590 | 603 | ip = get_ipython() |
|
591 | 604 | c = ip.Completer |
|
592 | 605 | c.use_jedi = False |
|
593 | 606 | ip.ex("def myfunc(a=1,b=2): return a+b") |
|
594 | 607 | s, matches = c.complete(None, "myfunc(1,b") |
|
595 | 608 | nt.assert_in("b=", matches) |
|
596 | 609 | # Simulate completing with cursor right after b (pos==10): |
|
597 | 610 | s, matches = c.complete(None, "myfunc(1,b)", 10) |
|
598 | 611 | nt.assert_in("b=", matches) |
|
599 | 612 | s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b') |
|
600 | 613 | nt.assert_in("b=", matches) |
|
601 | 614 | # builtin function |
|
602 | 615 | s, matches = c.complete(None, "min(k, k") |
|
603 | 616 | nt.assert_in("key=", matches) |
|
604 | 617 | |
|
605 | 618 | def test_default_arguments_from_docstring(self): |
|
606 | 619 | ip = get_ipython() |
|
607 | 620 | c = ip.Completer |
|
608 | 621 | kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value") |
|
609 | 622 | nt.assert_equal(kwd, ["key"]) |
|
610 | 623 | # with cython type etc |
|
611 | 624 | kwd = c._default_arguments_from_docstring( |
|
612 | 625 | "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n" |
|
613 | 626 | ) |
|
614 | 627 | nt.assert_equal(kwd, ["ncall", "resume", "nsplit"]) |
|
615 | 628 | # white spaces |
|
616 | 629 | kwd = c._default_arguments_from_docstring( |
|
617 | 630 | "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n" |
|
618 | 631 | ) |
|
619 | 632 | nt.assert_equal(kwd, ["ncall", "resume", "nsplit"]) |
|
620 | 633 | |
|
621 | 634 | def test_line_magics(self): |
|
622 | 635 | ip = get_ipython() |
|
623 | 636 | c = ip.Completer |
|
624 | 637 | s, matches = c.complete(None, "lsmag") |
|
625 | 638 | nt.assert_in("%lsmagic", matches) |
|
626 | 639 | s, matches = c.complete(None, "%lsmag") |
|
627 | 640 | nt.assert_in("%lsmagic", matches) |
|
628 | 641 | |
|
629 | 642 | def test_cell_magics(self): |
|
630 | 643 | from IPython.core.magic import register_cell_magic |
|
631 | 644 | |
|
632 | 645 | @register_cell_magic |
|
633 | 646 | def _foo_cellm(line, cell): |
|
634 | 647 | pass |
|
635 | 648 | |
|
636 | 649 | ip = get_ipython() |
|
637 | 650 | c = ip.Completer |
|
638 | 651 | |
|
639 | 652 | s, matches = c.complete(None, "_foo_ce") |
|
640 | 653 | nt.assert_in("%%_foo_cellm", matches) |
|
641 | 654 | s, matches = c.complete(None, "%%_foo_ce") |
|
642 | 655 | nt.assert_in("%%_foo_cellm", matches) |
|
643 | 656 | |
|
644 | 657 | def test_line_cell_magics(self): |
|
645 | 658 | from IPython.core.magic import register_line_cell_magic |
|
646 | 659 | |
|
647 | 660 | @register_line_cell_magic |
|
648 | 661 | def _bar_cellm(line, cell): |
|
649 | 662 | pass |
|
650 | 663 | |
|
651 | 664 | ip = get_ipython() |
|
652 | 665 | c = ip.Completer |
|
653 | 666 | |
|
654 | 667 | # The policy here is trickier, see comments in completion code. The |
|
655 | 668 | # returned values depend on whether the user passes %% or not explicitly, |
|
656 | 669 | # and this will show a difference if the same name is both a line and cell |
|
657 | 670 | # magic. |
|
658 | 671 | s, matches = c.complete(None, "_bar_ce") |
|
659 | 672 | nt.assert_in("%_bar_cellm", matches) |
|
660 | 673 | nt.assert_in("%%_bar_cellm", matches) |
|
661 | 674 | s, matches = c.complete(None, "%_bar_ce") |
|
662 | 675 | nt.assert_in("%_bar_cellm", matches) |
|
663 | 676 | nt.assert_in("%%_bar_cellm", matches) |
|
664 | 677 | s, matches = c.complete(None, "%%_bar_ce") |
|
665 | 678 | nt.assert_not_in("%_bar_cellm", matches) |
|
666 | 679 | nt.assert_in("%%_bar_cellm", matches) |
|
667 | 680 | |
|
668 | 681 | def test_magic_completion_order(self): |
|
669 | 682 | ip = get_ipython() |
|
670 | 683 | c = ip.Completer |
|
671 | 684 | |
|
672 | 685 | # Test ordering of line and cell magics. |
|
673 | 686 | text, matches = c.complete("timeit") |
|
674 | 687 | nt.assert_equal(matches, ["%timeit", "%%timeit"]) |
|
675 | 688 | |
|
676 | 689 | def test_magic_completion_shadowing(self): |
|
677 | 690 | ip = get_ipython() |
|
678 | 691 | c = ip.Completer |
|
679 | 692 | c.use_jedi = False |
|
680 | 693 | |
|
681 | 694 | # Before importing matplotlib, %matplotlib magic should be the only option. |
|
682 | 695 | text, matches = c.complete("mat") |
|
683 | 696 | nt.assert_equal(matches, ["%matplotlib"]) |
|
684 | 697 | |
|
685 | 698 | # The newly introduced name should shadow the magic. |
|
686 | 699 | ip.run_cell("matplotlib = 1") |
|
687 | 700 | text, matches = c.complete("mat") |
|
688 | 701 | nt.assert_equal(matches, ["matplotlib"]) |
|
689 | 702 | |
|
690 | 703 | # After removing matplotlib from namespace, the magic should again be |
|
691 | 704 | # the only option. |
|
692 | 705 | del ip.user_ns["matplotlib"] |
|
693 | 706 | text, matches = c.complete("mat") |
|
694 | 707 | nt.assert_equal(matches, ["%matplotlib"]) |
|
695 | 708 | |
|
696 | 709 | def test_magic_completion_shadowing_explicit(self): |
|
697 | 710 | """ |
|
698 | 711 | If the user try to complete a shadowed magic, and explicit % start should |
|
699 | 712 | still return the completions. |
|
700 | 713 | """ |
|
701 | 714 | ip = get_ipython() |
|
702 | 715 | c = ip.Completer |
|
703 | 716 | |
|
704 | 717 | # Before importing matplotlib, %matplotlib magic should be the only option. |
|
705 | 718 | text, matches = c.complete("%mat") |
|
706 | 719 | nt.assert_equal(matches, ["%matplotlib"]) |
|
707 | 720 | |
|
708 | 721 | ip.run_cell("matplotlib = 1") |
|
709 | 722 | |
|
710 | 723 | # After removing matplotlib from namespace, the magic should still be |
|
711 | 724 | # the only option. |
|
712 | 725 | text, matches = c.complete("%mat") |
|
713 | 726 | nt.assert_equal(matches, ["%matplotlib"]) |
|
714 | 727 | |
|
715 | 728 | def test_magic_config(self): |
|
716 | 729 | ip = get_ipython() |
|
717 | 730 | c = ip.Completer |
|
718 | 731 | |
|
719 | 732 | s, matches = c.complete(None, "conf") |
|
720 | 733 | nt.assert_in("%config", matches) |
|
721 | 734 | s, matches = c.complete(None, "conf") |
|
722 | 735 | nt.assert_not_in("AliasManager", matches) |
|
723 | 736 | s, matches = c.complete(None, "config ") |
|
724 | 737 | nt.assert_in("AliasManager", matches) |
|
725 | 738 | s, matches = c.complete(None, "%config ") |
|
726 | 739 | nt.assert_in("AliasManager", matches) |
|
727 | 740 | s, matches = c.complete(None, "config Ali") |
|
728 | 741 | nt.assert_list_equal(["AliasManager"], matches) |
|
729 | 742 | s, matches = c.complete(None, "%config Ali") |
|
730 | 743 | nt.assert_list_equal(["AliasManager"], matches) |
|
731 | 744 | s, matches = c.complete(None, "config AliasManager") |
|
732 | 745 | nt.assert_list_equal(["AliasManager"], matches) |
|
733 | 746 | s, matches = c.complete(None, "%config AliasManager") |
|
734 | 747 | nt.assert_list_equal(["AliasManager"], matches) |
|
735 | 748 | s, matches = c.complete(None, "config AliasManager.") |
|
736 | 749 | nt.assert_in("AliasManager.default_aliases", matches) |
|
737 | 750 | s, matches = c.complete(None, "%config AliasManager.") |
|
738 | 751 | nt.assert_in("AliasManager.default_aliases", matches) |
|
739 | 752 | s, matches = c.complete(None, "config AliasManager.de") |
|
740 | 753 | nt.assert_list_equal(["AliasManager.default_aliases"], matches) |
|
741 | 754 | s, matches = c.complete(None, "config AliasManager.de") |
|
742 | 755 | nt.assert_list_equal(["AliasManager.default_aliases"], matches) |
|
743 | 756 | |
|
744 | 757 | def test_magic_color(self): |
|
745 | 758 | ip = get_ipython() |
|
746 | 759 | c = ip.Completer |
|
747 | 760 | |
|
748 | 761 | s, matches = c.complete(None, "colo") |
|
749 | 762 | nt.assert_in("%colors", matches) |
|
750 | 763 | s, matches = c.complete(None, "colo") |
|
751 | 764 | nt.assert_not_in("NoColor", matches) |
|
752 | 765 | s, matches = c.complete(None, "%colors") # No trailing space |
|
753 | 766 | nt.assert_not_in("NoColor", matches) |
|
754 | 767 | s, matches = c.complete(None, "colors ") |
|
755 | 768 | nt.assert_in("NoColor", matches) |
|
756 | 769 | s, matches = c.complete(None, "%colors ") |
|
757 | 770 | nt.assert_in("NoColor", matches) |
|
758 | 771 | s, matches = c.complete(None, "colors NoCo") |
|
759 | 772 | nt.assert_list_equal(["NoColor"], matches) |
|
760 | 773 | s, matches = c.complete(None, "%colors NoCo") |
|
761 | 774 | nt.assert_list_equal(["NoColor"], matches) |
|
762 | 775 | |
|
763 | 776 | def test_match_dict_keys(self): |
|
764 | 777 | """ |
|
765 | 778 | Test that match_dict_keys works on a couple of use case does return what |
|
766 | 779 | expected, and does not crash |
|
767 | 780 | """ |
|
768 | 781 | delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?" |
|
769 | 782 | |
|
770 | 783 | keys = ["foo", b"far"] |
|
771 | 784 | assert match_dict_keys(keys, "b'", delims=delims) == ("'", 2, ["far"]) |
|
772 | 785 | assert match_dict_keys(keys, "b'f", delims=delims) == ("'", 2, ["far"]) |
|
773 | 786 | assert match_dict_keys(keys, 'b"', delims=delims) == ('"', 2, ["far"]) |
|
774 | 787 | assert match_dict_keys(keys, 'b"f', delims=delims) == ('"', 2, ["far"]) |
|
775 | 788 | |
|
776 | 789 | assert match_dict_keys(keys, "'", delims=delims) == ("'", 1, ["foo"]) |
|
777 | 790 | assert match_dict_keys(keys, "'f", delims=delims) == ("'", 1, ["foo"]) |
|
778 | 791 | assert match_dict_keys(keys, '"', delims=delims) == ('"', 1, ["foo"]) |
|
779 | 792 | assert match_dict_keys(keys, '"f', delims=delims) == ('"', 1, ["foo"]) |
|
780 | 793 | |
|
781 | 794 | match_dict_keys |
|
782 | 795 | |
|
783 | 796 | def test_dict_key_completion_string(self): |
|
784 | 797 | """Test dictionary key completion for string keys""" |
|
785 | 798 | ip = get_ipython() |
|
786 | 799 | complete = ip.Completer.complete |
|
787 | 800 | |
|
788 | 801 | ip.user_ns["d"] = {"abc": None} |
|
789 | 802 | |
|
790 | 803 | # check completion at different stages |
|
791 | 804 | _, matches = complete(line_buffer="d[") |
|
792 | 805 | nt.assert_in("'abc'", matches) |
|
793 | 806 | nt.assert_not_in("'abc']", matches) |
|
794 | 807 | |
|
795 | 808 | _, matches = complete(line_buffer="d['") |
|
796 | 809 | nt.assert_in("abc", matches) |
|
797 | 810 | nt.assert_not_in("abc']", matches) |
|
798 | 811 | |
|
799 | 812 | _, matches = complete(line_buffer="d['a") |
|
800 | 813 | nt.assert_in("abc", matches) |
|
801 | 814 | nt.assert_not_in("abc']", matches) |
|
802 | 815 | |
|
803 | 816 | # check use of different quoting |
|
804 | 817 | _, matches = complete(line_buffer='d["') |
|
805 | 818 | nt.assert_in("abc", matches) |
|
806 | 819 | nt.assert_not_in('abc"]', matches) |
|
807 | 820 | |
|
808 | 821 | _, matches = complete(line_buffer='d["a') |
|
809 | 822 | nt.assert_in("abc", matches) |
|
810 | 823 | nt.assert_not_in('abc"]', matches) |
|
811 | 824 | |
|
812 | 825 | # check sensitivity to following context |
|
813 | 826 | _, matches = complete(line_buffer="d[]", cursor_pos=2) |
|
814 | 827 | nt.assert_in("'abc'", matches) |
|
815 | 828 | |
|
816 | 829 | _, matches = complete(line_buffer="d['']", cursor_pos=3) |
|
817 | 830 | nt.assert_in("abc", matches) |
|
818 | 831 | nt.assert_not_in("abc'", matches) |
|
819 | 832 | nt.assert_not_in("abc']", matches) |
|
820 | 833 | |
|
821 | 834 | # check multiple solutions are correctly returned and that noise is not |
|
822 | 835 | ip.user_ns["d"] = { |
|
823 | 836 | "abc": None, |
|
824 | 837 | "abd": None, |
|
825 | 838 | "bad": None, |
|
826 | 839 | object(): None, |
|
827 | 840 | 5: None, |
|
828 | 841 | } |
|
829 | 842 | |
|
830 | 843 | _, matches = complete(line_buffer="d['a") |
|
831 | 844 | nt.assert_in("abc", matches) |
|
832 | 845 | nt.assert_in("abd", matches) |
|
833 | 846 | nt.assert_not_in("bad", matches) |
|
834 | 847 | assert not any(m.endswith(("]", '"', "'")) for m in matches), matches |
|
835 | 848 | |
|
836 | 849 | # check escaping and whitespace |
|
837 | 850 | ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None} |
|
838 | 851 | _, matches = complete(line_buffer="d['a") |
|
839 | 852 | nt.assert_in("a\\nb", matches) |
|
840 | 853 | nt.assert_in("a\\'b", matches) |
|
841 | 854 | nt.assert_in('a"b', matches) |
|
842 | 855 | nt.assert_in("a word", matches) |
|
843 | 856 | assert not any(m.endswith(("]", '"', "'")) for m in matches), matches |
|
844 | 857 | |
|
845 | 858 | # - can complete on non-initial word of the string |
|
846 | 859 | _, matches = complete(line_buffer="d['a w") |
|
847 | 860 | nt.assert_in("word", matches) |
|
848 | 861 | |
|
849 | 862 | # - understands quote escaping |
|
850 | 863 | _, matches = complete(line_buffer="d['a\\'") |
|
851 | 864 | nt.assert_in("b", matches) |
|
852 | 865 | |
|
853 | 866 | # - default quoting should work like repr |
|
854 | 867 | _, matches = complete(line_buffer="d[") |
|
855 | 868 | nt.assert_in('"a\'b"', matches) |
|
856 | 869 | |
|
857 | 870 | # - when opening quote with ", possible to match with unescaped apostrophe |
|
858 | 871 | _, matches = complete(line_buffer="d[\"a'") |
|
859 | 872 | nt.assert_in("b", matches) |
|
860 | 873 | |
|
861 | 874 | # need to not split at delims that readline won't split at |
|
862 | 875 | if "-" not in ip.Completer.splitter.delims: |
|
863 | 876 | ip.user_ns["d"] = {"before-after": None} |
|
864 | 877 | _, matches = complete(line_buffer="d['before-af") |
|
865 | 878 | nt.assert_in("before-after", matches) |
|
866 | 879 | |
|
867 | 880 | def test_dict_key_completion_contexts(self): |
|
868 | 881 | """Test expression contexts in which dict key completion occurs""" |
|
869 | 882 | ip = get_ipython() |
|
870 | 883 | complete = ip.Completer.complete |
|
871 | 884 | d = {"abc": None} |
|
872 | 885 | ip.user_ns["d"] = d |
|
873 | 886 | |
|
874 | 887 | class C: |
|
875 | 888 | data = d |
|
876 | 889 | |
|
877 | 890 | ip.user_ns["C"] = C |
|
878 | 891 | ip.user_ns["get"] = lambda: d |
|
879 | 892 | |
|
880 | 893 | def assert_no_completion(**kwargs): |
|
881 | 894 | _, matches = complete(**kwargs) |
|
882 | 895 | nt.assert_not_in("abc", matches) |
|
883 | 896 | nt.assert_not_in("abc'", matches) |
|
884 | 897 | nt.assert_not_in("abc']", matches) |
|
885 | 898 | nt.assert_not_in("'abc'", matches) |
|
886 | 899 | nt.assert_not_in("'abc']", matches) |
|
887 | 900 | |
|
888 | 901 | def assert_completion(**kwargs): |
|
889 | 902 | _, matches = complete(**kwargs) |
|
890 | 903 | nt.assert_in("'abc'", matches) |
|
891 | 904 | nt.assert_not_in("'abc']", matches) |
|
892 | 905 | |
|
893 | 906 | # no completion after string closed, even if reopened |
|
894 | 907 | assert_no_completion(line_buffer="d['a'") |
|
895 | 908 | assert_no_completion(line_buffer='d["a"') |
|
896 | 909 | assert_no_completion(line_buffer="d['a' + ") |
|
897 | 910 | assert_no_completion(line_buffer="d['a' + '") |
|
898 | 911 | |
|
899 | 912 | # completion in non-trivial expressions |
|
900 | 913 | assert_completion(line_buffer="+ d[") |
|
901 | 914 | assert_completion(line_buffer="(d[") |
|
902 | 915 | assert_completion(line_buffer="C.data[") |
|
903 | 916 | |
|
904 | 917 | # greedy flag |
|
905 | 918 | def assert_completion(**kwargs): |
|
906 | 919 | _, matches = complete(**kwargs) |
|
907 | 920 | nt.assert_in("get()['abc']", matches) |
|
908 | 921 | |
|
909 | 922 | assert_no_completion(line_buffer="get()[") |
|
910 | 923 | with greedy_completion(): |
|
911 | 924 | assert_completion(line_buffer="get()[") |
|
912 | 925 | assert_completion(line_buffer="get()['") |
|
913 | 926 | assert_completion(line_buffer="get()['a") |
|
914 | 927 | assert_completion(line_buffer="get()['ab") |
|
915 | 928 | assert_completion(line_buffer="get()['abc") |
|
916 | 929 | |
|
917 | 930 | def test_dict_key_completion_bytes(self): |
|
918 | 931 | """Test handling of bytes in dict key completion""" |
|
919 | 932 | ip = get_ipython() |
|
920 | 933 | complete = ip.Completer.complete |
|
921 | 934 | |
|
922 | 935 | ip.user_ns["d"] = {"abc": None, b"abd": None} |
|
923 | 936 | |
|
924 | 937 | _, matches = complete(line_buffer="d[") |
|
925 | 938 | nt.assert_in("'abc'", matches) |
|
926 | 939 | nt.assert_in("b'abd'", matches) |
|
927 | 940 | |
|
928 | 941 | if False: # not currently implemented |
|
929 | 942 | _, matches = complete(line_buffer="d[b") |
|
930 | 943 | nt.assert_in("b'abd'", matches) |
|
931 | 944 | nt.assert_not_in("b'abc'", matches) |
|
932 | 945 | |
|
933 | 946 | _, matches = complete(line_buffer="d[b'") |
|
934 | 947 | nt.assert_in("abd", matches) |
|
935 | 948 | nt.assert_not_in("abc", matches) |
|
936 | 949 | |
|
937 | 950 | _, matches = complete(line_buffer="d[B'") |
|
938 | 951 | nt.assert_in("abd", matches) |
|
939 | 952 | nt.assert_not_in("abc", matches) |
|
940 | 953 | |
|
941 | 954 | _, matches = complete(line_buffer="d['") |
|
942 | 955 | nt.assert_in("abc", matches) |
|
943 | 956 | nt.assert_not_in("abd", matches) |
|
944 | 957 | |
|
945 | 958 | def test_dict_key_completion_unicode_py3(self): |
|
946 | 959 | """Test handling of unicode in dict key completion""" |
|
947 | 960 | ip = get_ipython() |
|
948 | 961 | complete = ip.Completer.complete |
|
949 | 962 | |
|
950 | 963 | ip.user_ns["d"] = {"a\u05d0": None} |
|
951 | 964 | |
|
952 | 965 | # query using escape |
|
953 | 966 | if sys.platform != "win32": |
|
954 | 967 | # Known failure on Windows |
|
955 | 968 | _, matches = complete(line_buffer="d['a\\u05d0") |
|
956 | 969 | nt.assert_in("u05d0", matches) # tokenized after \\ |
|
957 | 970 | |
|
958 | 971 | # query using character |
|
959 | 972 | _, matches = complete(line_buffer="d['a\u05d0") |
|
960 | 973 | nt.assert_in("a\u05d0", matches) |
|
961 | 974 | |
|
962 | 975 | with greedy_completion(): |
|
963 | 976 | # query using escape |
|
964 | 977 | _, matches = complete(line_buffer="d['a\\u05d0") |
|
965 | 978 | nt.assert_in("d['a\\u05d0']", matches) # tokenized after \\ |
|
966 | 979 | |
|
967 | 980 | # query using character |
|
968 | 981 | _, matches = complete(line_buffer="d['a\u05d0") |
|
969 | 982 | nt.assert_in("d['a\u05d0']", matches) |
|
970 | 983 | |
|
971 | 984 | @dec.skip_without("numpy") |
|
972 | 985 | def test_struct_array_key_completion(self): |
|
973 | 986 | """Test dict key completion applies to numpy struct arrays""" |
|
974 | 987 | import numpy |
|
975 | 988 | |
|
976 | 989 | ip = get_ipython() |
|
977 | 990 | complete = ip.Completer.complete |
|
978 | 991 | ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")]) |
|
979 | 992 | _, matches = complete(line_buffer="d['") |
|
980 | 993 | nt.assert_in("hello", matches) |
|
981 | 994 | nt.assert_in("world", matches) |
|
982 | 995 | # complete on the numpy struct itself |
|
983 | 996 | dt = numpy.dtype( |
|
984 | 997 | [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)] |
|
985 | 998 | ) |
|
986 | 999 | x = numpy.zeros(2, dtype=dt) |
|
987 | 1000 | ip.user_ns["d"] = x[1] |
|
988 | 1001 | _, matches = complete(line_buffer="d['") |
|
989 | 1002 | nt.assert_in("my_head", matches) |
|
990 | 1003 | nt.assert_in("my_data", matches) |
|
991 | 1004 | # complete on a nested level |
|
992 | 1005 | with greedy_completion(): |
|
993 | 1006 | ip.user_ns["d"] = numpy.zeros(2, dtype=dt) |
|
994 | 1007 | _, matches = complete(line_buffer="d[1]['my_head']['") |
|
995 | 1008 | nt.assert_true(any(["my_dt" in m for m in matches])) |
|
996 | 1009 | nt.assert_true(any(["my_df" in m for m in matches])) |
|
997 | 1010 | |
|
998 | 1011 | @dec.skip_without("pandas") |
|
999 | 1012 | def test_dataframe_key_completion(self): |
|
1000 | 1013 | """Test dict key completion applies to pandas DataFrames""" |
|
1001 | 1014 | import pandas |
|
1002 | 1015 | |
|
1003 | 1016 | ip = get_ipython() |
|
1004 | 1017 | complete = ip.Completer.complete |
|
1005 | 1018 | ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]}) |
|
1006 | 1019 | _, matches = complete(line_buffer="d['") |
|
1007 | 1020 | nt.assert_in("hello", matches) |
|
1008 | 1021 | nt.assert_in("world", matches) |
|
1009 | 1022 | |
|
1010 | 1023 | def test_dict_key_completion_invalids(self): |
|
1011 | 1024 | """Smoke test cases dict key completion can't handle""" |
|
1012 | 1025 | ip = get_ipython() |
|
1013 | 1026 | complete = ip.Completer.complete |
|
1014 | 1027 | |
|
1015 | 1028 | ip.user_ns["no_getitem"] = None |
|
1016 | 1029 | ip.user_ns["no_keys"] = [] |
|
1017 | 1030 | ip.user_ns["cant_call_keys"] = dict |
|
1018 | 1031 | ip.user_ns["empty"] = {} |
|
1019 | 1032 | ip.user_ns["d"] = {"abc": 5} |
|
1020 | 1033 | |
|
1021 | 1034 | _, matches = complete(line_buffer="no_getitem['") |
|
1022 | 1035 | _, matches = complete(line_buffer="no_keys['") |
|
1023 | 1036 | _, matches = complete(line_buffer="cant_call_keys['") |
|
1024 | 1037 | _, matches = complete(line_buffer="empty['") |
|
1025 | 1038 | _, matches = complete(line_buffer="name_error['") |
|
1026 | 1039 | _, matches = complete(line_buffer="d['\\") # incomplete escape |
|
1027 | 1040 | |
|
1028 | 1041 | def test_object_key_completion(self): |
|
1029 | 1042 | ip = get_ipython() |
|
1030 | 1043 | ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"]) |
|
1031 | 1044 | |
|
1032 | 1045 | _, matches = ip.Completer.complete(line_buffer="key_completable['qw") |
|
1033 | 1046 | nt.assert_in("qwerty", matches) |
|
1034 | 1047 | nt.assert_in("qwick", matches) |
|
1035 | 1048 | |
|
1036 | 1049 | def test_class_key_completion(self): |
|
1037 | 1050 | ip = get_ipython() |
|
1038 | 1051 | NamedInstanceClass("qwerty") |
|
1039 | 1052 | NamedInstanceClass("qwick") |
|
1040 | 1053 | ip.user_ns["named_instance_class"] = NamedInstanceClass |
|
1041 | 1054 | |
|
1042 | 1055 | _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw") |
|
1043 | 1056 | nt.assert_in("qwerty", matches) |
|
1044 | 1057 | nt.assert_in("qwick", matches) |
|
1045 | 1058 | |
|
1046 | 1059 | def test_tryimport(self): |
|
1047 | 1060 | """ |
|
1048 | 1061 | Test that try-import don't crash on trailing dot, and import modules before |
|
1049 | 1062 | """ |
|
1050 | 1063 | from IPython.core.completerlib import try_import |
|
1051 | 1064 | |
|
1052 | 1065 | assert try_import("IPython.") |
|
1053 | 1066 | |
|
1054 | 1067 | def test_aimport_module_completer(self): |
|
1055 | 1068 | ip = get_ipython() |
|
1056 | 1069 | _, matches = ip.complete("i", "%aimport i") |
|
1057 | 1070 | nt.assert_in("io", matches) |
|
1058 | 1071 | nt.assert_not_in("int", matches) |
|
1059 | 1072 | |
|
1060 | 1073 | def test_nested_import_module_completer(self): |
|
1061 | 1074 | ip = get_ipython() |
|
1062 | 1075 | _, matches = ip.complete(None, "import IPython.co", 17) |
|
1063 | 1076 | nt.assert_in("IPython.core", matches) |
|
1064 | 1077 | nt.assert_not_in("import IPython.core", matches) |
|
1065 | 1078 | nt.assert_not_in("IPython.display", matches) |
|
1066 | 1079 | |
|
1067 | 1080 | def test_import_module_completer(self): |
|
1068 | 1081 | ip = get_ipython() |
|
1069 | 1082 | _, matches = ip.complete("i", "import i") |
|
1070 | 1083 | nt.assert_in("io", matches) |
|
1071 | 1084 | nt.assert_not_in("int", matches) |
|
1072 | 1085 | |
|
1073 | 1086 | def test_from_module_completer(self): |
|
1074 | 1087 | ip = get_ipython() |
|
1075 | 1088 | _, matches = ip.complete("B", "from io import B", 16) |
|
1076 | 1089 | nt.assert_in("BytesIO", matches) |
|
1077 | 1090 | nt.assert_not_in("BaseException", matches) |
|
1078 | 1091 | |
|
1079 | 1092 | def test_snake_case_completion(self): |
|
1080 | 1093 | ip = get_ipython() |
|
1081 | 1094 | ip.Completer.use_jedi = False |
|
1082 | 1095 | ip.user_ns["some_three"] = 3 |
|
1083 | 1096 | ip.user_ns["some_four"] = 4 |
|
1084 | 1097 | _, matches = ip.complete("s_", "print(s_f") |
|
1085 | 1098 | nt.assert_in("some_three", matches) |
|
1086 | 1099 | nt.assert_in("some_four", matches) |
|
1087 | 1100 | |
|
1088 | 1101 | def test_mix_terms(self): |
|
1089 | 1102 | ip = get_ipython() |
|
1090 | 1103 | from textwrap import dedent |
|
1091 | 1104 | |
|
1092 | 1105 | ip.Completer.use_jedi = False |
|
1093 | 1106 | ip.ex( |
|
1094 | 1107 | dedent( |
|
1095 | 1108 | """ |
|
1096 | 1109 | class Test: |
|
1097 | 1110 | def meth(self, meth_arg1): |
|
1098 | 1111 | print("meth") |
|
1099 | 1112 | |
|
1100 | 1113 | def meth_1(self, meth1_arg1, meth1_arg2): |
|
1101 | 1114 | print("meth1") |
|
1102 | 1115 | |
|
1103 | 1116 | def meth_2(self, meth2_arg1, meth2_arg2): |
|
1104 | 1117 | print("meth2") |
|
1105 | 1118 | test = Test() |
|
1106 | 1119 | """ |
|
1107 | 1120 | ) |
|
1108 | 1121 | ) |
|
1109 | 1122 | _, matches = ip.complete(None, "test.meth(") |
|
1110 | 1123 | nt.assert_in("meth_arg1=", matches) |
|
1111 | 1124 | nt.assert_not_in("meth2_arg1=", matches) |
@@ -1,118 +1,124 b'' | |||
|
1 | 1 | #----------------------------------------------------------------------------- |
|
2 | 2 | # Copyright (C) 2010-2011, IPython Development Team. |
|
3 | 3 | # |
|
4 | 4 | # Distributed under the terms of the Modified BSD License. |
|
5 | 5 | # |
|
6 | 6 | # The full license is in the file COPYING.txt, distributed with this software. |
|
7 | 7 | #----------------------------------------------------------------------------- |
|
8 | 8 | |
|
9 | 9 | import argparse |
|
10 | import sys | |
|
10 | 11 | from nose.tools import assert_equal |
|
11 | 12 | |
|
12 | 13 | from IPython.core.magic_arguments import (argument, argument_group, kwds, |
|
13 | 14 | magic_arguments, parse_argstring, real_name) |
|
14 | 15 | |
|
15 | 16 | |
|
16 | 17 | @magic_arguments() |
|
17 | 18 | @argument('-f', '--foo', help="an argument") |
|
18 | 19 | def magic_foo1(self, args): |
|
19 | 20 | """ A docstring. |
|
20 | 21 | """ |
|
21 | 22 | return parse_argstring(magic_foo1, args) |
|
22 | 23 | |
|
23 | 24 | |
|
24 | 25 | @magic_arguments() |
|
25 | 26 | def magic_foo2(self, args): |
|
26 | 27 | """ A docstring. |
|
27 | 28 | """ |
|
28 | 29 | return parse_argstring(magic_foo2, args) |
|
29 | 30 | |
|
30 | 31 | |
|
31 | 32 | @magic_arguments() |
|
32 | 33 | @argument('-f', '--foo', help="an argument") |
|
33 | 34 | @argument_group('Group') |
|
34 | 35 | @argument('-b', '--bar', help="a grouped argument") |
|
35 | 36 | @argument_group('Second Group') |
|
36 | 37 | @argument('-z', '--baz', help="another grouped argument") |
|
37 | 38 | def magic_foo3(self, args): |
|
38 | 39 | """ A docstring. |
|
39 | 40 | """ |
|
40 | 41 | return parse_argstring(magic_foo3, args) |
|
41 | 42 | |
|
42 | 43 | |
|
43 | 44 | @magic_arguments() |
|
44 | 45 | @kwds(argument_default=argparse.SUPPRESS) |
|
45 | 46 | @argument('-f', '--foo', help="an argument") |
|
46 | 47 | def magic_foo4(self, args): |
|
47 | 48 | """ A docstring. |
|
48 | 49 | """ |
|
49 | 50 | return parse_argstring(magic_foo4, args) |
|
50 | 51 | |
|
51 | 52 | |
|
52 | 53 | @magic_arguments('frobnicate') |
|
53 | 54 | @argument('-f', '--foo', help="an argument") |
|
54 | 55 | def magic_foo5(self, args): |
|
55 | 56 | """ A docstring. |
|
56 | 57 | """ |
|
57 | 58 | return parse_argstring(magic_foo5, args) |
|
58 | 59 | |
|
59 | 60 | |
|
60 | 61 | @magic_arguments() |
|
61 | 62 | @argument('-f', '--foo', help="an argument") |
|
62 | 63 | def magic_magic_foo(self, args): |
|
63 | 64 | """ A docstring. |
|
64 | 65 | """ |
|
65 | 66 | return parse_argstring(magic_magic_foo, args) |
|
66 | 67 | |
|
67 | 68 | |
|
68 | 69 | @magic_arguments() |
|
69 | 70 | @argument('-f', '--foo', help="an argument") |
|
70 | 71 | def foo(self, args): |
|
71 | 72 | """ A docstring. |
|
72 | 73 | """ |
|
73 | 74 | return parse_argstring(foo, args) |
|
74 | 75 | |
|
75 | 76 | |
|
76 | 77 | def test_magic_arguments(): |
|
77 | assert_equal(magic_foo1.__doc__, '::\n\n %foo1 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n') | |
|
78 | # βoptional argumentsβ was replaced with βoptionsβ in argparse help | |
|
79 | # https://docs.python.org/3/whatsnew/3.10.html#argparse | |
|
80 | # https://bugs.python.org/issue9694 | |
|
81 | options = "optional arguments" if sys.version_info < (3, 10) else "options" | |
|
82 | ||
|
83 | assert_equal(magic_foo1.__doc__, f"::\n\n %foo1 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n") | |
|
78 | 84 | assert_equal(getattr(magic_foo1, 'argcmd_name', None), None) |
|
79 | 85 | assert_equal(real_name(magic_foo1), 'foo1') |
|
80 | 86 | assert_equal(magic_foo1(None, ''), argparse.Namespace(foo=None)) |
|
81 | 87 | assert hasattr(magic_foo1, 'has_arguments') |
|
82 | 88 | |
|
83 | 89 | assert_equal(magic_foo2.__doc__, '::\n\n %foo2\n\n A docstring.\n') |
|
84 | 90 | assert_equal(getattr(magic_foo2, 'argcmd_name', None), None) |
|
85 | 91 | assert_equal(real_name(magic_foo2), 'foo2') |
|
86 | 92 | assert_equal(magic_foo2(None, ''), argparse.Namespace()) |
|
87 | 93 | assert hasattr(magic_foo2, 'has_arguments') |
|
88 | 94 | |
|
89 |
assert_equal(magic_foo3.__doc__, |
|
|
95 | assert_equal(magic_foo3.__doc__, f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n") | |
|
90 | 96 | assert_equal(getattr(magic_foo3, 'argcmd_name', None), None) |
|
91 | 97 | assert_equal(real_name(magic_foo3), 'foo3') |
|
92 | 98 | assert_equal(magic_foo3(None, ''), |
|
93 | 99 | argparse.Namespace(bar=None, baz=None, foo=None)) |
|
94 | 100 | assert hasattr(magic_foo3, 'has_arguments') |
|
95 | 101 | |
|
96 |
assert_equal(magic_foo4.__doc__, |
|
|
102 | assert_equal(magic_foo4.__doc__, f"::\n\n %foo4 [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n") | |
|
97 | 103 | assert_equal(getattr(magic_foo4, 'argcmd_name', None), None) |
|
98 | 104 | assert_equal(real_name(magic_foo4), 'foo4') |
|
99 | 105 | assert_equal(magic_foo4(None, ''), argparse.Namespace()) |
|
100 | 106 | assert hasattr(magic_foo4, 'has_arguments') |
|
101 | 107 | |
|
102 |
assert_equal(magic_foo5.__doc__, |
|
|
108 | assert_equal(magic_foo5.__doc__, f"::\n\n %frobnicate [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n") | |
|
103 | 109 | assert_equal(getattr(magic_foo5, 'argcmd_name', None), 'frobnicate') |
|
104 | 110 | assert_equal(real_name(magic_foo5), 'frobnicate') |
|
105 | 111 | assert_equal(magic_foo5(None, ''), argparse.Namespace(foo=None)) |
|
106 | 112 | assert hasattr(magic_foo5, 'has_arguments') |
|
107 | 113 | |
|
108 |
assert_equal(magic_magic_foo.__doc__, |
|
|
114 | assert_equal(magic_magic_foo.__doc__, f"::\n\n %magic_foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n") | |
|
109 | 115 | assert_equal(getattr(magic_magic_foo, 'argcmd_name', None), None) |
|
110 | 116 | assert_equal(real_name(magic_magic_foo), 'magic_foo') |
|
111 | 117 | assert_equal(magic_magic_foo(None, ''), argparse.Namespace(foo=None)) |
|
112 | 118 | assert hasattr(magic_magic_foo, 'has_arguments') |
|
113 | 119 | |
|
114 |
assert_equal(foo.__doc__, |
|
|
120 | assert_equal(foo.__doc__, f"::\n\n %foo [-f FOO]\n\n A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n") | |
|
115 | 121 | assert_equal(getattr(foo, 'argcmd_name', None), None) |
|
116 | 122 | assert_equal(real_name(foo), 'foo') |
|
117 | 123 | assert_equal(foo(None, ''), argparse.Namespace(foo=None)) |
|
118 | 124 | assert hasattr(foo, 'has_arguments') |
@@ -1,447 +1,476 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 inspect import signature, Signature, Parameter |
|
9 | import inspect | |
|
9 | 10 | import os |
|
11 | import pytest | |
|
10 | 12 | import re |
|
11 | ||
|
12 | import nose.tools as nt | |
|
13 | import sys | |
|
13 | 14 | |
|
14 | 15 | from .. import oinspect |
|
15 | 16 | |
|
16 | 17 | from decorator import decorator |
|
17 | 18 | |
|
18 | 19 | from IPython.testing.tools import AssertPrints, AssertNotPrints |
|
19 | 20 | from IPython.utils.path import compress_user |
|
20 | 21 | |
|
21 | 22 | |
|
22 | 23 | #----------------------------------------------------------------------------- |
|
23 | 24 | # Globals and constants |
|
24 | 25 | #----------------------------------------------------------------------------- |
|
25 | 26 | |
|
26 | 27 | inspector = None |
|
27 | 28 | |
|
28 | 29 | def setup_module(): |
|
29 | 30 | global inspector |
|
30 | 31 | inspector = oinspect.Inspector() |
|
31 | 32 | |
|
32 | 33 | |
|
34 | class SourceModuleMainTest: | |
|
35 | __module__ = "__main__" | |
|
36 | ||
|
37 | ||
|
33 | 38 | #----------------------------------------------------------------------------- |
|
34 | 39 | # Local utilities |
|
35 | 40 | #----------------------------------------------------------------------------- |
|
36 | 41 | |
|
37 | 42 | # WARNING: since this test checks the line number where a function is |
|
38 | 43 | # defined, if any code is inserted above, the following line will need to be |
|
39 | 44 | # updated. Do NOT insert any whitespace between the next line and the function |
|
40 | 45 | # definition below. |
|
41 |
THIS_LINE_NUMBER = 4 |
|
|
46 | THIS_LINE_NUMBER = 46 # Put here the actual number of this line | |
|
47 | ||
|
48 | ||
|
49 | def test_find_source_lines(): | |
|
50 | assert oinspect.find_source_lines(test_find_source_lines) == THIS_LINE_NUMBER + 3 | |
|
51 | assert oinspect.find_source_lines(type) is None | |
|
52 | assert oinspect.find_source_lines(SourceModuleMainTest) is None | |
|
53 | assert oinspect.find_source_lines(SourceModuleMainTest()) is None | |
|
54 | ||
|
42 | 55 | |
|
43 | from unittest import TestCase | |
|
56 | def test_getsource(): | |
|
57 | assert oinspect.getsource(type) is None | |
|
58 | assert oinspect.getsource(SourceModuleMainTest) is None | |
|
59 | assert oinspect.getsource(SourceModuleMainTest()) is None | |
|
44 | 60 | |
|
45 | class Test(TestCase): | |
|
46 | 61 | |
|
47 | def test_find_source_lines(self): | |
|
48 | self.assertEqual(oinspect.find_source_lines(Test.test_find_source_lines), | |
|
49 | THIS_LINE_NUMBER+6) | |
|
62 | def test_inspect_getfile_raises_exception(): | |
|
63 | """Check oinspect.find_file/getsource/find_source_lines expectations""" | |
|
64 | with pytest.raises(TypeError): | |
|
65 | inspect.getfile(type) | |
|
66 | with pytest.raises(OSError if sys.version_info >= (3, 10) else TypeError): | |
|
67 | inspect.getfile(SourceModuleMainTest) | |
|
50 | 68 | |
|
51 | 69 | |
|
52 | 70 | # A couple of utilities to ensure these tests work the same from a source or a |
|
53 | 71 | # binary install |
|
54 | 72 | def pyfile(fname): |
|
55 | 73 | return os.path.normcase(re.sub('.py[co]$', '.py', fname)) |
|
56 | 74 | |
|
57 | 75 | |
|
58 | 76 | def match_pyfiles(f1, f2): |
|
59 |
|
|
|
77 | assert pyfile(f1) == pyfile(f2) | |
|
60 | 78 | |
|
61 | 79 | |
|
62 | 80 | def test_find_file(): |
|
63 | 81 | match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__)) |
|
82 | assert oinspect.find_file(type) is None | |
|
83 | assert oinspect.find_file(SourceModuleMainTest) is None | |
|
84 | assert oinspect.find_file(SourceModuleMainTest()) is None | |
|
64 | 85 | |
|
65 | 86 | |
|
66 | 87 | def test_find_file_decorated1(): |
|
67 | 88 | |
|
68 | 89 | @decorator |
|
69 | 90 | def noop1(f): |
|
70 | 91 | def wrapper(*a, **kw): |
|
71 | 92 | return f(*a, **kw) |
|
72 | 93 | return wrapper |
|
73 | 94 | |
|
74 | 95 | @noop1 |
|
75 | 96 | def f(x): |
|
76 | 97 | "My docstring" |
|
77 | ||
|
98 | ||
|
78 | 99 | match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) |
|
79 |
|
|
|
100 | assert f.__doc__ == "My docstring" | |
|
80 | 101 | |
|
81 | 102 | |
|
82 | 103 | def test_find_file_decorated2(): |
|
83 | 104 | |
|
84 | 105 | @decorator |
|
85 | 106 | def noop2(f, *a, **kw): |
|
86 | 107 | return f(*a, **kw) |
|
87 | 108 | |
|
88 | 109 | @noop2 |
|
89 | 110 | @noop2 |
|
90 | 111 | @noop2 |
|
91 | 112 | def f(x): |
|
92 | 113 | "My docstring 2" |
|
93 | ||
|
114 | ||
|
94 | 115 | match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__)) |
|
95 |
|
|
|
96 | ||
|
116 | assert f.__doc__ == "My docstring 2" | |
|
117 | ||
|
97 | 118 | |
|
98 | 119 | def test_find_file_magic(): |
|
99 | 120 | run = ip.find_line_magic('run') |
|
100 |
|
|
|
121 | assert oinspect.find_file(run) is not None | |
|
101 | 122 | |
|
102 | 123 | |
|
103 | 124 | # A few generic objects we can then inspect in the tests below |
|
104 | 125 | |
|
105 | 126 | class Call(object): |
|
106 | 127 | """This is the class docstring.""" |
|
107 | 128 | |
|
108 | 129 | def __init__(self, x, y=1): |
|
109 | 130 | """This is the constructor docstring.""" |
|
110 | 131 | |
|
111 | 132 | def __call__(self, *a, **kw): |
|
112 | 133 | """This is the call docstring.""" |
|
113 | 134 | |
|
114 | 135 | def method(self, x, z=2): |
|
115 | 136 | """Some method's docstring""" |
|
116 | 137 | |
|
117 | 138 | class HasSignature(object): |
|
118 | 139 | """This is the class docstring.""" |
|
119 | 140 | __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)]) |
|
120 | 141 | |
|
121 | 142 | def __init__(self, *args): |
|
122 | 143 | """This is the init docstring""" |
|
123 | 144 | |
|
124 | 145 | |
|
125 | 146 | class SimpleClass(object): |
|
126 | 147 | def method(self, x, z=2): |
|
127 | 148 | """Some method's docstring""" |
|
128 | 149 | |
|
129 | 150 | |
|
130 | 151 | class Awkward(object): |
|
131 | 152 | def __getattr__(self, name): |
|
132 | 153 | raise Exception(name) |
|
133 | 154 | |
|
134 | 155 | class NoBoolCall: |
|
135 | 156 | """ |
|
136 | 157 | callable with `__bool__` raising should still be inspect-able. |
|
137 | 158 | """ |
|
138 | 159 | |
|
139 | 160 | def __call__(self): |
|
140 | 161 | """does nothing""" |
|
141 | 162 | pass |
|
142 | 163 | |
|
143 | 164 | def __bool__(self): |
|
144 | 165 | """just raise NotImplemented""" |
|
145 | 166 | raise NotImplementedError('Must be implemented') |
|
146 | 167 | |
|
147 | 168 | |
|
148 | 169 | class SerialLiar(object): |
|
149 | 170 | """Attribute accesses always get another copy of the same class. |
|
150 | 171 | |
|
151 | 172 | unittest.mock.call does something similar, but it's not ideal for testing |
|
152 | 173 | as the failure mode is to eat all your RAM. This gives up after 10k levels. |
|
153 | 174 | """ |
|
154 | 175 | def __init__(self, max_fibbing_twig, lies_told=0): |
|
155 | 176 | if lies_told > 10000: |
|
156 | 177 | raise RuntimeError('Nose too long, honesty is the best policy') |
|
157 | 178 | self.max_fibbing_twig = max_fibbing_twig |
|
158 | 179 | self.lies_told = lies_told |
|
159 | 180 | max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told) |
|
160 | 181 | |
|
161 | 182 | def __getattr__(self, item): |
|
162 | 183 | return SerialLiar(self.max_fibbing_twig, self.lies_told + 1) |
|
163 | 184 | |
|
164 | 185 | #----------------------------------------------------------------------------- |
|
165 | 186 | # Tests |
|
166 | 187 | #----------------------------------------------------------------------------- |
|
167 | 188 | |
|
168 | 189 | def test_info(): |
|
169 | 190 | "Check that Inspector.info fills out various fields as expected." |
|
170 |
i = inspector.info(Call, oname= |
|
|
171 |
|
|
|
172 | expted_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'> | |
|
173 |
|
|
|
174 | nt.assert_regex(i['string_form'], "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>") | |
|
191 | i = inspector.info(Call, oname="Call") | |
|
192 | assert i["type_name"] == "type" | |
|
193 | expected_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'> | |
|
194 | assert i["base_class"] == expected_class | |
|
195 | assert re.search( | |
|
196 | "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>", | |
|
197 | i["string_form"], | |
|
198 | ) | |
|
175 | 199 | fname = __file__ |
|
176 | 200 | if fname.endswith(".pyc"): |
|
177 | 201 | fname = fname[:-1] |
|
178 | 202 | # case-insensitive comparison needed on some filesystems |
|
179 | 203 | # e.g. Windows: |
|
180 |
|
|
|
181 |
|
|
|
182 |
|
|
|
183 |
|
|
|
184 |
|
|
|
185 |
|
|
|
186 |
|
|
|
204 | assert i["file"].lower() == compress_user(fname).lower() | |
|
205 | assert i["definition"] == None | |
|
206 | assert i["docstring"] == Call.__doc__ | |
|
207 | assert i["source"] == None | |
|
208 | assert i["isclass"] is True | |
|
209 | assert i["init_definition"] == "Call(x, y=1)" | |
|
210 | assert i["init_docstring"] == Call.__init__.__doc__ | |
|
187 | 211 | |
|
188 | 212 | i = inspector.info(Call, detail_level=1) |
|
189 |
|
|
|
190 |
|
|
|
213 | assert i["source"] is not None | |
|
214 | assert i["docstring"] == None | |
|
191 | 215 | |
|
192 | 216 | c = Call(1) |
|
193 | 217 | c.__doc__ = "Modified instance docstring" |
|
194 | 218 | i = inspector.info(c) |
|
195 |
|
|
|
196 |
|
|
|
197 |
|
|
|
198 |
|
|
|
199 |
|
|
|
219 | assert i["type_name"] == "Call" | |
|
220 | assert i["docstring"] == "Modified instance docstring" | |
|
221 | assert i["class_docstring"] == Call.__doc__ | |
|
222 | assert i["init_docstring"] == Call.__init__.__doc__ | |
|
223 | assert i["call_docstring"] == Call.__call__.__doc__ | |
|
224 | ||
|
200 | 225 | |
|
201 | 226 | def test_class_signature(): |
|
202 |
info = inspector.info(HasSignature, |
|
|
203 |
|
|
|
204 |
|
|
|
227 | info = inspector.info(HasSignature, "HasSignature") | |
|
228 | assert info["init_definition"] == "HasSignature(test)" | |
|
229 | assert info["init_docstring"] == HasSignature.__init__.__doc__ | |
|
230 | ||
|
205 | 231 | |
|
206 | 232 | def test_info_awkward(): |
|
207 | 233 | # Just test that this doesn't throw an error. |
|
208 | 234 | inspector.info(Awkward()) |
|
209 | 235 | |
|
210 | 236 | def test_bool_raise(): |
|
211 | 237 | inspector.info(NoBoolCall()) |
|
212 | 238 | |
|
213 | 239 | def test_info_serialliar(): |
|
214 | 240 | fib_tracker = [0] |
|
215 | 241 | inspector.info(SerialLiar(fib_tracker)) |
|
216 | 242 | |
|
217 | 243 | # Nested attribute access should be cut off at 100 levels deep to avoid |
|
218 | 244 | # infinite loops: https://github.com/ipython/ipython/issues/9122 |
|
219 |
|
|
|
245 | assert fib_tracker[0] < 9000 | |
|
220 | 246 | |
|
221 | 247 | def support_function_one(x, y=2, *a, **kw): |
|
222 | 248 | """A simple function.""" |
|
223 | 249 | |
|
224 | 250 | def test_calldef_none(): |
|
225 | 251 | # We should ignore __call__ for all of these. |
|
226 | 252 | for obj in [support_function_one, SimpleClass().method, any, str.upper]: |
|
227 | 253 | i = inspector.info(obj) |
|
228 |
|
|
|
254 | assert i["call_def"] is None | |
|
255 | ||
|
229 | 256 | |
|
230 | 257 | def f_kwarg(pos, *, kwonly): |
|
231 | 258 | pass |
|
232 | 259 | |
|
233 | 260 | def test_definition_kwonlyargs(): |
|
234 |
i = inspector.info(f_kwarg, oname= |
|
|
235 |
|
|
|
261 | i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore | |
|
262 | assert i["definition"] == "f_kwarg(pos, *, kwonly)" | |
|
263 | ||
|
236 | 264 | |
|
237 | 265 | def test_getdoc(): |
|
238 | 266 | class A(object): |
|
239 | 267 | """standard docstring""" |
|
240 | 268 | pass |
|
241 | 269 | |
|
242 | 270 | class B(object): |
|
243 | 271 | """standard docstring""" |
|
244 | 272 | def getdoc(self): |
|
245 | 273 | return "custom docstring" |
|
246 | ||
|
274 | ||
|
247 | 275 | class C(object): |
|
248 | 276 | """standard docstring""" |
|
249 | 277 | def getdoc(self): |
|
250 | 278 | return None |
|
251 | ||
|
279 | ||
|
252 | 280 | a = A() |
|
253 | 281 | b = B() |
|
254 | 282 | c = C() |
|
255 | ||
|
256 |
|
|
|
257 |
|
|
|
258 |
|
|
|
283 | ||
|
284 | assert oinspect.getdoc(a) == "standard docstring" | |
|
285 | assert oinspect.getdoc(b) == "custom docstring" | |
|
286 | assert oinspect.getdoc(c) == "standard docstring" | |
|
259 | 287 | |
|
260 | 288 | |
|
261 | 289 | def test_empty_property_has_no_source(): |
|
262 | 290 | i = inspector.info(property(), detail_level=1) |
|
263 |
|
|
|
291 | assert i["source"] is None | |
|
264 | 292 | |
|
265 | 293 | |
|
266 | 294 | def test_property_sources(): |
|
267 | import posixpath | |
|
268 | 295 | # A simple adder whose source and signature stays |
|
269 | 296 | # the same across Python distributions |
|
270 | 297 | def simple_add(a, b): |
|
271 | 298 | "Adds two numbers" |
|
272 | 299 | return a + b |
|
273 | ||
|
300 | ||
|
274 | 301 | class A(object): |
|
275 | 302 | @property |
|
276 | 303 | def foo(self): |
|
277 | 304 | return 'bar' |
|
278 | 305 | |
|
279 | 306 | foo = foo.setter(lambda self, v: setattr(self, 'bar', v)) |
|
280 | 307 | |
|
281 |
dname = property( |
|
|
282 |
adder = property(simple_add) |
|
|
308 | dname = property(oinspect.getdoc) | |
|
309 | adder = property(simple_add) | |
|
283 | 310 | |
|
284 | 311 | i = inspector.info(A.foo, detail_level=1) |
|
285 |
|
|
|
286 |
|
|
|
312 | assert "def foo(self):" in i["source"] | |
|
313 | assert "lambda self, v:" in i["source"] | |
|
287 | 314 | |
|
288 | 315 | i = inspector.info(A.dname, detail_level=1) |
|
289 |
|
|
|
290 | ||
|
316 | assert "def getdoc(obj)" in i["source"] | |
|
317 | ||
|
291 | 318 | i = inspector.info(A.adder, detail_level=1) |
|
292 |
|
|
|
319 | assert "def simple_add(a, b)" in i["source"] | |
|
293 | 320 | |
|
294 | 321 | |
|
295 | 322 | def test_property_docstring_is_in_info_for_detail_level_0(): |
|
296 | 323 | class A(object): |
|
297 | 324 | @property |
|
298 | 325 | def foobar(self): |
|
299 | 326 | """This is `foobar` property.""" |
|
300 | 327 | pass |
|
301 | 328 | |
|
302 |
ip.user_ns[ |
|
|
303 |
|
|
|
304 |
|
|
|
305 |
ip.object_inspect( |
|
|
329 | ip.user_ns["a_obj"] = A() | |
|
330 | assert ( | |
|
331 | "This is `foobar` property." | |
|
332 | == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"] | |
|
333 | ) | |
|
306 | 334 | |
|
307 |
ip.user_ns[ |
|
|
308 |
|
|
|
309 |
|
|
|
310 |
ip.object_inspect( |
|
|
335 | ip.user_ns["a_cls"] = A | |
|
336 | assert ( | |
|
337 | "This is `foobar` property." | |
|
338 | == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"] | |
|
339 | ) | |
|
311 | 340 | |
|
312 | 341 | |
|
313 | 342 | def test_pdef(): |
|
314 | 343 | # See gh-1914 |
|
315 | 344 | def foo(): pass |
|
316 | 345 | inspector.pdef(foo, 'foo') |
|
317 | 346 | |
|
318 | 347 | |
|
319 | 348 | def test_pinfo_nonascii(): |
|
320 | 349 | # See gh-1177 |
|
321 | 350 | from . import nonascii2 |
|
322 | 351 | ip.user_ns['nonascii2'] = nonascii2 |
|
323 | 352 | ip._inspect('pinfo', 'nonascii2', detail_level=1) |
|
324 | 353 | |
|
325 | 354 | def test_pinfo_type(): |
|
326 | 355 | """ |
|
327 | 356 | type can fail in various edge case, for example `type.__subclass__()` |
|
328 | 357 | """ |
|
329 | 358 | ip._inspect('pinfo', 'type') |
|
330 | 359 | |
|
331 | 360 | |
|
332 | 361 | def test_pinfo_docstring_no_source(): |
|
333 | 362 | """Docstring should be included with detail_level=1 if there is no source""" |
|
334 | 363 | with AssertPrints('Docstring:'): |
|
335 | 364 | ip._inspect('pinfo', 'str.format', detail_level=0) |
|
336 | 365 | with AssertPrints('Docstring:'): |
|
337 | 366 | ip._inspect('pinfo', 'str.format', detail_level=1) |
|
338 | 367 | |
|
339 | 368 | |
|
340 | 369 | def test_pinfo_no_docstring_if_source(): |
|
341 | 370 | """Docstring should not be included with detail_level=1 if source is found""" |
|
342 | 371 | def foo(): |
|
343 | 372 | """foo has a docstring""" |
|
344 | 373 | |
|
345 | 374 | ip.user_ns['foo'] = foo |
|
346 | 375 | |
|
347 | 376 | with AssertPrints('Docstring:'): |
|
348 | 377 | ip._inspect('pinfo', 'foo', detail_level=0) |
|
349 | 378 | with AssertPrints('Source:'): |
|
350 | 379 | ip._inspect('pinfo', 'foo', detail_level=1) |
|
351 | 380 | with AssertNotPrints('Docstring:'): |
|
352 | 381 | ip._inspect('pinfo', 'foo', detail_level=1) |
|
353 | 382 | |
|
354 | 383 | |
|
355 | 384 | def test_pinfo_docstring_if_detail_and_no_source(): |
|
356 | 385 | """ Docstring should be displayed if source info not available """ |
|
357 | 386 | obj_def = '''class Foo(object): |
|
358 | 387 | """ This is a docstring for Foo """ |
|
359 | 388 | def bar(self): |
|
360 | 389 | """ This is a docstring for Foo.bar """ |
|
361 | 390 | pass |
|
362 |
''' |
|
|
363 | ||
|
391 | ''' | |
|
392 | ||
|
364 | 393 | ip.run_cell(obj_def) |
|
365 | 394 | ip.run_cell('foo = Foo()') |
|
366 | ||
|
395 | ||
|
367 | 396 | with AssertNotPrints("Source:"): |
|
368 | 397 | with AssertPrints('Docstring:'): |
|
369 | 398 | ip._inspect('pinfo', 'foo', detail_level=0) |
|
370 | 399 | with AssertPrints('Docstring:'): |
|
371 | 400 | ip._inspect('pinfo', 'foo', detail_level=1) |
|
372 | 401 | with AssertPrints('Docstring:'): |
|
373 | 402 | ip._inspect('pinfo', 'foo.bar', detail_level=0) |
|
374 | 403 | |
|
375 | 404 | with AssertNotPrints('Docstring:'): |
|
376 | 405 | with AssertPrints('Source:'): |
|
377 | 406 | ip._inspect('pinfo', 'foo.bar', detail_level=1) |
|
378 | 407 | |
|
379 | 408 | |
|
380 | 409 | def test_pinfo_magic(): |
|
381 | 410 | with AssertPrints('Docstring:'): |
|
382 | 411 | ip._inspect('pinfo', 'lsmagic', detail_level=0) |
|
383 | 412 | |
|
384 | 413 | with AssertPrints('Source:'): |
|
385 | 414 | ip._inspect('pinfo', 'lsmagic', detail_level=1) |
|
386 | 415 | |
|
387 | 416 | |
|
388 | 417 | def test_init_colors(): |
|
389 | 418 | # ensure colors are not present in signature info |
|
390 | 419 | info = inspector.info(HasSignature) |
|
391 |
init_def = info[ |
|
|
392 |
|
|
|
420 | init_def = info["init_definition"] | |
|
421 | assert "[0m" not in init_def | |
|
393 | 422 | |
|
394 | 423 | |
|
395 | 424 | def test_builtin_init(): |
|
396 | 425 | info = inspector.info(list) |
|
397 | 426 | init_def = info['init_definition'] |
|
398 | nt.assert_is_not_none(init_def) | |
|
427 | assert init_def is not None | |
|
399 | 428 | |
|
400 | 429 | |
|
401 | 430 | def test_render_signature_short(): |
|
402 | 431 | def short_fun(a=1): pass |
|
403 | 432 | sig = oinspect._render_signature( |
|
404 | 433 | signature(short_fun), |
|
405 | 434 | short_fun.__name__, |
|
406 | 435 | ) |
|
407 |
|
|
|
436 | assert sig == "short_fun(a=1)" | |
|
408 | 437 | |
|
409 | 438 | |
|
410 | 439 | def test_render_signature_long(): |
|
411 | 440 | from typing import Optional |
|
412 | 441 | |
|
413 | 442 | def long_function( |
|
414 | 443 | a_really_long_parameter: int, |
|
415 | 444 | and_another_long_one: bool = False, |
|
416 | 445 | let_us_make_sure_this_is_looong: Optional[str] = None, |
|
417 | 446 | ) -> bool: pass |
|
418 | 447 | |
|
419 | 448 | sig = oinspect._render_signature( |
|
420 | 449 | signature(long_function), |
|
421 | 450 | long_function.__name__, |
|
422 | 451 | ) |
|
423 |
|
|
|
452 | assert sig in [ | |
|
424 | 453 | # Python >=3.9 |
|
425 | 454 | '''\ |
|
426 | 455 | long_function( |
|
427 | 456 | a_really_long_parameter: int, |
|
428 | 457 | and_another_long_one: bool = False, |
|
429 | 458 | let_us_make_sure_this_is_looong: Optional[str] = None, |
|
430 | 459 | ) -> bool\ |
|
431 | 460 | ''', |
|
432 | 461 | # Python >=3.7 |
|
433 | 462 | '''\ |
|
434 | 463 | long_function( |
|
435 | 464 | a_really_long_parameter: int, |
|
436 | 465 | and_another_long_one: bool = False, |
|
437 | 466 | let_us_make_sure_this_is_looong: Union[str, NoneType] = None, |
|
438 | 467 | ) -> bool\ |
|
439 | 468 | ''', # Python <=3.6 |
|
440 | 469 | '''\ |
|
441 | 470 | long_function( |
|
442 | 471 | a_really_long_parameter:int, |
|
443 | 472 | and_another_long_one:bool=False, |
|
444 | 473 | let_us_make_sure_this_is_looong:Union[str, NoneType]=None, |
|
445 | 474 | ) -> bool\ |
|
446 | 475 | ''', |
|
447 |
] |
|
|
476 | ] No newline at end of file |
@@ -1,472 +1,476 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 |
|
9 | 9 | import os |
|
10 | 10 | import types |
|
11 | 11 | import string |
|
12 | import sys | |
|
12 | 13 | import unittest |
|
13 | 14 | |
|
14 | 15 | import nose.tools as nt |
|
15 | 16 | |
|
16 | 17 | from IPython.lib import pretty |
|
17 | 18 | from IPython.testing.decorators import skip_without |
|
18 | 19 | |
|
19 | 20 | from io import StringIO |
|
20 | 21 | |
|
21 | 22 | |
|
22 | 23 | class MyList(object): |
|
23 | 24 | def __init__(self, content): |
|
24 | 25 | self.content = content |
|
25 | 26 | def _repr_pretty_(self, p, cycle): |
|
26 | 27 | if cycle: |
|
27 | 28 | p.text("MyList(...)") |
|
28 | 29 | else: |
|
29 | 30 | with p.group(3, "MyList(", ")"): |
|
30 | 31 | for (i, child) in enumerate(self.content): |
|
31 | 32 | if i: |
|
32 | 33 | p.text(",") |
|
33 | 34 | p.breakable() |
|
34 | 35 | else: |
|
35 | 36 | p.breakable("") |
|
36 | 37 | p.pretty(child) |
|
37 | 38 | |
|
38 | 39 | |
|
39 | 40 | class MyDict(dict): |
|
40 | 41 | def _repr_pretty_(self, p, cycle): |
|
41 | 42 | p.text("MyDict(...)") |
|
42 | 43 | |
|
43 | 44 | class MyObj(object): |
|
44 | 45 | def somemethod(self): |
|
45 | 46 | pass |
|
46 | 47 | |
|
47 | 48 | |
|
48 | 49 | class Dummy1(object): |
|
49 | 50 | def _repr_pretty_(self, p, cycle): |
|
50 | 51 | p.text("Dummy1(...)") |
|
51 | 52 | |
|
52 | 53 | class Dummy2(Dummy1): |
|
53 | 54 | _repr_pretty_ = None |
|
54 | 55 | |
|
55 | 56 | class NoModule(object): |
|
56 | 57 | pass |
|
57 | 58 | |
|
58 | 59 | NoModule.__module__ = None |
|
59 | 60 | |
|
60 | 61 | class Breaking(object): |
|
61 | 62 | def _repr_pretty_(self, p, cycle): |
|
62 | 63 | with p.group(4,"TG: ",":"): |
|
63 | 64 | p.text("Breaking(") |
|
64 | 65 | p.break_() |
|
65 | 66 | p.text(")") |
|
66 | 67 | |
|
67 | 68 | class BreakingRepr(object): |
|
68 | 69 | def __repr__(self): |
|
69 | 70 | return "Breaking(\n)" |
|
70 | 71 | |
|
71 | 72 | class BadRepr(object): |
|
72 | 73 | |
|
73 | 74 | def __repr__(self): |
|
74 | 75 | return 1/0 |
|
75 | 76 | |
|
76 | 77 | |
|
77 | 78 | def test_indentation(): |
|
78 | 79 | """Test correct indentation in groups""" |
|
79 | 80 | count = 40 |
|
80 | 81 | gotoutput = pretty.pretty(MyList(range(count))) |
|
81 | 82 | expectedoutput = "MyList(\n" + ",\n".join(" %d" % i for i in range(count)) + ")" |
|
82 | 83 | |
|
83 | 84 | nt.assert_equal(gotoutput, expectedoutput) |
|
84 | 85 | |
|
85 | 86 | |
|
86 | 87 | def test_dispatch(): |
|
87 | 88 | """ |
|
88 | 89 | Test correct dispatching: The _repr_pretty_ method for MyDict |
|
89 | 90 | must be found before the registered printer for dict. |
|
90 | 91 | """ |
|
91 | 92 | gotoutput = pretty.pretty(MyDict()) |
|
92 | 93 | expectedoutput = "MyDict(...)" |
|
93 | 94 | |
|
94 | 95 | nt.assert_equal(gotoutput, expectedoutput) |
|
95 | 96 | |
|
96 | 97 | |
|
97 | 98 | def test_callability_checking(): |
|
98 | 99 | """ |
|
99 | 100 | Test that the _repr_pretty_ method is tested for callability and skipped if |
|
100 | 101 | not. |
|
101 | 102 | """ |
|
102 | 103 | gotoutput = pretty.pretty(Dummy2()) |
|
103 | 104 | expectedoutput = "Dummy1(...)" |
|
104 | 105 | |
|
105 | 106 | nt.assert_equal(gotoutput, expectedoutput) |
|
106 | 107 | |
|
107 | 108 | |
|
108 | 109 | def test_sets(): |
|
109 | 110 | """ |
|
110 | 111 | Test that set and frozenset use Python 3 formatting. |
|
111 | 112 | """ |
|
112 | 113 | objects = [set(), frozenset(), set([1]), frozenset([1]), set([1, 2]), |
|
113 | 114 | frozenset([1, 2]), set([-1, -2, -3])] |
|
114 | 115 | expected = ['set()', 'frozenset()', '{1}', 'frozenset({1})', '{1, 2}', |
|
115 | 116 | 'frozenset({1, 2})', '{-3, -2, -1}'] |
|
116 | 117 | for obj, expected_output in zip(objects, expected): |
|
117 | 118 | got_output = pretty.pretty(obj) |
|
118 | 119 | yield nt.assert_equal, got_output, expected_output |
|
119 | 120 | |
|
120 | 121 | |
|
121 | @skip_without('xxlimited') | |
|
122 | @skip_without("xxlimited" if sys.version_info < (3, 10) else "xxlimited_35") | |
|
122 | 123 | def test_pprint_heap_allocated_type(): |
|
123 | 124 | """ |
|
124 | 125 | Test that pprint works for heap allocated types. |
|
125 | 126 | """ |
|
126 | import xxlimited | |
|
127 | if sys.version_info < (3, 10): | |
|
128 | import xxlimited | |
|
129 | else: | |
|
130 | import xxlimited_35 as xxlimited | |
|
127 | 131 | output = pretty.pretty(xxlimited.Null) |
|
128 | 132 | nt.assert_equal(output, 'xxlimited.Null') |
|
129 | 133 | |
|
130 | 134 | def test_pprint_nomod(): |
|
131 | 135 | """ |
|
132 | 136 | Test that pprint works for classes with no __module__. |
|
133 | 137 | """ |
|
134 | 138 | output = pretty.pretty(NoModule) |
|
135 | 139 | nt.assert_equal(output, 'NoModule') |
|
136 | 140 | |
|
137 | 141 | def test_pprint_break(): |
|
138 | 142 | """ |
|
139 | 143 | Test that p.break_ produces expected output |
|
140 | 144 | """ |
|
141 | 145 | output = pretty.pretty(Breaking()) |
|
142 | 146 | expected = "TG: Breaking(\n ):" |
|
143 | 147 | nt.assert_equal(output, expected) |
|
144 | 148 | |
|
145 | 149 | def test_pprint_break_repr(): |
|
146 | 150 | """ |
|
147 | 151 | Test that p.break_ is used in repr |
|
148 | 152 | """ |
|
149 | 153 | output = pretty.pretty([[BreakingRepr()]]) |
|
150 | 154 | expected = "[[Breaking(\n )]]" |
|
151 | 155 | nt.assert_equal(output, expected) |
|
152 | 156 | |
|
153 | 157 | output = pretty.pretty([[BreakingRepr()]*2]) |
|
154 | 158 | expected = "[[Breaking(\n ),\n Breaking(\n )]]" |
|
155 | 159 | nt.assert_equal(output, expected) |
|
156 | 160 | |
|
157 | 161 | def test_bad_repr(): |
|
158 | 162 | """Don't catch bad repr errors""" |
|
159 | 163 | with nt.assert_raises(ZeroDivisionError): |
|
160 | 164 | pretty.pretty(BadRepr()) |
|
161 | 165 | |
|
162 | 166 | class BadException(Exception): |
|
163 | 167 | def __str__(self): |
|
164 | 168 | return -1 |
|
165 | 169 | |
|
166 | 170 | class ReallyBadRepr(object): |
|
167 | 171 | __module__ = 1 |
|
168 | 172 | @property |
|
169 | 173 | def __class__(self): |
|
170 | 174 | raise ValueError("I am horrible") |
|
171 | 175 | |
|
172 | 176 | def __repr__(self): |
|
173 | 177 | raise BadException() |
|
174 | 178 | |
|
175 | 179 | def test_really_bad_repr(): |
|
176 | 180 | with nt.assert_raises(BadException): |
|
177 | 181 | pretty.pretty(ReallyBadRepr()) |
|
178 | 182 | |
|
179 | 183 | |
|
180 | 184 | class SA(object): |
|
181 | 185 | pass |
|
182 | 186 | |
|
183 | 187 | class SB(SA): |
|
184 | 188 | pass |
|
185 | 189 | |
|
186 | 190 | class TestsPretty(unittest.TestCase): |
|
187 | 191 | |
|
188 | 192 | def test_super_repr(self): |
|
189 | 193 | # "<super: module_name.SA, None>" |
|
190 | 194 | output = pretty.pretty(super(SA)) |
|
191 | 195 | self.assertRegex(output, r"<super: \S+.SA, None>") |
|
192 | 196 | |
|
193 | 197 | # "<super: module_name.SA, <module_name.SB at 0x...>>" |
|
194 | 198 | sb = SB() |
|
195 | 199 | output = pretty.pretty(super(SA, sb)) |
|
196 | 200 | self.assertRegex(output, r"<super: \S+.SA,\s+<\S+.SB at 0x\S+>>") |
|
197 | 201 | |
|
198 | 202 | |
|
199 | 203 | def test_long_list(self): |
|
200 | 204 | lis = list(range(10000)) |
|
201 | 205 | p = pretty.pretty(lis) |
|
202 | 206 | last2 = p.rsplit('\n', 2)[-2:] |
|
203 | 207 | self.assertEqual(last2, [' 999,', ' ...]']) |
|
204 | 208 | |
|
205 | 209 | def test_long_set(self): |
|
206 | 210 | s = set(range(10000)) |
|
207 | 211 | p = pretty.pretty(s) |
|
208 | 212 | last2 = p.rsplit('\n', 2)[-2:] |
|
209 | 213 | self.assertEqual(last2, [' 999,', ' ...}']) |
|
210 | 214 | |
|
211 | 215 | def test_long_tuple(self): |
|
212 | 216 | tup = tuple(range(10000)) |
|
213 | 217 | p = pretty.pretty(tup) |
|
214 | 218 | last2 = p.rsplit('\n', 2)[-2:] |
|
215 | 219 | self.assertEqual(last2, [' 999,', ' ...)']) |
|
216 | 220 | |
|
217 | 221 | def test_long_dict(self): |
|
218 | 222 | d = { n:n for n in range(10000) } |
|
219 | 223 | p = pretty.pretty(d) |
|
220 | 224 | last2 = p.rsplit('\n', 2)[-2:] |
|
221 | 225 | self.assertEqual(last2, [' 999: 999,', ' ...}']) |
|
222 | 226 | |
|
223 | 227 | def test_unbound_method(self): |
|
224 | 228 | output = pretty.pretty(MyObj.somemethod) |
|
225 | 229 | self.assertIn('MyObj.somemethod', output) |
|
226 | 230 | |
|
227 | 231 | |
|
228 | 232 | class MetaClass(type): |
|
229 | 233 | def __new__(cls, name): |
|
230 | 234 | return type.__new__(cls, name, (object,), {'name': name}) |
|
231 | 235 | |
|
232 | 236 | def __repr__(self): |
|
233 | 237 | return "[CUSTOM REPR FOR CLASS %s]" % self.name |
|
234 | 238 | |
|
235 | 239 | |
|
236 | 240 | ClassWithMeta = MetaClass('ClassWithMeta') |
|
237 | 241 | |
|
238 | 242 | |
|
239 | 243 | def test_metaclass_repr(): |
|
240 | 244 | output = pretty.pretty(ClassWithMeta) |
|
241 | 245 | nt.assert_equal(output, "[CUSTOM REPR FOR CLASS ClassWithMeta]") |
|
242 | 246 | |
|
243 | 247 | |
|
244 | 248 | def test_unicode_repr(): |
|
245 | 249 | u = u"üniçodé" |
|
246 | 250 | ustr = u |
|
247 | 251 | |
|
248 | 252 | class C(object): |
|
249 | 253 | def __repr__(self): |
|
250 | 254 | return ustr |
|
251 | 255 | |
|
252 | 256 | c = C() |
|
253 | 257 | p = pretty.pretty(c) |
|
254 | 258 | nt.assert_equal(p, u) |
|
255 | 259 | p = pretty.pretty([c]) |
|
256 | 260 | nt.assert_equal(p, u'[%s]' % u) |
|
257 | 261 | |
|
258 | 262 | |
|
259 | 263 | def test_basic_class(): |
|
260 | 264 | def type_pprint_wrapper(obj, p, cycle): |
|
261 | 265 | if obj is MyObj: |
|
262 | 266 | type_pprint_wrapper.called = True |
|
263 | 267 | return pretty._type_pprint(obj, p, cycle) |
|
264 | 268 | type_pprint_wrapper.called = False |
|
265 | 269 | |
|
266 | 270 | stream = StringIO() |
|
267 | 271 | printer = pretty.RepresentationPrinter(stream) |
|
268 | 272 | printer.type_pprinters[type] = type_pprint_wrapper |
|
269 | 273 | printer.pretty(MyObj) |
|
270 | 274 | printer.flush() |
|
271 | 275 | output = stream.getvalue() |
|
272 | 276 | |
|
273 | 277 | nt.assert_equal(output, '%s.MyObj' % __name__) |
|
274 | 278 | nt.assert_true(type_pprint_wrapper.called) |
|
275 | 279 | |
|
276 | 280 | |
|
277 | 281 | def test_collections_defaultdict(): |
|
278 | 282 | # Create defaultdicts with cycles |
|
279 | 283 | a = defaultdict() |
|
280 | 284 | a.default_factory = a |
|
281 | 285 | b = defaultdict(list) |
|
282 | 286 | b['key'] = b |
|
283 | 287 | |
|
284 | 288 | # Dictionary order cannot be relied on, test against single keys. |
|
285 | 289 | cases = [ |
|
286 | 290 | (defaultdict(list), 'defaultdict(list, {})'), |
|
287 | 291 | (defaultdict(list, {'key': '-' * 50}), |
|
288 | 292 | "defaultdict(list,\n" |
|
289 | 293 | " {'key': '--------------------------------------------------'})"), |
|
290 | 294 | (a, 'defaultdict(defaultdict(...), {})'), |
|
291 | 295 | (b, "defaultdict(list, {'key': defaultdict(...)})"), |
|
292 | 296 | ] |
|
293 | 297 | for obj, expected in cases: |
|
294 | 298 | nt.assert_equal(pretty.pretty(obj), expected) |
|
295 | 299 | |
|
296 | 300 | |
|
297 | 301 | def test_collections_ordereddict(): |
|
298 | 302 | # Create OrderedDict with cycle |
|
299 | 303 | a = OrderedDict() |
|
300 | 304 | a['key'] = a |
|
301 | 305 | |
|
302 | 306 | cases = [ |
|
303 | 307 | (OrderedDict(), 'OrderedDict()'), |
|
304 | 308 | (OrderedDict((i, i) for i in range(1000, 1010)), |
|
305 | 309 | 'OrderedDict([(1000, 1000),\n' |
|
306 | 310 | ' (1001, 1001),\n' |
|
307 | 311 | ' (1002, 1002),\n' |
|
308 | 312 | ' (1003, 1003),\n' |
|
309 | 313 | ' (1004, 1004),\n' |
|
310 | 314 | ' (1005, 1005),\n' |
|
311 | 315 | ' (1006, 1006),\n' |
|
312 | 316 | ' (1007, 1007),\n' |
|
313 | 317 | ' (1008, 1008),\n' |
|
314 | 318 | ' (1009, 1009)])'), |
|
315 | 319 | (a, "OrderedDict([('key', OrderedDict(...))])"), |
|
316 | 320 | ] |
|
317 | 321 | for obj, expected in cases: |
|
318 | 322 | nt.assert_equal(pretty.pretty(obj), expected) |
|
319 | 323 | |
|
320 | 324 | |
|
321 | 325 | def test_collections_deque(): |
|
322 | 326 | # Create deque with cycle |
|
323 | 327 | a = deque() |
|
324 | 328 | a.append(a) |
|
325 | 329 | |
|
326 | 330 | cases = [ |
|
327 | 331 | (deque(), 'deque([])'), |
|
328 | 332 | (deque(i for i in range(1000, 1020)), |
|
329 | 333 | 'deque([1000,\n' |
|
330 | 334 | ' 1001,\n' |
|
331 | 335 | ' 1002,\n' |
|
332 | 336 | ' 1003,\n' |
|
333 | 337 | ' 1004,\n' |
|
334 | 338 | ' 1005,\n' |
|
335 | 339 | ' 1006,\n' |
|
336 | 340 | ' 1007,\n' |
|
337 | 341 | ' 1008,\n' |
|
338 | 342 | ' 1009,\n' |
|
339 | 343 | ' 1010,\n' |
|
340 | 344 | ' 1011,\n' |
|
341 | 345 | ' 1012,\n' |
|
342 | 346 | ' 1013,\n' |
|
343 | 347 | ' 1014,\n' |
|
344 | 348 | ' 1015,\n' |
|
345 | 349 | ' 1016,\n' |
|
346 | 350 | ' 1017,\n' |
|
347 | 351 | ' 1018,\n' |
|
348 | 352 | ' 1019])'), |
|
349 | 353 | (a, 'deque([deque(...)])'), |
|
350 | 354 | ] |
|
351 | 355 | for obj, expected in cases: |
|
352 | 356 | nt.assert_equal(pretty.pretty(obj), expected) |
|
353 | 357 | |
|
354 | 358 | def test_collections_counter(): |
|
355 | 359 | class MyCounter(Counter): |
|
356 | 360 | pass |
|
357 | 361 | cases = [ |
|
358 | 362 | (Counter(), 'Counter()'), |
|
359 | 363 | (Counter(a=1), "Counter({'a': 1})"), |
|
360 | 364 | (MyCounter(a=1), "MyCounter({'a': 1})"), |
|
361 | 365 | ] |
|
362 | 366 | for obj, expected in cases: |
|
363 | 367 | nt.assert_equal(pretty.pretty(obj), expected) |
|
364 | 368 | |
|
365 | 369 | def test_mappingproxy(): |
|
366 | 370 | MP = types.MappingProxyType |
|
367 | 371 | underlying_dict = {} |
|
368 | 372 | mp_recursive = MP(underlying_dict) |
|
369 | 373 | underlying_dict[2] = mp_recursive |
|
370 | 374 | underlying_dict[3] = underlying_dict |
|
371 | 375 | |
|
372 | 376 | cases = [ |
|
373 | 377 | (MP({}), "mappingproxy({})"), |
|
374 | 378 | (MP({None: MP({})}), "mappingproxy({None: mappingproxy({})})"), |
|
375 | 379 | (MP({k: k.upper() for k in string.ascii_lowercase}), |
|
376 | 380 | "mappingproxy({'a': 'A',\n" |
|
377 | 381 | " 'b': 'B',\n" |
|
378 | 382 | " 'c': 'C',\n" |
|
379 | 383 | " 'd': 'D',\n" |
|
380 | 384 | " 'e': 'E',\n" |
|
381 | 385 | " 'f': 'F',\n" |
|
382 | 386 | " 'g': 'G',\n" |
|
383 | 387 | " 'h': 'H',\n" |
|
384 | 388 | " 'i': 'I',\n" |
|
385 | 389 | " 'j': 'J',\n" |
|
386 | 390 | " 'k': 'K',\n" |
|
387 | 391 | " 'l': 'L',\n" |
|
388 | 392 | " 'm': 'M',\n" |
|
389 | 393 | " 'n': 'N',\n" |
|
390 | 394 | " 'o': 'O',\n" |
|
391 | 395 | " 'p': 'P',\n" |
|
392 | 396 | " 'q': 'Q',\n" |
|
393 | 397 | " 'r': 'R',\n" |
|
394 | 398 | " 's': 'S',\n" |
|
395 | 399 | " 't': 'T',\n" |
|
396 | 400 | " 'u': 'U',\n" |
|
397 | 401 | " 'v': 'V',\n" |
|
398 | 402 | " 'w': 'W',\n" |
|
399 | 403 | " 'x': 'X',\n" |
|
400 | 404 | " 'y': 'Y',\n" |
|
401 | 405 | " 'z': 'Z'})"), |
|
402 | 406 | (mp_recursive, "mappingproxy({2: {...}, 3: {2: {...}, 3: {...}}})"), |
|
403 | 407 | (underlying_dict, |
|
404 | 408 | "{2: mappingproxy({2: {...}, 3: {...}}), 3: {...}}"), |
|
405 | 409 | ] |
|
406 | 410 | for obj, expected in cases: |
|
407 | 411 | nt.assert_equal(pretty.pretty(obj), expected) |
|
408 | 412 | |
|
409 | 413 | |
|
410 | 414 | def test_simplenamespace(): |
|
411 | 415 | SN = types.SimpleNamespace |
|
412 | 416 | |
|
413 | 417 | sn_recursive = SN() |
|
414 | 418 | sn_recursive.first = sn_recursive |
|
415 | 419 | sn_recursive.second = sn_recursive |
|
416 | 420 | cases = [ |
|
417 | 421 | (SN(), "namespace()"), |
|
418 | 422 | (SN(x=SN()), "namespace(x=namespace())"), |
|
419 | 423 | (SN(a_long_name=[SN(s=string.ascii_lowercase)]*3, a_short_name=None), |
|
420 | 424 | "namespace(a_long_name=[namespace(s='abcdefghijklmnopqrstuvwxyz'),\n" |
|
421 | 425 | " namespace(s='abcdefghijklmnopqrstuvwxyz'),\n" |
|
422 | 426 | " namespace(s='abcdefghijklmnopqrstuvwxyz')],\n" |
|
423 | 427 | " a_short_name=None)"), |
|
424 | 428 | (sn_recursive, "namespace(first=namespace(...), second=namespace(...))"), |
|
425 | 429 | ] |
|
426 | 430 | for obj, expected in cases: |
|
427 | 431 | nt.assert_equal(pretty.pretty(obj), expected) |
|
428 | 432 | |
|
429 | 433 | |
|
430 | 434 | def test_pretty_environ(): |
|
431 | 435 | dict_repr = pretty.pretty(dict(os.environ)) |
|
432 | 436 | # reindent to align with 'environ' prefix |
|
433 | 437 | dict_indented = dict_repr.replace('\n', '\n' + (' ' * len('environ'))) |
|
434 | 438 | env_repr = pretty.pretty(os.environ) |
|
435 | 439 | nt.assert_equal(env_repr, 'environ' + dict_indented) |
|
436 | 440 | |
|
437 | 441 | |
|
438 | 442 | def test_function_pretty(): |
|
439 | 443 | "Test pretty print of function" |
|
440 | 444 | # posixpath is a pure python module, its interface is consistent |
|
441 | 445 | # across Python distributions |
|
442 | 446 | import posixpath |
|
443 | 447 | nt.assert_equal(pretty.pretty(posixpath.join), '<function posixpath.join(a, *p)>') |
|
444 | 448 | |
|
445 | 449 | # custom function |
|
446 | 450 | def meaning_of_life(question=None): |
|
447 | 451 | if question: |
|
448 | 452 | return 42 |
|
449 | 453 | return "Don't panic" |
|
450 | 454 | |
|
451 | 455 | nt.assert_in('meaning_of_life(question=None)', pretty.pretty(meaning_of_life)) |
|
452 | 456 | |
|
453 | 457 | |
|
454 | 458 | class OrderedCounter(Counter, OrderedDict): |
|
455 | 459 | 'Counter that remembers the order elements are first encountered' |
|
456 | 460 | |
|
457 | 461 | def __repr__(self): |
|
458 | 462 | return '%s(%r)' % (self.__class__.__name__, OrderedDict(self)) |
|
459 | 463 | |
|
460 | 464 | def __reduce__(self): |
|
461 | 465 | return self.__class__, (OrderedDict(self),) |
|
462 | 466 | |
|
463 | 467 | class MySet(set): # Override repr of a basic type |
|
464 | 468 | def __repr__(self): |
|
465 | 469 | return 'mine' |
|
466 | 470 | |
|
467 | 471 | def test_custom_repr(): |
|
468 | 472 | """A custom repr should override a pretty printer for a parent type""" |
|
469 | 473 | oc = OrderedCounter("abracadabra") |
|
470 | 474 | nt.assert_in("OrderedCounter(OrderedDict", pretty.pretty(oc)) |
|
471 | 475 | |
|
472 | 476 | nt.assert_equal(pretty.pretty(MySet()), 'mine') |
General Comments 0
You need to be logged in to leave comments.
Login now