##// END OF EJS Templates
MAINT: remove support and testing on Python 3.8...
Matthias Bussonnier -
Show More
@@ -1,98 +1,98 b''
1 1 name: Run tests
2 2
3 3 on:
4 4 push:
5 5 branches:
6 6 - main
7 7 - '*.x'
8 8 pull_request:
9 9 # Run weekly on Monday at 1:23 UTC
10 10 schedule:
11 11 - cron: '23 1 * * 1'
12 12 workflow_dispatch:
13 13
14 14
15 15 jobs:
16 16 test:
17 17 runs-on: ${{ matrix.os }}
18 18 strategy:
19 19 fail-fast: false
20 20 matrix:
21 21 os: [ubuntu-latest, windows-latest]
22 python-version: ["3.8", "3.9", "3.10", "3.11"]
22 python-version: ["3.9", "3.10", "3.11"]
23 23 deps: [test_extra]
24 24 # Test all on ubuntu, test ends on macos
25 25 include:
26 26 - os: macos-latest
27 python-version: "3.8"
27 python-version: "3.9"
28 28 deps: test_extra
29 29 - os: macos-latest
30 30 python-version: "3.11"
31 31 deps: test_extra
32 32 # Tests minimal dependencies set
33 33 - os: ubuntu-latest
34 34 python-version: "3.11"
35 35 deps: test
36 36 # Tests latest development Python version
37 37 - os: ubuntu-latest
38 38 python-version: "3.12-dev"
39 39 deps: test
40 40 # Installing optional dependencies stuff takes ages on PyPy
41 41 - os: ubuntu-latest
42 python-version: "pypy-3.8"
42 python-version: "pypy-3.9"
43 43 deps: test
44 44 - os: windows-latest
45 python-version: "pypy-3.8"
45 python-version: "pypy-3.9"
46 46 deps: test
47 47 - os: macos-latest
48 python-version: "pypy-3.8"
48 python-version: "pypy-3.9"
49 49 deps: test
50 50
51 51 steps:
52 52 - uses: actions/checkout@v3
53 53 - name: Set up Python ${{ matrix.python-version }}
54 54 uses: actions/setup-python@v4
55 55 with:
56 56 python-version: ${{ matrix.python-version }}
57 57 cache: pip
58 58 cache-dependency-path: |
59 59 setup.cfg
60 60 - name: Install latex
61 61 if: runner.os == 'Linux' && matrix.deps == 'test_extra'
62 62 run: echo "disable latex for now, issues in mirros" #sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng
63 63 - name: Install and update Python dependencies (binary only)
64 64 if: ${{ ! contains( matrix.python-version, 'dev' ) }}
65 65 run: |
66 66 python -m pip install --only-binary ':all:' --upgrade pip setuptools wheel build
67 67 python -m pip install --only-binary ':all:' --no-binary curio --upgrade -e .[${{ matrix.deps }}]
68 68 python -m pip install --only-binary ':all:' --upgrade check-manifest pytest-cov pytest-json-report
69 69 - name: Install and update Python dependencies (dev?)
70 70 if: ${{ contains( matrix.python-version, 'dev' ) }}
71 71 run: |
72 72 python -m pip install --pre --upgrade pip setuptools wheel build
73 73 python -m pip install --pre --no-binary curio --upgrade -e .[${{ matrix.deps }}]
74 74 python -m pip install --pre --upgrade check-manifest pytest-cov pytest-json-report
75 75 - name: Try building with Python build
76 76 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
77 77 run: |
78 78 python -m build
79 79 shasum -a 256 dist/*
80 80 - name: Check manifest
81 81 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
82 82 run: check-manifest
83 83 - name: pytest
84 84 env:
85 85 COLUMNS: 120
86 86 run: |
87 87 pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }} --json-report --json-report-file=./report-${{ matrix.python-version }}-${{runner.os}}.json
88 88 - uses: actions/upload-artifact@v3
89 89 with:
90 90 name: upload pytest timing reports as json
91 91 path: |
92 92 ./report-*.json
93 93
94 94 - name: Upload coverage to Codecov
95 95 uses: codecov/codecov-action@v3
96 96 with:
97 97 name: Test
98 98 files: /home/runner/work/ipython/ipython/coverage.xml
@@ -1,161 +1,162 b''
1 1 # PYTHON_ARGCOMPLETE_OK
2 2 """
3 3 IPython: tools for interactive and parallel computing in Python.
4 4
5 5 https://ipython.org
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (c) 2008-2011, IPython Development Team.
9 9 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
10 10 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
11 11 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
12 12 #
13 13 # Distributed under the terms of the Modified BSD License.
14 14 #
15 15 # The full license is in the file COPYING.txt, distributed with this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import sys
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Setup everything
26 26 #-----------------------------------------------------------------------------
27 27
28 28 # Don't forget to also update setup.py when this changes!
29 if sys.version_info < (3, 8):
29 if sys.version_info < (3, 9):
30 30 raise ImportError(
31 31 """
32 IPython 8+ supports Python 3.8 and above, following NEP 29.
32 IPython 8.13+ supports Python 3.9 and above, following NEP 29.
33 IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29.
33 34 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
34 35 Python 3.3 and 3.4 were supported up to IPython 6.x.
35 36 Python 3.5 was supported with IPython 7.0 to 7.9.
36 37 Python 3.6 was supported with IPython up to 7.16.
37 38 Python 3.7 was still supported with the 7.x branch.
38 39
39 40 See IPython `README.rst` file for more information:
40 41
41 42 https://github.com/ipython/ipython/blob/main/README.rst
42 43
43 44 """
44 45 )
45 46
46 47 #-----------------------------------------------------------------------------
47 48 # Setup the top level names
48 49 #-----------------------------------------------------------------------------
49 50
50 51 from .core.getipython import get_ipython
51 52 from .core import release
52 53 from .core.application import Application
53 54 from .terminal.embed import embed
54 55
55 56 from .core.interactiveshell import InteractiveShell
56 57 from .utils.sysinfo import sys_info
57 58 from .utils.frame import extract_module_locals
58 59
59 60 __all__ = ["start_ipython", "embed", "start_kernel", "embed_kernel"]
60 61
61 62 # Release data
62 63 __author__ = '%s <%s>' % (release.author, release.author_email)
63 64 __license__ = release.license
64 65 __version__ = release.version
65 66 version_info = release.version_info
66 67 # list of CVEs that should have been patched in this release.
67 68 # this is informational and should not be relied upon.
68 69 __patched_cves__ = {"CVE-2022-21699", "CVE-2023-24816"}
69 70
70 71
71 72 def embed_kernel(module=None, local_ns=None, **kwargs):
72 73 """Embed and start an IPython kernel in a given scope.
73 74
74 75 If you don't want the kernel to initialize the namespace
75 76 from the scope of the surrounding function,
76 77 and/or you want to load full IPython configuration,
77 78 you probably want `IPython.start_kernel()` instead.
78 79
79 80 Parameters
80 81 ----------
81 82 module : types.ModuleType, optional
82 83 The module to load into IPython globals (default: caller)
83 84 local_ns : dict, optional
84 85 The namespace to load into IPython user namespace (default: caller)
85 86 **kwargs : various, optional
86 87 Further keyword args are relayed to the IPKernelApp constructor,
87 88 such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
88 89 allowing configuration of the kernel (see :ref:`kernel_options`). Will only have an effect
89 90 on the first embed_kernel call for a given process.
90 91 """
91 92
92 93 (caller_module, caller_locals) = extract_module_locals(1)
93 94 if module is None:
94 95 module = caller_module
95 96 if local_ns is None:
96 97 local_ns = caller_locals
97 98
98 99 # Only import .zmq when we really need it
99 100 from ipykernel.embed import embed_kernel as real_embed_kernel
100 101 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
101 102
102 103 def start_ipython(argv=None, **kwargs):
103 104 """Launch a normal IPython instance (as opposed to embedded)
104 105
105 106 `IPython.embed()` puts a shell in a particular calling scope,
106 107 such as a function or method for debugging purposes,
107 108 which is often not desirable.
108 109
109 110 `start_ipython()` does full, regular IPython initialization,
110 111 including loading startup files, configuration, etc.
111 112 much of which is skipped by `embed()`.
112 113
113 114 This is a public API method, and will survive implementation changes.
114 115
115 116 Parameters
116 117 ----------
117 118 argv : list or None, optional
118 119 If unspecified or None, IPython will parse command-line options from sys.argv.
119 120 To prevent any command-line parsing, pass an empty list: `argv=[]`.
120 121 user_ns : dict, optional
121 122 specify this dictionary to initialize the IPython user namespace with particular values.
122 123 **kwargs : various, optional
123 124 Any other kwargs will be passed to the Application constructor,
124 125 such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
125 126 allowing configuration of the instance (see :ref:`terminal_options`).
126 127 """
127 128 from IPython.terminal.ipapp import launch_new_instance
128 129 return launch_new_instance(argv=argv, **kwargs)
129 130
130 131 def start_kernel(argv=None, **kwargs):
131 132 """Launch a normal IPython kernel instance (as opposed to embedded)
132 133
133 134 `IPython.embed_kernel()` puts a shell in a particular calling scope,
134 135 such as a function or method for debugging purposes,
135 136 which is often not desirable.
136 137
137 138 `start_kernel()` does full, regular IPython initialization,
138 139 including loading startup files, configuration, etc.
139 140 much of which is skipped by `embed_kernel()`.
140 141
141 142 Parameters
142 143 ----------
143 144 argv : list or None, optional
144 145 If unspecified or None, IPython will parse command-line options from sys.argv.
145 146 To prevent any command-line parsing, pass an empty list: `argv=[]`.
146 147 user_ns : dict, optional
147 148 specify this dictionary to initialize the IPython user namespace with particular values.
148 149 **kwargs : various, optional
149 150 Any other kwargs will be passed to the Application constructor,
150 151 such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
151 152 allowing configuration of the kernel (see :ref:`kernel_options`).
152 153 """
153 154 import warnings
154 155
155 156 warnings.warn(
156 157 "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`",
157 158 DeprecationWarning,
158 159 stacklevel=2,
159 160 )
160 161 from ipykernel.kernelapp import launch_new_instance
161 162 return launch_new_instance(argv=argv, **kwargs)
@@ -1,738 +1,732 b''
1 1 from typing import (
2 2 Any,
3 3 Callable,
4 4 Dict,
5 5 Set,
6 6 Sequence,
7 7 Tuple,
8 8 NamedTuple,
9 9 Type,
10 10 Literal,
11 11 Union,
12 12 TYPE_CHECKING,
13 13 )
14 14 import ast
15 15 import builtins
16 16 import collections
17 17 import operator
18 18 import sys
19 19 from functools import cached_property
20 20 from dataclasses import dataclass, field
21 21
22 22 from IPython.utils.docs import GENERATING_DOCUMENTATION
23 23 from IPython.utils.decorators import undoc
24 24
25 25
26 26 if TYPE_CHECKING or GENERATING_DOCUMENTATION:
27 27 from typing_extensions import Protocol
28 28 else:
29 29 # do not require on runtime
30 30 Protocol = object # requires Python >=3.8
31 31
32 32
33 33 @undoc
34 34 class HasGetItem(Protocol):
35 35 def __getitem__(self, key) -> None:
36 36 ...
37 37
38 38
39 39 @undoc
40 40 class InstancesHaveGetItem(Protocol):
41 41 def __call__(self, *args, **kwargs) -> HasGetItem:
42 42 ...
43 43
44 44
45 45 @undoc
46 46 class HasGetAttr(Protocol):
47 47 def __getattr__(self, key) -> None:
48 48 ...
49 49
50 50
51 51 @undoc
52 52 class DoesNotHaveGetAttr(Protocol):
53 53 pass
54 54
55 55
56 56 # By default `__getattr__` is not explicitly implemented on most objects
57 57 MayHaveGetattr = Union[HasGetAttr, DoesNotHaveGetAttr]
58 58
59 59
60 60 def _unbind_method(func: Callable) -> Union[Callable, None]:
61 61 """Get unbound method for given bound method.
62 62
63 63 Returns None if cannot get unbound method, or method is already unbound.
64 64 """
65 65 owner = getattr(func, "__self__", None)
66 66 owner_class = type(owner)
67 67 name = getattr(func, "__name__", None)
68 68 instance_dict_overrides = getattr(owner, "__dict__", None)
69 69 if (
70 70 owner is not None
71 71 and name
72 72 and (
73 73 not instance_dict_overrides
74 74 or (instance_dict_overrides and name not in instance_dict_overrides)
75 75 )
76 76 ):
77 77 return getattr(owner_class, name)
78 78 return None
79 79
80 80
81 81 @undoc
82 82 @dataclass
83 83 class EvaluationPolicy:
84 84 """Definition of evaluation policy."""
85 85
86 86 allow_locals_access: bool = False
87 87 allow_globals_access: bool = False
88 88 allow_item_access: bool = False
89 89 allow_attr_access: bool = False
90 90 allow_builtins_access: bool = False
91 91 allow_all_operations: bool = False
92 92 allow_any_calls: bool = False
93 93 allowed_calls: Set[Callable] = field(default_factory=set)
94 94
95 95 def can_get_item(self, value, item):
96 96 return self.allow_item_access
97 97
98 98 def can_get_attr(self, value, attr):
99 99 return self.allow_attr_access
100 100
101 101 def can_operate(self, dunders: Tuple[str, ...], a, b=None):
102 102 if self.allow_all_operations:
103 103 return True
104 104
105 105 def can_call(self, func):
106 106 if self.allow_any_calls:
107 107 return True
108 108
109 109 if func in self.allowed_calls:
110 110 return True
111 111
112 112 owner_method = _unbind_method(func)
113 113
114 114 if owner_method and owner_method in self.allowed_calls:
115 115 return True
116 116
117 117
118 118 def _get_external(module_name: str, access_path: Sequence[str]):
119 119 """Get value from external module given a dotted access path.
120 120
121 121 Raises:
122 122 * `KeyError` if module is removed not found, and
123 123 * `AttributeError` if acess path does not match an exported object
124 124 """
125 125 member_type = sys.modules[module_name]
126 126 for attr in access_path:
127 127 member_type = getattr(member_type, attr)
128 128 return member_type
129 129
130 130
131 131 def _has_original_dunder_external(
132 132 value,
133 133 module_name: str,
134 134 access_path: Sequence[str],
135 135 method_name: str,
136 136 ):
137 137 if module_name not in sys.modules:
138 138 # LBYLB as it is faster
139 139 return False
140 140 try:
141 141 member_type = _get_external(module_name, access_path)
142 142 value_type = type(value)
143 143 if type(value) == member_type:
144 144 return True
145 145 if method_name == "__getattribute__":
146 146 # we have to short-circuit here due to an unresolved issue in
147 147 # `isinstance` implementation: https://bugs.python.org/issue32683
148 148 return False
149 149 if isinstance(value, member_type):
150 150 method = getattr(value_type, method_name, None)
151 151 member_method = getattr(member_type, method_name, None)
152 152 if member_method == method:
153 153 return True
154 154 except (AttributeError, KeyError):
155 155 return False
156 156
157 157
158 158 def _has_original_dunder(
159 159 value, allowed_types, allowed_methods, allowed_external, method_name
160 160 ):
161 161 # note: Python ignores `__getattr__`/`__getitem__` on instances,
162 162 # we only need to check at class level
163 163 value_type = type(value)
164 164
165 165 # strict type check passes β†’ no need to check method
166 166 if value_type in allowed_types:
167 167 return True
168 168
169 169 method = getattr(value_type, method_name, None)
170 170
171 171 if method is None:
172 172 return None
173 173
174 174 if method in allowed_methods:
175 175 return True
176 176
177 177 for module_name, *access_path in allowed_external:
178 178 if _has_original_dunder_external(value, module_name, access_path, method_name):
179 179 return True
180 180
181 181 return False
182 182
183 183
184 184 @undoc
185 185 @dataclass
186 186 class SelectivePolicy(EvaluationPolicy):
187 187 allowed_getitem: Set[InstancesHaveGetItem] = field(default_factory=set)
188 188 allowed_getitem_external: Set[Tuple[str, ...]] = field(default_factory=set)
189 189
190 190 allowed_getattr: Set[MayHaveGetattr] = field(default_factory=set)
191 191 allowed_getattr_external: Set[Tuple[str, ...]] = field(default_factory=set)
192 192
193 193 allowed_operations: Set = field(default_factory=set)
194 194 allowed_operations_external: Set[Tuple[str, ...]] = field(default_factory=set)
195 195
196 196 _operation_methods_cache: Dict[str, Set[Callable]] = field(
197 197 default_factory=dict, init=False
198 198 )
199 199
200 200 def can_get_attr(self, value, attr):
201 201 has_original_attribute = _has_original_dunder(
202 202 value,
203 203 allowed_types=self.allowed_getattr,
204 204 allowed_methods=self._getattribute_methods,
205 205 allowed_external=self.allowed_getattr_external,
206 206 method_name="__getattribute__",
207 207 )
208 208 has_original_attr = _has_original_dunder(
209 209 value,
210 210 allowed_types=self.allowed_getattr,
211 211 allowed_methods=self._getattr_methods,
212 212 allowed_external=self.allowed_getattr_external,
213 213 method_name="__getattr__",
214 214 )
215 215
216 216 accept = False
217 217
218 218 # Many objects do not have `__getattr__`, this is fine.
219 219 if has_original_attr is None and has_original_attribute:
220 220 accept = True
221 221 else:
222 222 # Accept objects without modifications to `__getattr__` and `__getattribute__`
223 223 accept = has_original_attr and has_original_attribute
224 224
225 225 if accept:
226 226 # We still need to check for overriden properties.
227 227
228 228 value_class = type(value)
229 229 if not hasattr(value_class, attr):
230 230 return True
231 231
232 232 class_attr_val = getattr(value_class, attr)
233 233 is_property = isinstance(class_attr_val, property)
234 234
235 235 if not is_property:
236 236 return True
237 237
238 238 # Properties in allowed types are ok (although we do not include any
239 239 # properties in our default allow list currently).
240 240 if type(value) in self.allowed_getattr:
241 241 return True # pragma: no cover
242 242
243 243 # Properties in subclasses of allowed types may be ok if not changed
244 244 for module_name, *access_path in self.allowed_getattr_external:
245 245 try:
246 246 external_class = _get_external(module_name, access_path)
247 247 external_class_attr_val = getattr(external_class, attr)
248 248 except (KeyError, AttributeError):
249 249 return False # pragma: no cover
250 250 return class_attr_val == external_class_attr_val
251 251
252 252 return False
253 253
254 254 def can_get_item(self, value, item):
255 255 """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified."""
256 256 return _has_original_dunder(
257 257 value,
258 258 allowed_types=self.allowed_getitem,
259 259 allowed_methods=self._getitem_methods,
260 260 allowed_external=self.allowed_getitem_external,
261 261 method_name="__getitem__",
262 262 )
263 263
264 264 def can_operate(self, dunders: Tuple[str, ...], a, b=None):
265 265 objects = [a]
266 266 if b is not None:
267 267 objects.append(b)
268 268 return all(
269 269 [
270 270 _has_original_dunder(
271 271 obj,
272 272 allowed_types=self.allowed_operations,
273 273 allowed_methods=self._operator_dunder_methods(dunder),
274 274 allowed_external=self.allowed_operations_external,
275 275 method_name=dunder,
276 276 )
277 277 for dunder in dunders
278 278 for obj in objects
279 279 ]
280 280 )
281 281
282 282 def _operator_dunder_methods(self, dunder: str) -> Set[Callable]:
283 283 if dunder not in self._operation_methods_cache:
284 284 self._operation_methods_cache[dunder] = self._safe_get_methods(
285 285 self.allowed_operations, dunder
286 286 )
287 287 return self._operation_methods_cache[dunder]
288 288
289 289 @cached_property
290 290 def _getitem_methods(self) -> Set[Callable]:
291 291 return self._safe_get_methods(self.allowed_getitem, "__getitem__")
292 292
293 293 @cached_property
294 294 def _getattr_methods(self) -> Set[Callable]:
295 295 return self._safe_get_methods(self.allowed_getattr, "__getattr__")
296 296
297 297 @cached_property
298 298 def _getattribute_methods(self) -> Set[Callable]:
299 299 return self._safe_get_methods(self.allowed_getattr, "__getattribute__")
300 300
301 301 def _safe_get_methods(self, classes, name) -> Set[Callable]:
302 302 return {
303 303 method
304 304 for class_ in classes
305 305 for method in [getattr(class_, name, None)]
306 306 if method
307 307 }
308 308
309 309
310 310 class _DummyNamedTuple(NamedTuple):
311 311 """Used internally to retrieve methods of named tuple instance."""
312 312
313 313
314 314 class EvaluationContext(NamedTuple):
315 315 #: Local namespace
316 316 locals: dict
317 317 #: Global namespace
318 318 globals: dict
319 319 #: Evaluation policy identifier
320 320 evaluation: Literal[
321 321 "forbidden", "minimal", "limited", "unsafe", "dangerous"
322 322 ] = "forbidden"
323 323 #: Whether the evalution of code takes place inside of a subscript.
324 324 #: Useful for evaluating ``:-1, 'col'`` in ``df[:-1, 'col']``.
325 325 in_subscript: bool = False
326 326
327 327
328 328 class _IdentitySubscript:
329 329 """Returns the key itself when item is requested via subscript."""
330 330
331 331 def __getitem__(self, key):
332 332 return key
333 333
334 334
335 335 IDENTITY_SUBSCRIPT = _IdentitySubscript()
336 336 SUBSCRIPT_MARKER = "__SUBSCRIPT_SENTINEL__"
337 337
338 338
339 339 class GuardRejection(Exception):
340 340 """Exception raised when guard rejects evaluation attempt."""
341 341
342 342 pass
343 343
344 344
345 345 def guarded_eval(code: str, context: EvaluationContext):
346 346 """Evaluate provided code in the evaluation context.
347 347
348 348 If evaluation policy given by context is set to ``forbidden``
349 349 no evaluation will be performed; if it is set to ``dangerous``
350 350 standard :func:`eval` will be used; finally, for any other,
351 351 policy :func:`eval_node` will be called on parsed AST.
352 352 """
353 353 locals_ = context.locals
354 354
355 355 if context.evaluation == "forbidden":
356 356 raise GuardRejection("Forbidden mode")
357 357
358 358 # note: not using `ast.literal_eval` as it does not implement
359 359 # getitem at all, for example it fails on simple `[0][1]`
360 360
361 361 if context.in_subscript:
362 362 # syntatic sugar for ellipsis (:) is only available in susbcripts
363 363 # so we need to trick the ast parser into thinking that we have
364 364 # a subscript, but we need to be able to later recognise that we did
365 365 # it so we can ignore the actual __getitem__ operation
366 366 if not code:
367 367 return tuple()
368 368 locals_ = locals_.copy()
369 369 locals_[SUBSCRIPT_MARKER] = IDENTITY_SUBSCRIPT
370 370 code = SUBSCRIPT_MARKER + "[" + code + "]"
371 371 context = EvaluationContext(**{**context._asdict(), **{"locals": locals_}})
372 372
373 373 if context.evaluation == "dangerous":
374 374 return eval(code, context.globals, context.locals)
375 375
376 376 expression = ast.parse(code, mode="eval")
377 377
378 378 return eval_node(expression, context)
379 379
380 380
381 381 BINARY_OP_DUNDERS: Dict[Type[ast.operator], Tuple[str]] = {
382 382 ast.Add: ("__add__",),
383 383 ast.Sub: ("__sub__",),
384 384 ast.Mult: ("__mul__",),
385 385 ast.Div: ("__truediv__",),
386 386 ast.FloorDiv: ("__floordiv__",),
387 387 ast.Mod: ("__mod__",),
388 388 ast.Pow: ("__pow__",),
389 389 ast.LShift: ("__lshift__",),
390 390 ast.RShift: ("__rshift__",),
391 391 ast.BitOr: ("__or__",),
392 392 ast.BitXor: ("__xor__",),
393 393 ast.BitAnd: ("__and__",),
394 394 ast.MatMult: ("__matmul__",),
395 395 }
396 396
397 397 COMP_OP_DUNDERS: Dict[Type[ast.cmpop], Tuple[str, ...]] = {
398 398 ast.Eq: ("__eq__",),
399 399 ast.NotEq: ("__ne__", "__eq__"),
400 400 ast.Lt: ("__lt__", "__gt__"),
401 401 ast.LtE: ("__le__", "__ge__"),
402 402 ast.Gt: ("__gt__", "__lt__"),
403 403 ast.GtE: ("__ge__", "__le__"),
404 404 ast.In: ("__contains__",),
405 405 # Note: ast.Is, ast.IsNot, ast.NotIn are handled specially
406 406 }
407 407
408 408 UNARY_OP_DUNDERS: Dict[Type[ast.unaryop], Tuple[str, ...]] = {
409 409 ast.USub: ("__neg__",),
410 410 ast.UAdd: ("__pos__",),
411 411 # we have to check both __inv__ and __invert__!
412 412 ast.Invert: ("__invert__", "__inv__"),
413 413 ast.Not: ("__not__",),
414 414 }
415 415
416 416
417 417 def _find_dunder(node_op, dunders) -> Union[Tuple[str, ...], None]:
418 418 dunder = None
419 419 for op, candidate_dunder in dunders.items():
420 420 if isinstance(node_op, op):
421 421 dunder = candidate_dunder
422 422 return dunder
423 423
424 424
425 425 def eval_node(node: Union[ast.AST, None], context: EvaluationContext):
426 426 """Evaluate AST node in provided context.
427 427
428 428 Applies evaluation restrictions defined in the context. Currently does not support evaluation of functions with keyword arguments.
429 429
430 430 Does not evaluate actions that always have side effects:
431 431
432 432 - class definitions (``class sth: ...``)
433 433 - function definitions (``def sth: ...``)
434 434 - variable assignments (``x = 1``)
435 435 - augmented assignments (``x += 1``)
436 436 - deletions (``del x``)
437 437
438 438 Does not evaluate operations which do not return values:
439 439
440 440 - assertions (``assert x``)
441 441 - pass (``pass``)
442 442 - imports (``import x``)
443 443 - control flow:
444 444
445 445 - conditionals (``if x:``) except for ternary IfExp (``a if x else b``)
446 446 - loops (``for`` and `while``)
447 447 - exception handling
448 448
449 449 The purpose of this function is to guard against unwanted side-effects;
450 450 it does not give guarantees on protection from malicious code execution.
451 451 """
452 452 policy = EVALUATION_POLICIES[context.evaluation]
453 453 if node is None:
454 454 return None
455 455 if isinstance(node, ast.Expression):
456 456 return eval_node(node.body, context)
457 457 if isinstance(node, ast.BinOp):
458 458 left = eval_node(node.left, context)
459 459 right = eval_node(node.right, context)
460 460 dunders = _find_dunder(node.op, BINARY_OP_DUNDERS)
461 461 if dunders:
462 462 if policy.can_operate(dunders, left, right):
463 463 return getattr(left, dunders[0])(right)
464 464 else:
465 465 raise GuardRejection(
466 466 f"Operation (`{dunders}`) for",
467 467 type(left),
468 468 f"not allowed in {context.evaluation} mode",
469 469 )
470 470 if isinstance(node, ast.Compare):
471 471 left = eval_node(node.left, context)
472 472 all_true = True
473 473 negate = False
474 474 for op, right in zip(node.ops, node.comparators):
475 475 right = eval_node(right, context)
476 476 dunder = None
477 477 dunders = _find_dunder(op, COMP_OP_DUNDERS)
478 478 if not dunders:
479 479 if isinstance(op, ast.NotIn):
480 480 dunders = COMP_OP_DUNDERS[ast.In]
481 481 negate = True
482 482 if isinstance(op, ast.Is):
483 483 dunder = "is_"
484 484 if isinstance(op, ast.IsNot):
485 485 dunder = "is_"
486 486 negate = True
487 487 if not dunder and dunders:
488 488 dunder = dunders[0]
489 489 if dunder:
490 490 a, b = (right, left) if dunder == "__contains__" else (left, right)
491 491 if dunder == "is_" or dunders and policy.can_operate(dunders, a, b):
492 492 result = getattr(operator, dunder)(a, b)
493 493 if negate:
494 494 result = not result
495 495 if not result:
496 496 all_true = False
497 497 left = right
498 498 else:
499 499 raise GuardRejection(
500 500 f"Comparison (`{dunder}`) for",
501 501 type(left),
502 502 f"not allowed in {context.evaluation} mode",
503 503 )
504 504 else:
505 505 raise ValueError(
506 506 f"Comparison `{dunder}` not supported"
507 507 ) # pragma: no cover
508 508 return all_true
509 509 if isinstance(node, ast.Constant):
510 510 return node.value
511 if isinstance(node, ast.Index):
512 # deprecated since Python 3.9
513 return eval_node(node.value, context) # pragma: no cover
514 511 if isinstance(node, ast.Tuple):
515 512 return tuple(eval_node(e, context) for e in node.elts)
516 513 if isinstance(node, ast.List):
517 514 return [eval_node(e, context) for e in node.elts]
518 515 if isinstance(node, ast.Set):
519 516 return {eval_node(e, context) for e in node.elts}
520 517 if isinstance(node, ast.Dict):
521 518 return dict(
522 519 zip(
523 520 [eval_node(k, context) for k in node.keys],
524 521 [eval_node(v, context) for v in node.values],
525 522 )
526 523 )
527 524 if isinstance(node, ast.Slice):
528 525 return slice(
529 526 eval_node(node.lower, context),
530 527 eval_node(node.upper, context),
531 528 eval_node(node.step, context),
532 529 )
533 if isinstance(node, ast.ExtSlice):
534 # deprecated since Python 3.9
535 return tuple([eval_node(dim, context) for dim in node.dims]) # pragma: no cover
536 530 if isinstance(node, ast.UnaryOp):
537 531 value = eval_node(node.operand, context)
538 532 dunders = _find_dunder(node.op, UNARY_OP_DUNDERS)
539 533 if dunders:
540 534 if policy.can_operate(dunders, value):
541 535 return getattr(value, dunders[0])()
542 536 else:
543 537 raise GuardRejection(
544 538 f"Operation (`{dunders}`) for",
545 539 type(value),
546 540 f"not allowed in {context.evaluation} mode",
547 541 )
548 542 if isinstance(node, ast.Subscript):
549 543 value = eval_node(node.value, context)
550 544 slice_ = eval_node(node.slice, context)
551 545 if policy.can_get_item(value, slice_):
552 546 return value[slice_]
553 547 raise GuardRejection(
554 548 "Subscript access (`__getitem__`) for",
555 549 type(value), # not joined to avoid calling `repr`
556 550 f" not allowed in {context.evaluation} mode",
557 551 )
558 552 if isinstance(node, ast.Name):
559 553 if policy.allow_locals_access and node.id in context.locals:
560 554 return context.locals[node.id]
561 555 if policy.allow_globals_access and node.id in context.globals:
562 556 return context.globals[node.id]
563 557 if policy.allow_builtins_access and hasattr(builtins, node.id):
564 558 # note: do not use __builtins__, it is implementation detail of cPython
565 559 return getattr(builtins, node.id)
566 560 if not policy.allow_globals_access and not policy.allow_locals_access:
567 561 raise GuardRejection(
568 562 f"Namespace access not allowed in {context.evaluation} mode"
569 563 )
570 564 else:
571 565 raise NameError(f"{node.id} not found in locals, globals, nor builtins")
572 566 if isinstance(node, ast.Attribute):
573 567 value = eval_node(node.value, context)
574 568 if policy.can_get_attr(value, node.attr):
575 569 return getattr(value, node.attr)
576 570 raise GuardRejection(
577 571 "Attribute access (`__getattr__`) for",
578 572 type(value), # not joined to avoid calling `repr`
579 573 f"not allowed in {context.evaluation} mode",
580 574 )
581 575 if isinstance(node, ast.IfExp):
582 576 test = eval_node(node.test, context)
583 577 if test:
584 578 return eval_node(node.body, context)
585 579 else:
586 580 return eval_node(node.orelse, context)
587 581 if isinstance(node, ast.Call):
588 582 func = eval_node(node.func, context)
589 583 if policy.can_call(func) and not node.keywords:
590 584 args = [eval_node(arg, context) for arg in node.args]
591 585 return func(*args)
592 586 raise GuardRejection(
593 587 "Call for",
594 588 func, # not joined to avoid calling `repr`
595 589 f"not allowed in {context.evaluation} mode",
596 590 )
597 591 raise ValueError("Unhandled node", ast.dump(node))
598 592
599 593
600 594 SUPPORTED_EXTERNAL_GETITEM = {
601 595 ("pandas", "core", "indexing", "_iLocIndexer"),
602 596 ("pandas", "core", "indexing", "_LocIndexer"),
603 597 ("pandas", "DataFrame"),
604 598 ("pandas", "Series"),
605 599 ("numpy", "ndarray"),
606 600 ("numpy", "void"),
607 601 }
608 602
609 603
610 604 BUILTIN_GETITEM: Set[InstancesHaveGetItem] = {
611 605 dict,
612 606 str, # type: ignore[arg-type]
613 607 bytes, # type: ignore[arg-type]
614 608 list,
615 609 tuple,
616 610 collections.defaultdict,
617 611 collections.deque,
618 612 collections.OrderedDict,
619 613 collections.ChainMap,
620 614 collections.UserDict,
621 615 collections.UserList,
622 616 collections.UserString, # type: ignore[arg-type]
623 617 _DummyNamedTuple,
624 618 _IdentitySubscript,
625 619 }
626 620
627 621
628 622 def _list_methods(cls, source=None):
629 623 """For use on immutable objects or with methods returning a copy"""
630 624 return [getattr(cls, k) for k in (source if source else dir(cls))]
631 625
632 626
633 627 dict_non_mutating_methods = ("copy", "keys", "values", "items")
634 628 list_non_mutating_methods = ("copy", "index", "count")
635 629 set_non_mutating_methods = set(dir(set)) & set(dir(frozenset))
636 630
637 631
638 632 dict_keys: Type[collections.abc.KeysView] = type({}.keys())
639 633 method_descriptor: Any = type(list.copy)
640 634
641 635 NUMERICS = {int, float, complex}
642 636
643 637 ALLOWED_CALLS = {
644 638 bytes,
645 639 *_list_methods(bytes),
646 640 dict,
647 641 *_list_methods(dict, dict_non_mutating_methods),
648 642 dict_keys.isdisjoint,
649 643 list,
650 644 *_list_methods(list, list_non_mutating_methods),
651 645 set,
652 646 *_list_methods(set, set_non_mutating_methods),
653 647 frozenset,
654 648 *_list_methods(frozenset),
655 649 range,
656 650 str,
657 651 *_list_methods(str),
658 652 tuple,
659 653 *_list_methods(tuple),
660 654 *NUMERICS,
661 655 *[method for numeric_cls in NUMERICS for method in _list_methods(numeric_cls)],
662 656 collections.deque,
663 657 *_list_methods(collections.deque, list_non_mutating_methods),
664 658 collections.defaultdict,
665 659 *_list_methods(collections.defaultdict, dict_non_mutating_methods),
666 660 collections.OrderedDict,
667 661 *_list_methods(collections.OrderedDict, dict_non_mutating_methods),
668 662 collections.UserDict,
669 663 *_list_methods(collections.UserDict, dict_non_mutating_methods),
670 664 collections.UserList,
671 665 *_list_methods(collections.UserList, list_non_mutating_methods),
672 666 collections.UserString,
673 667 *_list_methods(collections.UserString, dir(str)),
674 668 collections.Counter,
675 669 *_list_methods(collections.Counter, dict_non_mutating_methods),
676 670 collections.Counter.elements,
677 671 collections.Counter.most_common,
678 672 }
679 673
680 674 BUILTIN_GETATTR: Set[MayHaveGetattr] = {
681 675 *BUILTIN_GETITEM,
682 676 set,
683 677 frozenset,
684 678 object,
685 679 type, # `type` handles a lot of generic cases, e.g. numbers as in `int.real`.
686 680 *NUMERICS,
687 681 dict_keys,
688 682 method_descriptor,
689 683 }
690 684
691 685
692 686 BUILTIN_OPERATIONS = {*BUILTIN_GETATTR}
693 687
694 688 EVALUATION_POLICIES = {
695 689 "minimal": EvaluationPolicy(
696 690 allow_builtins_access=True,
697 691 allow_locals_access=False,
698 692 allow_globals_access=False,
699 693 allow_item_access=False,
700 694 allow_attr_access=False,
701 695 allowed_calls=set(),
702 696 allow_any_calls=False,
703 697 allow_all_operations=False,
704 698 ),
705 699 "limited": SelectivePolicy(
706 700 allowed_getitem=BUILTIN_GETITEM,
707 701 allowed_getitem_external=SUPPORTED_EXTERNAL_GETITEM,
708 702 allowed_getattr=BUILTIN_GETATTR,
709 703 allowed_getattr_external={
710 704 # pandas Series/Frame implements custom `__getattr__`
711 705 ("pandas", "DataFrame"),
712 706 ("pandas", "Series"),
713 707 },
714 708 allowed_operations=BUILTIN_OPERATIONS,
715 709 allow_builtins_access=True,
716 710 allow_locals_access=True,
717 711 allow_globals_access=True,
718 712 allowed_calls=ALLOWED_CALLS,
719 713 ),
720 714 "unsafe": EvaluationPolicy(
721 715 allow_builtins_access=True,
722 716 allow_locals_access=True,
723 717 allow_globals_access=True,
724 718 allow_attr_access=True,
725 719 allow_item_access=True,
726 720 allow_any_calls=True,
727 721 allow_all_operations=True,
728 722 ),
729 723 }
730 724
731 725
732 726 __all__ = [
733 727 "guarded_eval",
734 728 "eval_node",
735 729 "GuardRejection",
736 730 "EvaluationContext",
737 731 "_unbind_method",
738 732 ]
@@ -1,316 +1,316 b''
1 1 """
2 2 Test for async helpers.
3 3
4 4 Should only trigger on python 3.5+ or will have syntax errors.
5 5 """
6 6 import platform
7 7 from itertools import chain, repeat
8 8 from textwrap import dedent, indent
9 9 from unittest import TestCase
10 10 from IPython.testing.decorators import skip_without
11 11 import sys
12 12 from typing import TYPE_CHECKING
13 13
14 14 if TYPE_CHECKING:
15 15 from IPython import get_ipython
16 16
17 17 ip = get_ipython()
18 18
19 19
20 20 iprc = lambda x: ip.run_cell(dedent(x)).raise_error()
21 21 iprc_nr = lambda x: ip.run_cell(dedent(x))
22 22
23 23 from IPython.core.async_helpers import _should_be_async
24 24
25 25 class AsyncTest(TestCase):
26 26 def test_should_be_async(self):
27 27 self.assertFalse(_should_be_async("False"))
28 28 self.assertTrue(_should_be_async("await bar()"))
29 29 self.assertTrue(_should_be_async("x = await bar()"))
30 30 self.assertFalse(
31 31 _should_be_async(
32 32 dedent(
33 33 """
34 34 async def awaitable():
35 35 pass
36 36 """
37 37 )
38 38 )
39 39 )
40 40
41 41 def _get_top_level_cases(self):
42 42 # These are test cases that should be valid in a function
43 43 # but invalid outside of a function.
44 44 test_cases = []
45 45 test_cases.append(('basic', "{val}"))
46 46
47 47 # Note, in all conditional cases, I use True instead of
48 48 # False so that the peephole optimizer won't optimize away
49 49 # the return, so CPython will see this as a syntax error:
50 50 #
51 51 # while True:
52 52 # break
53 53 # return
54 54 #
55 55 # But not this:
56 56 #
57 57 # while False:
58 58 # return
59 59 #
60 60 # See https://bugs.python.org/issue1875
61 61
62 62 test_cases.append(('if', dedent("""
63 63 if True:
64 64 {val}
65 65 """)))
66 66
67 67 test_cases.append(('while', dedent("""
68 68 while True:
69 69 {val}
70 70 break
71 71 """)))
72 72
73 73 test_cases.append(('try', dedent("""
74 74 try:
75 75 {val}
76 76 except:
77 77 pass
78 78 """)))
79 79
80 80 test_cases.append(('except', dedent("""
81 81 try:
82 82 pass
83 83 except:
84 84 {val}
85 85 """)))
86 86
87 87 test_cases.append(('finally', dedent("""
88 88 try:
89 89 pass
90 90 except:
91 91 pass
92 92 finally:
93 93 {val}
94 94 """)))
95 95
96 96 test_cases.append(('for', dedent("""
97 97 for _ in range(4):
98 98 {val}
99 99 """)))
100 100
101 101
102 102 test_cases.append(('nested', dedent("""
103 103 if True:
104 104 while True:
105 105 {val}
106 106 break
107 107 """)))
108 108
109 109 test_cases.append(('deep-nested', dedent("""
110 110 if True:
111 111 while True:
112 112 break
113 113 for x in range(3):
114 114 if True:
115 115 while True:
116 116 for x in range(3):
117 117 {val}
118 118 """)))
119 119
120 120 return test_cases
121 121
122 122 def _get_ry_syntax_errors(self):
123 123 # This is a mix of tests that should be a syntax error if
124 124 # return or yield whether or not they are in a function
125 125
126 126 test_cases = []
127 127
128 128 test_cases.append(('class', dedent("""
129 129 class V:
130 130 {val}
131 131 """)))
132 132
133 133 test_cases.append(('nested-class', dedent("""
134 134 class V:
135 135 class C:
136 136 {val}
137 137 """)))
138 138
139 139 return test_cases
140 140
141 141
142 142 def test_top_level_return_error(self):
143 143 tl_err_test_cases = self._get_top_level_cases()
144 144 tl_err_test_cases.extend(self._get_ry_syntax_errors())
145 145
146 146 vals = ('return', 'yield', 'yield from (_ for _ in range(3))',
147 147 dedent('''
148 148 def f():
149 149 pass
150 150 return
151 151 '''),
152 152 )
153 153
154 154 for test_name, test_case in tl_err_test_cases:
155 155 # This example should work if 'pass' is used as the value
156 156 with self.subTest((test_name, 'pass')):
157 157 iprc(test_case.format(val='pass'))
158 158
159 159 # It should fail with all the values
160 160 for val in vals:
161 161 with self.subTest((test_name, val)):
162 162 msg = "Syntax error not raised for %s, %s" % (test_name, val)
163 163 with self.assertRaises(SyntaxError, msg=msg):
164 164 iprc(test_case.format(val=val))
165 165
166 166 def test_in_func_no_error(self):
167 167 # Test that the implementation of top-level return/yield
168 168 # detection isn't *too* aggressive, and works inside a function
169 169 func_contexts = []
170 170
171 171 func_contexts.append(('func', False, dedent("""
172 172 def f():""")))
173 173
174 174 func_contexts.append(('method', False, dedent("""
175 175 class MyClass:
176 176 def __init__(self):
177 177 """)))
178 178
179 179 func_contexts.append(('async-func', True, dedent("""
180 180 async def f():""")))
181 181
182 182 func_contexts.append(('async-method', True, dedent("""
183 183 class MyClass:
184 184 async def f(self):""")))
185 185
186 186 func_contexts.append(('closure', False, dedent("""
187 187 def f():
188 188 def g():
189 189 """)))
190 190
191 191 def nest_case(context, case):
192 192 # Detect indentation
193 193 lines = context.strip().splitlines()
194 194 prefix_len = 0
195 195 for c in lines[-1]:
196 196 if c != ' ':
197 197 break
198 198 prefix_len += 1
199 199
200 200 indented_case = indent(case, ' ' * (prefix_len + 4))
201 201 return context + '\n' + indented_case
202 202
203 203 # Gather and run the tests
204 204
205 205 # yield is allowed in async functions, starting in Python 3.6,
206 206 # and yield from is not allowed in any version
207 207 vals = ('return', 'yield', 'yield from (_ for _ in range(3))')
208 208
209 209 success_tests = zip(self._get_top_level_cases(), repeat(False))
210 210 failure_tests = zip(self._get_ry_syntax_errors(), repeat(True))
211 211
212 212 tests = chain(success_tests, failure_tests)
213 213
214 214 for context_name, async_func, context in func_contexts:
215 215 for (test_name, test_case), should_fail in tests:
216 216 nested_case = nest_case(context, test_case)
217 217
218 218 for val in vals:
219 219 test_id = (context_name, test_name, val)
220 220 cell = nested_case.format(val=val)
221 221
222 222 with self.subTest(test_id):
223 223 if should_fail:
224 224 msg = ("SyntaxError not raised for %s" %
225 225 str(test_id))
226 226 with self.assertRaises(SyntaxError, msg=msg):
227 227 iprc(cell)
228 228
229 229 print(cell)
230 230 else:
231 231 iprc(cell)
232 232
233 233 def test_nonlocal(self):
234 234 # fails if outer scope is not a function scope or if var not defined
235 235 with self.assertRaises(SyntaxError):
236 236 iprc("nonlocal x")
237 237 iprc("""
238 238 x = 1
239 239 def f():
240 240 nonlocal x
241 241 x = 10000
242 242 yield x
243 243 """)
244 244 iprc("""
245 245 def f():
246 246 def g():
247 247 nonlocal x
248 248 x = 10000
249 249 yield x
250 250 """)
251 251
252 252 # works if outer scope is a function scope and var exists
253 253 iprc("""
254 254 def f():
255 255 x = 20
256 256 def g():
257 257 nonlocal x
258 258 x = 10000
259 259 yield x
260 260 """)
261 261
262 262
263 263 def test_execute(self):
264 264 iprc("""
265 265 import asyncio
266 266 await asyncio.sleep(0.001)
267 267 """
268 268 )
269 269
270 270 def test_autoawait(self):
271 271 iprc("%autoawait False")
272 272 iprc("%autoawait True")
273 273 iprc("""
274 274 from asyncio import sleep
275 275 await sleep(0.1)
276 276 """
277 277 )
278 278
279 if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy":
280 # new pgen parser in 3.9 does not raise MemoryError on too many nested
281 # parens anymore
282 def test_memory_error(self):
283 with self.assertRaises(MemoryError):
284 iprc("(" * 200 + ")" * 200)
279 def test_memory_error(self):
280 """
281 The pgen parser in 3.8 or before use to raise MemoryError on too many
282 nested parens anymore"""
283
284 iprc("(" * 200 + ")" * 200)
285 285
286 286 @skip_without('curio')
287 287 def test_autoawait_curio(self):
288 288 iprc("%autoawait curio")
289 289
290 290 @skip_without('trio')
291 291 def test_autoawait_trio(self):
292 292 iprc("%autoawait trio")
293 293
294 294 @skip_without('trio')
295 295 def test_autoawait_trio_wrong_sleep(self):
296 296 iprc("%autoawait trio")
297 297 res = iprc_nr("""
298 298 import asyncio
299 299 await asyncio.sleep(0)
300 300 """)
301 301 with self.assertRaises(TypeError):
302 302 res.raise_error()
303 303
304 304 @skip_without('trio')
305 305 def test_autoawait_asyncio_wrong_sleep(self):
306 306 iprc("%autoawait asyncio")
307 307 res = iprc_nr("""
308 308 import trio
309 309 await trio.sleep(0)
310 310 """)
311 311 with self.assertRaises(RuntimeError):
312 312 res.raise_error()
313 313
314 314
315 315 def tearDown(self):
316 316 ip.loop_runner = "asyncio"
@@ -1,1200 +1,1197 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for the key interactiveshell module.
3 3
4 4 Historically the main classes in interactiveshell have been under-tested. This
5 5 module should grow as many single-method tests as possible to trap many of the
6 6 recurring bugs we seem to encounter with high-level interaction.
7 7 """
8 8
9 9 # Copyright (c) IPython Development Team.
10 10 # Distributed under the terms of the Modified BSD License.
11 11
12 12 import asyncio
13 13 import ast
14 14 import os
15 15 import signal
16 16 import shutil
17 17 import sys
18 18 import tempfile
19 19 import unittest
20 20 import pytest
21 21 from unittest import mock
22 22
23 23 from os.path import join
24 24
25 25 from IPython.core.error import InputRejected
26 26 from IPython.core.inputtransformer import InputTransformer
27 27 from IPython.core import interactiveshell
28 28 from IPython.core.oinspect import OInfo
29 29 from IPython.testing.decorators import (
30 30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
31 31 )
32 32 from IPython.testing import tools as tt
33 33 from IPython.utils.process import find_cmd
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Globals
37 37 #-----------------------------------------------------------------------------
38 38 # This is used by every single test, no point repeating it ad nauseam
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Tests
42 42 #-----------------------------------------------------------------------------
43 43
44 44 class DerivedInterrupt(KeyboardInterrupt):
45 45 pass
46 46
47 47 class InteractiveShellTestCase(unittest.TestCase):
48 48 def test_naked_string_cells(self):
49 49 """Test that cells with only naked strings are fully executed"""
50 50 # First, single-line inputs
51 51 ip.run_cell('"a"\n')
52 52 self.assertEqual(ip.user_ns['_'], 'a')
53 53 # And also multi-line cells
54 54 ip.run_cell('"""a\nb"""\n')
55 55 self.assertEqual(ip.user_ns['_'], 'a\nb')
56 56
57 57 def test_run_empty_cell(self):
58 58 """Just make sure we don't get a horrible error with a blank
59 59 cell of input. Yes, I did overlook that."""
60 60 old_xc = ip.execution_count
61 61 res = ip.run_cell('')
62 62 self.assertEqual(ip.execution_count, old_xc)
63 63 self.assertEqual(res.execution_count, None)
64 64
65 65 def test_run_cell_multiline(self):
66 66 """Multi-block, multi-line cells must execute correctly.
67 67 """
68 68 src = '\n'.join(["x=1",
69 69 "y=2",
70 70 "if 1:",
71 71 " x += 1",
72 72 " y += 1",])
73 73 res = ip.run_cell(src)
74 74 self.assertEqual(ip.user_ns['x'], 2)
75 75 self.assertEqual(ip.user_ns['y'], 3)
76 76 self.assertEqual(res.success, True)
77 77 self.assertEqual(res.result, None)
78 78
79 79 def test_multiline_string_cells(self):
80 80 "Code sprinkled with multiline strings should execute (GH-306)"
81 81 ip.run_cell('tmp=0')
82 82 self.assertEqual(ip.user_ns['tmp'], 0)
83 83 res = ip.run_cell('tmp=1;"""a\nb"""\n')
84 84 self.assertEqual(ip.user_ns['tmp'], 1)
85 85 self.assertEqual(res.success, True)
86 86 self.assertEqual(res.result, "a\nb")
87 87
88 88 def test_dont_cache_with_semicolon(self):
89 89 "Ending a line with semicolon should not cache the returned object (GH-307)"
90 90 oldlen = len(ip.user_ns['Out'])
91 91 for cell in ['1;', '1;1;']:
92 92 res = ip.run_cell(cell, store_history=True)
93 93 newlen = len(ip.user_ns['Out'])
94 94 self.assertEqual(oldlen, newlen)
95 95 self.assertIsNone(res.result)
96 96 i = 0
97 97 #also test the default caching behavior
98 98 for cell in ['1', '1;1']:
99 99 ip.run_cell(cell, store_history=True)
100 100 newlen = len(ip.user_ns['Out'])
101 101 i += 1
102 102 self.assertEqual(oldlen+i, newlen)
103 103
104 104 def test_syntax_error(self):
105 105 res = ip.run_cell("raise = 3")
106 106 self.assertIsInstance(res.error_before_exec, SyntaxError)
107 107
108 108 def test_open_standard_input_stream(self):
109 109 res = ip.run_cell("open(0)")
110 110 self.assertIsInstance(res.error_in_exec, ValueError)
111 111
112 112 def test_open_standard_output_stream(self):
113 113 res = ip.run_cell("open(1)")
114 114 self.assertIsInstance(res.error_in_exec, ValueError)
115 115
116 116 def test_open_standard_error_stream(self):
117 117 res = ip.run_cell("open(2)")
118 118 self.assertIsInstance(res.error_in_exec, ValueError)
119 119
120 120 def test_In_variable(self):
121 121 "Verify that In variable grows with user input (GH-284)"
122 122 oldlen = len(ip.user_ns['In'])
123 123 ip.run_cell('1;', store_history=True)
124 124 newlen = len(ip.user_ns['In'])
125 125 self.assertEqual(oldlen+1, newlen)
126 126 self.assertEqual(ip.user_ns['In'][-1],'1;')
127 127
128 128 def test_magic_names_in_string(self):
129 129 ip.run_cell('a = """\n%exit\n"""')
130 130 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
131 131
132 132 def test_trailing_newline(self):
133 133 """test that running !(command) does not raise a SyntaxError"""
134 134 ip.run_cell('!(true)\n', False)
135 135 ip.run_cell('!(true)\n\n\n', False)
136 136
137 137 def test_gh_597(self):
138 138 """Pretty-printing lists of objects with non-ascii reprs may cause
139 139 problems."""
140 140 class Spam(object):
141 141 def __repr__(self):
142 142 return "\xe9"*50
143 143 import IPython.core.formatters
144 144 f = IPython.core.formatters.PlainTextFormatter()
145 145 f([Spam(),Spam()])
146 146
147 147
148 148 def test_future_flags(self):
149 149 """Check that future flags are used for parsing code (gh-777)"""
150 150 ip.run_cell('from __future__ import barry_as_FLUFL')
151 151 try:
152 152 ip.run_cell('prfunc_return_val = 1 <> 2')
153 153 assert 'prfunc_return_val' in ip.user_ns
154 154 finally:
155 155 # Reset compiler flags so we don't mess up other tests.
156 156 ip.compile.reset_compiler_flags()
157 157
158 158 def test_can_pickle(self):
159 159 "Can we pickle objects defined interactively (GH-29)"
160 160 ip = get_ipython()
161 161 ip.reset()
162 162 ip.run_cell(("class Mylist(list):\n"
163 163 " def __init__(self,x=[]):\n"
164 164 " list.__init__(self,x)"))
165 165 ip.run_cell("w=Mylist([1,2,3])")
166 166
167 167 from pickle import dumps
168 168
169 169 # We need to swap in our main module - this is only necessary
170 170 # inside the test framework, because IPython puts the interactive module
171 171 # in place (but the test framework undoes this).
172 172 _main = sys.modules['__main__']
173 173 sys.modules['__main__'] = ip.user_module
174 174 try:
175 175 res = dumps(ip.user_ns["w"])
176 176 finally:
177 177 sys.modules['__main__'] = _main
178 178 self.assertTrue(isinstance(res, bytes))
179 179
180 180 def test_global_ns(self):
181 181 "Code in functions must be able to access variables outside them."
182 182 ip = get_ipython()
183 183 ip.run_cell("a = 10")
184 184 ip.run_cell(("def f(x):\n"
185 185 " return x + a"))
186 186 ip.run_cell("b = f(12)")
187 187 self.assertEqual(ip.user_ns["b"], 22)
188 188
189 189 def test_bad_custom_tb(self):
190 190 """Check that InteractiveShell is protected from bad custom exception handlers"""
191 191 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
192 192 self.assertEqual(ip.custom_exceptions, (IOError,))
193 193 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
194 194 ip.run_cell(u'raise IOError("foo")')
195 195 self.assertEqual(ip.custom_exceptions, ())
196 196
197 197 def test_bad_custom_tb_return(self):
198 198 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
199 199 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
200 200 self.assertEqual(ip.custom_exceptions, (NameError,))
201 201 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
202 202 ip.run_cell(u'a=abracadabra')
203 203 self.assertEqual(ip.custom_exceptions, ())
204 204
205 205 def test_drop_by_id(self):
206 206 myvars = {"a":object(), "b":object(), "c": object()}
207 207 ip.push(myvars, interactive=False)
208 208 for name in myvars:
209 209 assert name in ip.user_ns, name
210 210 assert name in ip.user_ns_hidden, name
211 211 ip.user_ns['b'] = 12
212 212 ip.drop_by_id(myvars)
213 213 for name in ["a", "c"]:
214 214 assert name not in ip.user_ns, name
215 215 assert name not in ip.user_ns_hidden, name
216 216 assert ip.user_ns['b'] == 12
217 217 ip.reset()
218 218
219 219 def test_var_expand(self):
220 220 ip.user_ns['f'] = u'Ca\xf1o'
221 221 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
222 222 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
223 223 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
224 224 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
225 225
226 226 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
227 227
228 228 ip.user_ns['f'] = b'Ca\xc3\xb1o'
229 229 # This should not raise any exception:
230 230 ip.var_expand(u'echo $f')
231 231
232 232 def test_var_expand_local(self):
233 233 """Test local variable expansion in !system and %magic calls"""
234 234 # !system
235 235 ip.run_cell(
236 236 "def test():\n"
237 237 ' lvar = "ttt"\n'
238 238 " ret = !echo {lvar}\n"
239 239 " return ret[0]\n"
240 240 )
241 241 res = ip.user_ns["test"]()
242 242 self.assertIn("ttt", res)
243 243
244 244 # %magic
245 245 ip.run_cell(
246 246 "def makemacro():\n"
247 247 ' macroname = "macro_var_expand_locals"\n'
248 248 " %macro {macroname} codestr\n"
249 249 )
250 250 ip.user_ns["codestr"] = "str(12)"
251 251 ip.run_cell("makemacro()")
252 252 self.assertIn("macro_var_expand_locals", ip.user_ns)
253 253
254 254 def test_var_expand_self(self):
255 255 """Test variable expansion with the name 'self', which was failing.
256 256
257 257 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
258 258 """
259 259 ip.run_cell(
260 260 "class cTest:\n"
261 261 ' classvar="see me"\n'
262 262 " def test(self):\n"
263 263 " res = !echo Variable: {self.classvar}\n"
264 264 " return res[0]\n"
265 265 )
266 266 self.assertIn("see me", ip.user_ns["cTest"]().test())
267 267
268 268 def test_bad_var_expand(self):
269 269 """var_expand on invalid formats shouldn't raise"""
270 270 # SyntaxError
271 271 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
272 272 # NameError
273 273 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
274 274 # ZeroDivisionError
275 275 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
276 276
277 277 def test_silent_postexec(self):
278 278 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
279 279 pre_explicit = mock.Mock()
280 280 pre_always = mock.Mock()
281 281 post_explicit = mock.Mock()
282 282 post_always = mock.Mock()
283 283 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
284 284
285 285 ip.events.register('pre_run_cell', pre_explicit)
286 286 ip.events.register('pre_execute', pre_always)
287 287 ip.events.register('post_run_cell', post_explicit)
288 288 ip.events.register('post_execute', post_always)
289 289
290 290 try:
291 291 ip.run_cell("1", silent=True)
292 292 assert pre_always.called
293 293 assert not pre_explicit.called
294 294 assert post_always.called
295 295 assert not post_explicit.called
296 296 # double-check that non-silent exec did what we expected
297 297 # silent to avoid
298 298 ip.run_cell("1")
299 299 assert pre_explicit.called
300 300 assert post_explicit.called
301 301 info, = pre_explicit.call_args[0]
302 302 result, = post_explicit.call_args[0]
303 303 self.assertEqual(info, result.info)
304 304 # check that post hooks are always called
305 305 [m.reset_mock() for m in all_mocks]
306 306 ip.run_cell("syntax error")
307 307 assert pre_always.called
308 308 assert pre_explicit.called
309 309 assert post_always.called
310 310 assert post_explicit.called
311 311 info, = pre_explicit.call_args[0]
312 312 result, = post_explicit.call_args[0]
313 313 self.assertEqual(info, result.info)
314 314 finally:
315 315 # remove post-exec
316 316 ip.events.unregister('pre_run_cell', pre_explicit)
317 317 ip.events.unregister('pre_execute', pre_always)
318 318 ip.events.unregister('post_run_cell', post_explicit)
319 319 ip.events.unregister('post_execute', post_always)
320 320
321 321 def test_silent_noadvance(self):
322 322 """run_cell(silent=True) doesn't advance execution_count"""
323 323 ec = ip.execution_count
324 324 # silent should force store_history=False
325 325 ip.run_cell("1", store_history=True, silent=True)
326 326
327 327 self.assertEqual(ec, ip.execution_count)
328 328 # double-check that non-silent exec did what we expected
329 329 # silent to avoid
330 330 ip.run_cell("1", store_history=True)
331 331 self.assertEqual(ec+1, ip.execution_count)
332 332
333 333 def test_silent_nodisplayhook(self):
334 334 """run_cell(silent=True) doesn't trigger displayhook"""
335 335 d = dict(called=False)
336 336
337 337 trap = ip.display_trap
338 338 save_hook = trap.hook
339 339
340 340 def failing_hook(*args, **kwargs):
341 341 d['called'] = True
342 342
343 343 try:
344 344 trap.hook = failing_hook
345 345 res = ip.run_cell("1", silent=True)
346 346 self.assertFalse(d['called'])
347 347 self.assertIsNone(res.result)
348 348 # double-check that non-silent exec did what we expected
349 349 # silent to avoid
350 350 ip.run_cell("1")
351 351 self.assertTrue(d['called'])
352 352 finally:
353 353 trap.hook = save_hook
354 354
355 355 def test_ofind_line_magic(self):
356 356 from IPython.core.magic import register_line_magic
357 357
358 358 @register_line_magic
359 359 def lmagic(line):
360 360 "A line magic"
361 361
362 362 # Get info on line magic
363 363 lfind = ip._ofind("lmagic")
364 364 info = OInfo(
365 365 found=True,
366 366 isalias=False,
367 367 ismagic=True,
368 368 namespace="IPython internal",
369 369 obj=lmagic,
370 370 parent=None,
371 371 )
372 372 self.assertEqual(lfind, info)
373 373
374 374 def test_ofind_cell_magic(self):
375 375 from IPython.core.magic import register_cell_magic
376 376
377 377 @register_cell_magic
378 378 def cmagic(line, cell):
379 379 "A cell magic"
380 380
381 381 # Get info on cell magic
382 382 find = ip._ofind("cmagic")
383 383 info = OInfo(
384 384 found=True,
385 385 isalias=False,
386 386 ismagic=True,
387 387 namespace="IPython internal",
388 388 obj=cmagic,
389 389 parent=None,
390 390 )
391 391 self.assertEqual(find, info)
392 392
393 393 def test_ofind_property_with_error(self):
394 394 class A(object):
395 395 @property
396 396 def foo(self):
397 397 raise NotImplementedError() # pragma: no cover
398 398
399 399 a = A()
400 400
401 401 found = ip._ofind("a.foo", [("locals", locals())])
402 402 info = OInfo(
403 403 found=True,
404 404 isalias=False,
405 405 ismagic=False,
406 406 namespace="locals",
407 407 obj=A.foo,
408 408 parent=a,
409 409 )
410 410 self.assertEqual(found, info)
411 411
412 412 def test_ofind_multiple_attribute_lookups(self):
413 413 class A(object):
414 414 @property
415 415 def foo(self):
416 416 raise NotImplementedError() # pragma: no cover
417 417
418 418 a = A()
419 419 a.a = A()
420 420 a.a.a = A()
421 421
422 422 found = ip._ofind("a.a.a.foo", [("locals", locals())])
423 423 info = OInfo(
424 424 found=True,
425 425 isalias=False,
426 426 ismagic=False,
427 427 namespace="locals",
428 428 obj=A.foo,
429 429 parent=a.a.a,
430 430 )
431 431 self.assertEqual(found, info)
432 432
433 433 def test_ofind_slotted_attributes(self):
434 434 class A(object):
435 435 __slots__ = ['foo']
436 436 def __init__(self):
437 437 self.foo = 'bar'
438 438
439 439 a = A()
440 440 found = ip._ofind("a.foo", [("locals", locals())])
441 441 info = OInfo(
442 442 found=True,
443 443 isalias=False,
444 444 ismagic=False,
445 445 namespace="locals",
446 446 obj=a.foo,
447 447 parent=a,
448 448 )
449 449 self.assertEqual(found, info)
450 450
451 451 found = ip._ofind("a.bar", [("locals", locals())])
452 452 expected = OInfo(
453 453 found=False,
454 454 isalias=False,
455 455 ismagic=False,
456 456 namespace=None,
457 457 obj=None,
458 458 parent=a,
459 459 )
460 460 assert found == expected
461 461
462 462 def test_ofind_prefers_property_to_instance_level_attribute(self):
463 463 class A(object):
464 464 @property
465 465 def foo(self):
466 466 return 'bar'
467 467 a = A()
468 468 a.__dict__["foo"] = "baz"
469 469 self.assertEqual(a.foo, "bar")
470 470 found = ip._ofind("a.foo", [("locals", locals())])
471 471 self.assertIs(found.obj, A.foo)
472 472
473 473 def test_custom_syntaxerror_exception(self):
474 474 called = []
475 475 def my_handler(shell, etype, value, tb, tb_offset=None):
476 476 called.append(etype)
477 477 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
478 478
479 479 ip.set_custom_exc((SyntaxError,), my_handler)
480 480 try:
481 481 ip.run_cell("1f")
482 482 # Check that this was called, and only once.
483 483 self.assertEqual(called, [SyntaxError])
484 484 finally:
485 485 # Reset the custom exception hook
486 486 ip.set_custom_exc((), None)
487 487
488 488 def test_custom_exception(self):
489 489 called = []
490 490 def my_handler(shell, etype, value, tb, tb_offset=None):
491 491 called.append(etype)
492 492 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
493 493
494 494 ip.set_custom_exc((ValueError,), my_handler)
495 495 try:
496 496 res = ip.run_cell("raise ValueError('test')")
497 497 # Check that this was called, and only once.
498 498 self.assertEqual(called, [ValueError])
499 499 # Check that the error is on the result object
500 500 self.assertIsInstance(res.error_in_exec, ValueError)
501 501 finally:
502 502 # Reset the custom exception hook
503 503 ip.set_custom_exc((), None)
504 504
505 505 @mock.patch("builtins.print")
506 506 def test_showtraceback_with_surrogates(self, mocked_print):
507 507 values = []
508 508
509 509 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
510 510 values.append(value)
511 511 if value == chr(0xD8FF):
512 512 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
513 513
514 514 # mock builtins.print
515 515 mocked_print.side_effect = mock_print_func
516 516
517 517 # ip._showtraceback() is replaced in globalipapp.py.
518 518 # Call original method to test.
519 519 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
520 520
521 521 self.assertEqual(mocked_print.call_count, 2)
522 522 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
523 523
524 524 def test_mktempfile(self):
525 525 filename = ip.mktempfile()
526 526 # Check that we can open the file again on Windows
527 527 with open(filename, "w", encoding="utf-8") as f:
528 528 f.write("abc")
529 529
530 530 filename = ip.mktempfile(data="blah")
531 531 with open(filename, "r", encoding="utf-8") as f:
532 532 self.assertEqual(f.read(), "blah")
533 533
534 534 def test_new_main_mod(self):
535 535 # Smoketest to check that this accepts a unicode module name
536 536 name = u'jiefmw'
537 537 mod = ip.new_main_mod(u'%s.py' % name, name)
538 538 self.assertEqual(mod.__name__, name)
539 539
540 540 def test_get_exception_only(self):
541 541 try:
542 542 raise KeyboardInterrupt
543 543 except KeyboardInterrupt:
544 544 msg = ip.get_exception_only()
545 545 self.assertEqual(msg, 'KeyboardInterrupt\n')
546 546
547 547 try:
548 548 raise DerivedInterrupt("foo")
549 549 except KeyboardInterrupt:
550 550 msg = ip.get_exception_only()
551 551 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
552 552
553 553 def test_inspect_text(self):
554 554 ip.run_cell('a = 5')
555 555 text = ip.object_inspect_text('a')
556 556 self.assertIsInstance(text, str)
557 557
558 558 def test_last_execution_result(self):
559 559 """ Check that last execution result gets set correctly (GH-10702) """
560 560 result = ip.run_cell('a = 5; a')
561 561 self.assertTrue(ip.last_execution_succeeded)
562 562 self.assertEqual(ip.last_execution_result.result, 5)
563 563
564 564 result = ip.run_cell('a = x_invalid_id_x')
565 565 self.assertFalse(ip.last_execution_succeeded)
566 566 self.assertFalse(ip.last_execution_result.success)
567 567 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
568 568
569 569 def test_reset_aliasing(self):
570 570 """ Check that standard posix aliases work after %reset. """
571 571 if os.name != 'posix':
572 572 return
573 573
574 574 ip.reset()
575 575 for cmd in ('clear', 'more', 'less', 'man'):
576 576 res = ip.run_cell('%' + cmd)
577 577 self.assertEqual(res.success, True)
578 578
579 579
580 580 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
581 581
582 582 @onlyif_unicode_paths
583 583 def setUp(self):
584 584 self.BASETESTDIR = tempfile.mkdtemp()
585 585 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
586 586 os.mkdir(self.TESTDIR)
587 587 with open(
588 588 join(self.TESTDIR, "Γ₯Àâtestscript.py"), "w", encoding="utf-8"
589 589 ) as sfile:
590 590 sfile.write("pass\n")
591 591 self.oldpath = os.getcwd()
592 592 os.chdir(self.TESTDIR)
593 593 self.fname = u"Γ₯Àâtestscript.py"
594 594
595 595 def tearDown(self):
596 596 os.chdir(self.oldpath)
597 597 shutil.rmtree(self.BASETESTDIR)
598 598
599 599 @onlyif_unicode_paths
600 600 def test_1(self):
601 601 """Test safe_execfile with non-ascii path
602 602 """
603 603 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
604 604
605 605 class ExitCodeChecks(tt.TempFileMixin):
606 606
607 607 def setUp(self):
608 608 self.system = ip.system_raw
609 609
610 610 def test_exit_code_ok(self):
611 611 self.system('exit 0')
612 612 self.assertEqual(ip.user_ns['_exit_code'], 0)
613 613
614 614 def test_exit_code_error(self):
615 615 self.system('exit 1')
616 616 self.assertEqual(ip.user_ns['_exit_code'], 1)
617 617
618 618 @skipif(not hasattr(signal, 'SIGALRM'))
619 619 def test_exit_code_signal(self):
620 620 self.mktmp("import signal, time\n"
621 621 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
622 622 "time.sleep(1)\n")
623 623 self.system("%s %s" % (sys.executable, self.fname))
624 624 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
625 625
626 626 @onlyif_cmds_exist("csh")
627 627 def test_exit_code_signal_csh(self): # pragma: no cover
628 628 SHELL = os.environ.get("SHELL", None)
629 629 os.environ["SHELL"] = find_cmd("csh")
630 630 try:
631 631 self.test_exit_code_signal()
632 632 finally:
633 633 if SHELL is not None:
634 634 os.environ['SHELL'] = SHELL
635 635 else:
636 636 del os.environ['SHELL']
637 637
638 638
639 639 class TestSystemRaw(ExitCodeChecks):
640 640
641 641 def setUp(self):
642 642 super().setUp()
643 643 self.system = ip.system_raw
644 644
645 645 @onlyif_unicode_paths
646 646 def test_1(self):
647 647 """Test system_raw with non-ascii cmd
648 648 """
649 649 cmd = u'''python -c "'Γ₯Àâ'" '''
650 650 ip.system_raw(cmd)
651 651
652 652 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
653 653 @mock.patch('os.system', side_effect=KeyboardInterrupt)
654 654 def test_control_c(self, *mocks):
655 655 try:
656 656 self.system("sleep 1 # wont happen")
657 657 except KeyboardInterrupt: # pragma: no cove
658 658 self.fail(
659 659 "system call should intercept "
660 660 "keyboard interrupt from subprocess.call"
661 661 )
662 662 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
663 663
664 664
665 665 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
666 666 def test_magic_warnings(magic_cmd):
667 667 if sys.platform == "win32":
668 668 to_mock = "os.system"
669 669 expected_arg, expected_kwargs = magic_cmd, dict()
670 670 else:
671 671 to_mock = "subprocess.call"
672 672 expected_arg, expected_kwargs = magic_cmd, dict(
673 673 shell=True, executable=os.environ.get("SHELL", None)
674 674 )
675 675
676 676 with mock.patch(to_mock, return_value=0) as mock_sub:
677 677 with pytest.warns(Warning, match=r"You executed the system command"):
678 678 ip.system_raw(magic_cmd)
679 679 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
680 680
681 681
682 682 # TODO: Exit codes are currently ignored on Windows.
683 683 class TestSystemPipedExitCode(ExitCodeChecks):
684 684
685 685 def setUp(self):
686 686 super().setUp()
687 687 self.system = ip.system_piped
688 688
689 689 @skip_win32
690 690 def test_exit_code_ok(self):
691 691 ExitCodeChecks.test_exit_code_ok(self)
692 692
693 693 @skip_win32
694 694 def test_exit_code_error(self):
695 695 ExitCodeChecks.test_exit_code_error(self)
696 696
697 697 @skip_win32
698 698 def test_exit_code_signal(self):
699 699 ExitCodeChecks.test_exit_code_signal(self)
700 700
701 701 class TestModules(tt.TempFileMixin):
702 702 def test_extraneous_loads(self):
703 703 """Test we're not loading modules on startup that we shouldn't.
704 704 """
705 705 self.mktmp("import sys\n"
706 706 "print('numpy' in sys.modules)\n"
707 707 "print('ipyparallel' in sys.modules)\n"
708 708 "print('ipykernel' in sys.modules)\n"
709 709 )
710 710 out = "False\nFalse\nFalse\n"
711 711 tt.ipexec_validate(self.fname, out)
712 712
713 713 class Negator(ast.NodeTransformer):
714 714 """Negates all number literals in an AST."""
715 715
716 # for python 3.7 and earlier
717 716 def visit_Num(self, node):
718 717 node.n = -node.n
719 718 return node
720 719
721 # for python 3.8+
722 720 def visit_Constant(self, node):
723 721 if isinstance(node.value, int):
724 722 return self.visit_Num(node)
725 723 return node
726 724
727 725 class TestAstTransform(unittest.TestCase):
728 726 def setUp(self):
729 727 self.negator = Negator()
730 728 ip.ast_transformers.append(self.negator)
731 729
732 730 def tearDown(self):
733 731 ip.ast_transformers.remove(self.negator)
734 732
735 733 def test_non_int_const(self):
736 734 with tt.AssertPrints("hello"):
737 735 ip.run_cell('print("hello")')
738 736
739 737 def test_run_cell(self):
740 738 with tt.AssertPrints("-34"):
741 739 ip.run_cell("print(12 + 22)")
742 740
743 741 # A named reference to a number shouldn't be transformed.
744 742 ip.user_ns["n"] = 55
745 743 with tt.AssertNotPrints("-55"):
746 744 ip.run_cell("print(n)")
747 745
748 746 def test_timeit(self):
749 747 called = set()
750 748 def f(x):
751 749 called.add(x)
752 750 ip.push({'f':f})
753 751
754 752 with tt.AssertPrints("std. dev. of"):
755 753 ip.run_line_magic("timeit", "-n1 f(1)")
756 754 self.assertEqual(called, {-1})
757 755 called.clear()
758 756
759 757 with tt.AssertPrints("std. dev. of"):
760 758 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
761 759 self.assertEqual(called, {-2, -3})
762 760
763 761 def test_time(self):
764 762 called = []
765 763 def f(x):
766 764 called.append(x)
767 765 ip.push({'f':f})
768 766
769 767 # Test with an expression
770 768 with tt.AssertPrints("Wall time: "):
771 769 ip.run_line_magic("time", "f(5+9)")
772 770 self.assertEqual(called, [-14])
773 771 called[:] = []
774 772
775 773 # Test with a statement (different code path)
776 774 with tt.AssertPrints("Wall time: "):
777 775 ip.run_line_magic("time", "a = f(-3 + -2)")
778 776 self.assertEqual(called, [5])
779 777
780 778 def test_macro(self):
781 779 ip.push({'a':10})
782 780 # The AST transformation makes this do a+=-1
783 781 ip.define_macro("amacro", "a+=1\nprint(a)")
784 782
785 783 with tt.AssertPrints("9"):
786 784 ip.run_cell("amacro")
787 785 with tt.AssertPrints("8"):
788 786 ip.run_cell("amacro")
789 787
790 788 class TestMiscTransform(unittest.TestCase):
791 789
792 790
793 791 def test_transform_only_once(self):
794 792 cleanup = 0
795 793 line_t = 0
796 794 def count_cleanup(lines):
797 795 nonlocal cleanup
798 796 cleanup += 1
799 797 return lines
800 798
801 799 def count_line_t(lines):
802 800 nonlocal line_t
803 801 line_t += 1
804 802 return lines
805 803
806 804 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
807 805 ip.input_transformer_manager.line_transforms.append(count_line_t)
808 806
809 807 ip.run_cell('1')
810 808
811 809 assert cleanup == 1
812 810 assert line_t == 1
813 811
814 812 class IntegerWrapper(ast.NodeTransformer):
815 813 """Wraps all integers in a call to Integer()"""
816 814
817 815 # for Python 3.7 and earlier
818 816
819 817 # for Python 3.7 and earlier
820 818 def visit_Num(self, node):
821 819 if isinstance(node.n, int):
822 820 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
823 821 args=[node], keywords=[])
824 822 return node
825 823
826 824 # For Python 3.8+
827 825 def visit_Constant(self, node):
828 826 if isinstance(node.value, int):
829 827 return self.visit_Num(node)
830 828 return node
831 829
832 830
833 831 class TestAstTransform2(unittest.TestCase):
834 832 def setUp(self):
835 833 self.intwrapper = IntegerWrapper()
836 834 ip.ast_transformers.append(self.intwrapper)
837 835
838 836 self.calls = []
839 837 def Integer(*args):
840 838 self.calls.append(args)
841 839 return args
842 840 ip.push({"Integer": Integer})
843 841
844 842 def tearDown(self):
845 843 ip.ast_transformers.remove(self.intwrapper)
846 844 del ip.user_ns['Integer']
847 845
848 846 def test_run_cell(self):
849 847 ip.run_cell("n = 2")
850 848 self.assertEqual(self.calls, [(2,)])
851 849
852 850 # This shouldn't throw an error
853 851 ip.run_cell("o = 2.0")
854 852 self.assertEqual(ip.user_ns['o'], 2.0)
855 853
856 854 def test_run_cell_non_int(self):
857 855 ip.run_cell("n = 'a'")
858 856 assert self.calls == []
859 857
860 858 def test_timeit(self):
861 859 called = set()
862 860 def f(x):
863 861 called.add(x)
864 862 ip.push({'f':f})
865 863
866 864 with tt.AssertPrints("std. dev. of"):
867 865 ip.run_line_magic("timeit", "-n1 f(1)")
868 866 self.assertEqual(called, {(1,)})
869 867 called.clear()
870 868
871 869 with tt.AssertPrints("std. dev. of"):
872 870 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
873 871 self.assertEqual(called, {(2,), (3,)})
874 872
875 873 class ErrorTransformer(ast.NodeTransformer):
876 874 """Throws an error when it sees a number."""
877 875
878 876 def visit_Constant(self, node):
879 877 if isinstance(node.value, int):
880 878 raise ValueError("test")
881 879 return node
882 880
883 881
884 882 class TestAstTransformError(unittest.TestCase):
885 883 def test_unregistering(self):
886 884 err_transformer = ErrorTransformer()
887 885 ip.ast_transformers.append(err_transformer)
888 886
889 887 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
890 888 ip.run_cell("1 + 2")
891 889
892 890 # This should have been removed.
893 891 self.assertNotIn(err_transformer, ip.ast_transformers)
894 892
895 893
896 894 class StringRejector(ast.NodeTransformer):
897 895 """Throws an InputRejected when it sees a string literal.
898 896
899 897 Used to verify that NodeTransformers can signal that a piece of code should
900 898 not be executed by throwing an InputRejected.
901 899 """
902 900
903 # 3.8 only
904 901 def visit_Constant(self, node):
905 902 if isinstance(node.value, str):
906 903 raise InputRejected("test")
907 904 return node
908 905
909 906
910 907 class TestAstTransformInputRejection(unittest.TestCase):
911 908
912 909 def setUp(self):
913 910 self.transformer = StringRejector()
914 911 ip.ast_transformers.append(self.transformer)
915 912
916 913 def tearDown(self):
917 914 ip.ast_transformers.remove(self.transformer)
918 915
919 916 def test_input_rejection(self):
920 917 """Check that NodeTransformers can reject input."""
921 918
922 919 expect_exception_tb = tt.AssertPrints("InputRejected: test")
923 920 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
924 921
925 922 # Run the same check twice to verify that the transformer is not
926 923 # disabled after raising.
927 924 with expect_exception_tb, expect_no_cell_output:
928 925 ip.run_cell("'unsafe'")
929 926
930 927 with expect_exception_tb, expect_no_cell_output:
931 928 res = ip.run_cell("'unsafe'")
932 929
933 930 self.assertIsInstance(res.error_before_exec, InputRejected)
934 931
935 932 def test__IPYTHON__():
936 933 # This shouldn't raise a NameError, that's all
937 934 __IPYTHON__
938 935
939 936
940 937 class DummyRepr(object):
941 938 def __repr__(self):
942 939 return "DummyRepr"
943 940
944 941 def _repr_html_(self):
945 942 return "<b>dummy</b>"
946 943
947 944 def _repr_javascript_(self):
948 945 return "console.log('hi');", {'key': 'value'}
949 946
950 947
951 948 def test_user_variables():
952 949 # enable all formatters
953 950 ip.display_formatter.active_types = ip.display_formatter.format_types
954 951
955 952 ip.user_ns['dummy'] = d = DummyRepr()
956 953 keys = {'dummy', 'doesnotexist'}
957 954 r = ip.user_expressions({ key:key for key in keys})
958 955
959 956 assert keys == set(r.keys())
960 957 dummy = r["dummy"]
961 958 assert {"status", "data", "metadata"} == set(dummy.keys())
962 959 assert dummy["status"] == "ok"
963 960 data = dummy["data"]
964 961 metadata = dummy["metadata"]
965 962 assert data.get("text/html") == d._repr_html_()
966 963 js, jsmd = d._repr_javascript_()
967 964 assert data.get("application/javascript") == js
968 965 assert metadata.get("application/javascript") == jsmd
969 966
970 967 dne = r["doesnotexist"]
971 968 assert dne["status"] == "error"
972 969 assert dne["ename"] == "NameError"
973 970
974 971 # back to text only
975 972 ip.display_formatter.active_types = ['text/plain']
976 973
977 974 def test_user_expression():
978 975 # enable all formatters
979 976 ip.display_formatter.active_types = ip.display_formatter.format_types
980 977 query = {
981 978 'a' : '1 + 2',
982 979 'b' : '1/0',
983 980 }
984 981 r = ip.user_expressions(query)
985 982 import pprint
986 983 pprint.pprint(r)
987 984 assert set(r.keys()) == set(query.keys())
988 985 a = r["a"]
989 986 assert {"status", "data", "metadata"} == set(a.keys())
990 987 assert a["status"] == "ok"
991 988 data = a["data"]
992 989 metadata = a["metadata"]
993 990 assert data.get("text/plain") == "3"
994 991
995 992 b = r["b"]
996 993 assert b["status"] == "error"
997 994 assert b["ename"] == "ZeroDivisionError"
998 995
999 996 # back to text only
1000 997 ip.display_formatter.active_types = ['text/plain']
1001 998
1002 999
1003 1000 class TestSyntaxErrorTransformer(unittest.TestCase):
1004 1001 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
1005 1002
1006 1003 @staticmethod
1007 1004 def transformer(lines):
1008 1005 for line in lines:
1009 1006 pos = line.find('syntaxerror')
1010 1007 if pos >= 0:
1011 1008 e = SyntaxError('input contains "syntaxerror"')
1012 1009 e.text = line
1013 1010 e.offset = pos + 1
1014 1011 raise e
1015 1012 return lines
1016 1013
1017 1014 def setUp(self):
1018 1015 ip.input_transformers_post.append(self.transformer)
1019 1016
1020 1017 def tearDown(self):
1021 1018 ip.input_transformers_post.remove(self.transformer)
1022 1019
1023 1020 def test_syntaxerror_input_transformer(self):
1024 1021 with tt.AssertPrints('1234'):
1025 1022 ip.run_cell('1234')
1026 1023 with tt.AssertPrints('SyntaxError: invalid syntax'):
1027 1024 ip.run_cell('1 2 3') # plain python syntax error
1028 1025 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1029 1026 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1030 1027 with tt.AssertPrints('3456'):
1031 1028 ip.run_cell('3456')
1032 1029
1033 1030
1034 1031 class TestWarningSuppression(unittest.TestCase):
1035 1032 def test_warning_suppression(self):
1036 1033 ip.run_cell("import warnings")
1037 1034 try:
1038 1035 with self.assertWarnsRegex(UserWarning, "asdf"):
1039 1036 ip.run_cell("warnings.warn('asdf')")
1040 1037 # Here's the real test -- if we run that again, we should get the
1041 1038 # warning again. Traditionally, each warning was only issued once per
1042 1039 # IPython session (approximately), even if the user typed in new and
1043 1040 # different code that should have also triggered the warning, leading
1044 1041 # to much confusion.
1045 1042 with self.assertWarnsRegex(UserWarning, "asdf"):
1046 1043 ip.run_cell("warnings.warn('asdf')")
1047 1044 finally:
1048 1045 ip.run_cell("del warnings")
1049 1046
1050 1047
1051 1048 def test_deprecation_warning(self):
1052 1049 ip.run_cell("""
1053 1050 import warnings
1054 1051 def wrn():
1055 1052 warnings.warn(
1056 1053 "I AM A WARNING",
1057 1054 DeprecationWarning
1058 1055 )
1059 1056 """)
1060 1057 try:
1061 1058 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1062 1059 ip.run_cell("wrn()")
1063 1060 finally:
1064 1061 ip.run_cell("del warnings")
1065 1062 ip.run_cell("del wrn")
1066 1063
1067 1064
1068 1065 class TestImportNoDeprecate(tt.TempFileMixin):
1069 1066
1070 1067 def setUp(self):
1071 1068 """Make a valid python temp file."""
1072 1069 self.mktmp("""
1073 1070 import warnings
1074 1071 def wrn():
1075 1072 warnings.warn(
1076 1073 "I AM A WARNING",
1077 1074 DeprecationWarning
1078 1075 )
1079 1076 """)
1080 1077 super().setUp()
1081 1078
1082 1079 def test_no_dep(self):
1083 1080 """
1084 1081 No deprecation warning should be raised from imported functions
1085 1082 """
1086 1083 ip.run_cell("from {} import wrn".format(self.fname))
1087 1084
1088 1085 with tt.AssertNotPrints("I AM A WARNING"):
1089 1086 ip.run_cell("wrn()")
1090 1087 ip.run_cell("del wrn")
1091 1088
1092 1089
1093 1090 def test_custom_exc_count():
1094 1091 hook = mock.Mock(return_value=None)
1095 1092 ip.set_custom_exc((SyntaxError,), hook)
1096 1093 before = ip.execution_count
1097 1094 ip.run_cell("def foo()", store_history=True)
1098 1095 # restore default excepthook
1099 1096 ip.set_custom_exc((), None)
1100 1097 assert hook.call_count == 1
1101 1098 assert ip.execution_count == before + 1
1102 1099
1103 1100
1104 1101 def test_run_cell_async():
1105 1102 ip.run_cell("import asyncio")
1106 1103 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1107 1104 assert asyncio.iscoroutine(coro)
1108 1105 loop = asyncio.new_event_loop()
1109 1106 result = loop.run_until_complete(coro)
1110 1107 assert isinstance(result, interactiveshell.ExecutionResult)
1111 1108 assert result.result == 5
1112 1109
1113 1110
1114 1111 def test_run_cell_await():
1115 1112 ip.run_cell("import asyncio")
1116 1113 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1117 1114 assert ip.user_ns["_"] == 10
1118 1115
1119 1116
1120 1117 def test_run_cell_asyncio_run():
1121 1118 ip.run_cell("import asyncio")
1122 1119 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1123 1120 assert ip.user_ns["_"] == 1
1124 1121 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1125 1122 assert ip.user_ns["_"] == 2
1126 1123 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1127 1124 assert ip.user_ns["_"] == 3
1128 1125
1129 1126
1130 1127 def test_should_run_async():
1131 1128 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1132 1129 assert ip.should_run_async("await x", transformed_cell="await x")
1133 1130 assert ip.should_run_async(
1134 1131 "import asyncio; await asyncio.sleep(1)",
1135 1132 transformed_cell="import asyncio; await asyncio.sleep(1)",
1136 1133 )
1137 1134
1138 1135
1139 1136 def test_set_custom_completer():
1140 1137 num_completers = len(ip.Completer.matchers)
1141 1138
1142 1139 def foo(*args, **kwargs):
1143 1140 return "I'm a completer!"
1144 1141
1145 1142 ip.set_custom_completer(foo, 0)
1146 1143
1147 1144 # check that we've really added a new completer
1148 1145 assert len(ip.Completer.matchers) == num_completers + 1
1149 1146
1150 1147 # check that the first completer is the function we defined
1151 1148 assert ip.Completer.matchers[0]() == "I'm a completer!"
1152 1149
1153 1150 # clean up
1154 1151 ip.Completer.custom_matchers.pop()
1155 1152
1156 1153
1157 1154 class TestShowTracebackAttack(unittest.TestCase):
1158 1155 """Test that the interactive shell is resilient against the client attack of
1159 1156 manipulating the showtracebacks method. These attacks shouldn't result in an
1160 1157 unhandled exception in the kernel."""
1161 1158
1162 1159 def setUp(self):
1163 1160 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1164 1161
1165 1162 def tearDown(self):
1166 1163 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1167 1164
1168 1165 def test_set_show_tracebacks_none(self):
1169 1166 """Test the case of the client setting showtracebacks to None"""
1170 1167
1171 1168 result = ip.run_cell(
1172 1169 """
1173 1170 import IPython.core.interactiveshell
1174 1171 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1175 1172
1176 1173 assert False, "This should not raise an exception"
1177 1174 """
1178 1175 )
1179 1176 print(result)
1180 1177
1181 1178 assert result.result is None
1182 1179 assert isinstance(result.error_in_exec, TypeError)
1183 1180 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1184 1181
1185 1182 def test_set_show_tracebacks_noop(self):
1186 1183 """Test the case of the client setting showtracebacks to a no op lambda"""
1187 1184
1188 1185 result = ip.run_cell(
1189 1186 """
1190 1187 import IPython.core.interactiveshell
1191 1188 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1192 1189
1193 1190 assert False, "This should not raise an exception"
1194 1191 """
1195 1192 )
1196 1193 print(result)
1197 1194
1198 1195 assert result.result is None
1199 1196 assert isinstance(result.error_in_exec, AssertionError)
1200 1197 assert str(result.error_in_exec) == "This should not raise an exception"
@@ -1,291 +1,246 b''
1 1 """Tests for the key interactiveshell module, where the main ipython class is defined.
2 2 """
3 3
4 4 import stack_data
5 5 import sys
6 6
7 7 SV_VERSION = tuple([int(x) for x in stack_data.__version__.split(".")[0:2]])
8 8
9 9
10 10 def test_reset():
11 11 """reset must clear most namespaces."""
12 12
13 13 # Check that reset runs without error
14 14 ip.reset()
15 15
16 16 # Once we've reset it (to clear of any junk that might have been there from
17 17 # other tests, we can count how many variables are in the user's namespace
18 18 nvars_user_ns = len(ip.user_ns)
19 19 nvars_hidden = len(ip.user_ns_hidden)
20 20
21 21 # Now add a few variables to user_ns, and check that reset clears them
22 22 ip.user_ns['x'] = 1
23 23 ip.user_ns['y'] = 1
24 24 ip.reset()
25 25
26 26 # Finally, check that all namespaces have only as many variables as we
27 27 # expect to find in them:
28 28 assert len(ip.user_ns) == nvars_user_ns
29 29 assert len(ip.user_ns_hidden) == nvars_hidden
30 30
31 31
32 32 # Tests for reporting of exceptions in various modes, handling of SystemExit,
33 33 # and %tb functionality. This is really a mix of testing ultraTB and interactiveshell.
34 34
35 35 def doctest_tb_plain():
36 36 """
37 37 In [18]: xmode plain
38 38 Exception reporting mode: Plain
39 39
40 40 In [19]: run simpleerr.py
41 41 Traceback (most recent call last):
42 42 File ...:...
43 43 bar(mode)
44 44 File ...:... in bar
45 45 div0()
46 46 File ...:... in div0
47 47 x/y
48 48 ZeroDivisionError: ...
49 49 """
50 50
51 51
52 52 def doctest_tb_context():
53 53 """
54 54 In [3]: xmode context
55 55 Exception reporting mode: Context
56 56
57 57 In [4]: run simpleerr.py
58 58 ---------------------------------------------------------------------------
59 59 ZeroDivisionError Traceback (most recent call last)
60 60 <BLANKLINE>
61 61 ...
62 62 30 except IndexError:
63 63 31 mode = 'div'
64 64 ---> 33 bar(mode)
65 65 <BLANKLINE>
66 66 ... in bar(mode)
67 67 15 "bar"
68 68 16 if mode=='div':
69 69 ---> 17 div0()
70 70 18 elif mode=='exit':
71 71 19 try:
72 72 <BLANKLINE>
73 73 ... in div0()
74 74 6 x = 1
75 75 7 y = 0
76 76 ----> 8 x/y
77 77 <BLANKLINE>
78 78 ZeroDivisionError: ..."""
79 79
80 80
81 81 def doctest_tb_verbose():
82 82 """
83 83 In [5]: xmode verbose
84 84 Exception reporting mode: Verbose
85 85
86 86 In [6]: run simpleerr.py
87 87 ---------------------------------------------------------------------------
88 88 ZeroDivisionError Traceback (most recent call last)
89 89 <BLANKLINE>
90 90 ...
91 91 30 except IndexError:
92 92 31 mode = 'div'
93 93 ---> 33 bar(mode)
94 94 mode = 'div'
95 95 <BLANKLINE>
96 96 ... in bar(mode='div')
97 97 15 "bar"
98 98 16 if mode=='div':
99 99 ---> 17 div0()
100 100 18 elif mode=='exit':
101 101 19 try:
102 102 <BLANKLINE>
103 103 ... in div0()
104 104 6 x = 1
105 105 7 y = 0
106 106 ----> 8 x/y
107 107 x = 1
108 108 y = 0
109 109 <BLANKLINE>
110 110 ZeroDivisionError: ...
111 111 """
112 112
113 113
114 114 def doctest_tb_sysexit():
115 115 """
116 116 In [17]: %xmode plain
117 117 Exception reporting mode: Plain
118 118
119 119 In [18]: %run simpleerr.py exit
120 120 An exception has occurred, use %tb to see the full traceback.
121 121 SystemExit: (1, 'Mode = exit')
122 122
123 123 In [19]: %run simpleerr.py exit 2
124 124 An exception has occurred, use %tb to see the full traceback.
125 125 SystemExit: (2, 'Mode = exit')
126 126
127 127 In [20]: %tb
128 128 Traceback (most recent call last):
129 129 File ...:... in execfile
130 130 exec(compiler(f.read(), fname, "exec"), glob, loc)
131 131 File ...:...
132 132 bar(mode)
133 133 File ...:... in bar
134 134 sysexit(stat, mode)
135 135 File ...:... in sysexit
136 136 raise SystemExit(stat, f"Mode = {mode}")
137 137 SystemExit: (2, 'Mode = exit')
138 138
139 139 In [21]: %xmode context
140 140 Exception reporting mode: Context
141 141
142 142 In [22]: %tb
143 143 ---------------------------------------------------------------------------
144 144 SystemExit Traceback (most recent call last)
145 145 File ..., in execfile(fname, glob, loc, compiler)
146 146 ... with open(fname, "rb") as f:
147 147 ... compiler = compiler or compile
148 148 ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc)
149 149 ...
150 150 30 except IndexError:
151 151 31 mode = 'div'
152 152 ---> 33 bar(mode)
153 153 <BLANKLINE>
154 154 ...bar(mode)
155 155 21 except:
156 156 22 stat = 1
157 157 ---> 23 sysexit(stat, mode)
158 158 24 else:
159 159 25 raise ValueError('Unknown mode')
160 160 <BLANKLINE>
161 161 ...sysexit(stat, mode)
162 162 10 def sysexit(stat, mode):
163 163 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
164 164 <BLANKLINE>
165 165 SystemExit: (2, 'Mode = exit')
166 166 """
167 167
168 168
169 if sys.version_info >= (3, 9):
170 if SV_VERSION < (0, 6):
171
172 def doctest_tb_sysexit_verbose_stack_data_05():
173 """
174 In [18]: %run simpleerr.py exit
175 An exception has occurred, use %tb to see the full traceback.
176 SystemExit: (1, 'Mode = exit')
177
178 In [19]: %run simpleerr.py exit 2
179 An exception has occurred, use %tb to see the full traceback.
180 SystemExit: (2, 'Mode = exit')
181
182 In [23]: %xmode verbose
183 Exception reporting mode: Verbose
184
185 In [24]: %tb
186 ---------------------------------------------------------------------------
187 SystemExit Traceback (most recent call last)
188 <BLANKLINE>
189 ...
190 30 except IndexError:
191 31 mode = 'div'
192 ---> 33 bar(mode)
193 mode = 'exit'
194 <BLANKLINE>
195 ... in bar(mode='exit')
196 ... except:
197 ... stat = 1
198 ---> ... sysexit(stat, mode)
199 mode = 'exit'
200 stat = 2
201 ... else:
202 ... raise ValueError('Unknown mode')
203 <BLANKLINE>
204 ... in sysexit(stat=2, mode='exit')
205 10 def sysexit(stat, mode):
206 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
207 stat = 2
208 <BLANKLINE>
209 SystemExit: (2, 'Mode = exit')
210 """
211
212 else:
213 # currently the only difference is
214 # + mode = 'exit'
215
216 def doctest_tb_sysexit_verbose_stack_data_06():
217 """
218 In [18]: %run simpleerr.py exit
219 An exception has occurred, use %tb to see the full traceback.
220 SystemExit: (1, 'Mode = exit')
221
222 In [19]: %run simpleerr.py exit 2
223 An exception has occurred, use %tb to see the full traceback.
224 SystemExit: (2, 'Mode = exit')
225
226 In [23]: %xmode verbose
227 Exception reporting mode: Verbose
228
229 In [24]: %tb
230 ---------------------------------------------------------------------------
231 SystemExit Traceback (most recent call last)
232 <BLANKLINE>
233 ...
234 30 except IndexError:
235 31 mode = 'div'
236 ---> 33 bar(mode)
237 mode = 'exit'
238 <BLANKLINE>
239 ... in bar(mode='exit')
240 ... except:
241 ... stat = 1
242 ---> ... sysexit(stat, mode)
243 mode = 'exit'
244 stat = 2
245 ... else:
246 ... raise ValueError('Unknown mode')
247 <BLANKLINE>
248 ... in sysexit(stat=2, mode='exit')
249 10 def sysexit(stat, mode):
250 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
251 stat = 2
252 mode = 'exit'
253 <BLANKLINE>
254 SystemExit: (2, 'Mode = exit')
255 """
169 if SV_VERSION < (0, 6):
170
171 def doctest_tb_sysexit_verbose_stack_data_05():
172 """
173 In [18]: %run simpleerr.py exit
174 An exception has occurred, use %tb to see the full traceback.
175 SystemExit: (1, 'Mode = exit')
176
177 In [19]: %run simpleerr.py exit 2
178 An exception has occurred, use %tb to see the full traceback.
179 SystemExit: (2, 'Mode = exit')
180
181 In [23]: %xmode verbose
182 Exception reporting mode: Verbose
183
184 In [24]: %tb
185 ---------------------------------------------------------------------------
186 SystemExit Traceback (most recent call last)
187 <BLANKLINE>
188 ...
189 30 except IndexError:
190 31 mode = 'div'
191 ---> 33 bar(mode)
192 mode = 'exit'
193 <BLANKLINE>
194 ... in bar(mode='exit')
195 ... except:
196 ... stat = 1
197 ---> ... sysexit(stat, mode)
198 mode = 'exit'
199 stat = 2
200 ... else:
201 ... raise ValueError('Unknown mode')
202 <BLANKLINE>
203 ... in sysexit(stat=2, mode='exit')
204 10 def sysexit(stat, mode):
205 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
206 stat = 2
207 <BLANKLINE>
208 SystemExit: (2, 'Mode = exit')
209 """
210
256 211
257 212 def test_run_cell():
258 213 import textwrap
259 214
260 215 ip.run_cell("a = 10\na+=1")
261 216 ip.run_cell("assert a == 11\nassert 1")
262 217
263 218 assert ip.user_ns["a"] == 11
264 219 complex = textwrap.dedent(
265 220 """
266 221 if 1:
267 222 print "hello"
268 223 if 1:
269 224 print "world"
270 225
271 226 if 2:
272 227 print "foo"
273 228
274 229 if 3:
275 230 print "bar"
276 231
277 232 if 4:
278 233 print "bar"
279 234
280 235 """
281 236 )
282 237 # Simply verifies that this kind of input is run
283 238 ip.run_cell(complex)
284 239
285 240
286 241 def test_db():
287 242 """Test the internal database used for variable persistence."""
288 243 ip.db["__unittest_"] = 12
289 244 assert ip.db["__unittest_"] == 12
290 245 del ip.db["__unittest_"]
291 246 assert "__unittest_" not in ip.db
@@ -1,587 +1,579 b''
1 1 """Tests for the object inspection functionality.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from contextlib import contextmanager
9 9 from inspect import signature, Signature, Parameter
10 10 import inspect
11 11 import os
12 12 import pytest
13 13 import re
14 14 import sys
15 15
16 16 from .. import oinspect
17 17
18 18 from decorator import decorator
19 19
20 20 from IPython.testing.tools import AssertPrints, AssertNotPrints
21 21 from IPython.utils.path import compress_user
22 22
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Globals and constants
26 26 #-----------------------------------------------------------------------------
27 27
28 28 inspector = None
29 29
30 30 def setup_module():
31 31 global inspector
32 32 inspector = oinspect.Inspector()
33 33
34 34
35 35 class SourceModuleMainTest:
36 36 __module__ = "__main__"
37 37
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Local utilities
41 41 #-----------------------------------------------------------------------------
42 42
43 43 # WARNING: since this test checks the line number where a function is
44 44 # defined, if any code is inserted above, the following line will need to be
45 45 # updated. Do NOT insert any whitespace between the next line and the function
46 46 # definition below.
47 47 THIS_LINE_NUMBER = 47 # Put here the actual number of this line
48 48
49 49
50 50 def test_find_source_lines():
51 51 assert oinspect.find_source_lines(test_find_source_lines) == THIS_LINE_NUMBER + 3
52 52 assert oinspect.find_source_lines(type) is None
53 53 assert oinspect.find_source_lines(SourceModuleMainTest) is None
54 54 assert oinspect.find_source_lines(SourceModuleMainTest()) is None
55 55
56 56
57 57 def test_getsource():
58 58 assert oinspect.getsource(type) is None
59 59 assert oinspect.getsource(SourceModuleMainTest) is None
60 60 assert oinspect.getsource(SourceModuleMainTest()) is None
61 61
62 62
63 63 def test_inspect_getfile_raises_exception():
64 64 """Check oinspect.find_file/getsource/find_source_lines expectations"""
65 65 with pytest.raises(TypeError):
66 66 inspect.getfile(type)
67 67 with pytest.raises(OSError if sys.version_info >= (3, 10) else TypeError):
68 68 inspect.getfile(SourceModuleMainTest)
69 69
70 70
71 71 # A couple of utilities to ensure these tests work the same from a source or a
72 72 # binary install
73 73 def pyfile(fname):
74 74 return os.path.normcase(re.sub('.py[co]$', '.py', fname))
75 75
76 76
77 77 def match_pyfiles(f1, f2):
78 78 assert pyfile(f1) == pyfile(f2)
79 79
80 80
81 81 def test_find_file():
82 82 match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
83 83 assert oinspect.find_file(type) is None
84 84 assert oinspect.find_file(SourceModuleMainTest) is None
85 85 assert oinspect.find_file(SourceModuleMainTest()) is None
86 86
87 87
88 88 def test_find_file_decorated1():
89 89
90 90 @decorator
91 91 def noop1(f):
92 92 def wrapper(*a, **kw):
93 93 return f(*a, **kw)
94 94 return wrapper
95 95
96 96 @noop1
97 97 def f(x):
98 98 "My docstring"
99 99
100 100 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
101 101 assert f.__doc__ == "My docstring"
102 102
103 103
104 104 def test_find_file_decorated2():
105 105
106 106 @decorator
107 107 def noop2(f, *a, **kw):
108 108 return f(*a, **kw)
109 109
110 110 @noop2
111 111 @noop2
112 112 @noop2
113 113 def f(x):
114 114 "My docstring 2"
115 115
116 116 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
117 117 assert f.__doc__ == "My docstring 2"
118 118
119 119
120 120 def test_find_file_magic():
121 121 run = ip.find_line_magic('run')
122 122 assert oinspect.find_file(run) is not None
123 123
124 124
125 125 # A few generic objects we can then inspect in the tests below
126 126
127 127 class Call(object):
128 128 """This is the class docstring."""
129 129
130 130 def __init__(self, x, y=1):
131 131 """This is the constructor docstring."""
132 132
133 133 def __call__(self, *a, **kw):
134 134 """This is the call docstring."""
135 135
136 136 def method(self, x, z=2):
137 137 """Some method's docstring"""
138 138
139 139 class HasSignature(object):
140 140 """This is the class docstring."""
141 141 __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
142 142
143 143 def __init__(self, *args):
144 144 """This is the init docstring"""
145 145
146 146
147 147 class SimpleClass(object):
148 148 def method(self, x, z=2):
149 149 """Some method's docstring"""
150 150
151 151
152 152 class Awkward(object):
153 153 def __getattr__(self, name):
154 154 raise Exception(name)
155 155
156 156 class NoBoolCall:
157 157 """
158 158 callable with `__bool__` raising should still be inspect-able.
159 159 """
160 160
161 161 def __call__(self):
162 162 """does nothing"""
163 163 pass
164 164
165 165 def __bool__(self):
166 166 """just raise NotImplemented"""
167 167 raise NotImplementedError('Must be implemented')
168 168
169 169
170 170 class SerialLiar(object):
171 171 """Attribute accesses always get another copy of the same class.
172 172
173 173 unittest.mock.call does something similar, but it's not ideal for testing
174 174 as the failure mode is to eat all your RAM. This gives up after 10k levels.
175 175 """
176 176 def __init__(self, max_fibbing_twig, lies_told=0):
177 177 if lies_told > 10000:
178 178 raise RuntimeError('Nose too long, honesty is the best policy')
179 179 self.max_fibbing_twig = max_fibbing_twig
180 180 self.lies_told = lies_told
181 181 max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told)
182 182
183 183 def __getattr__(self, item):
184 184 return SerialLiar(self.max_fibbing_twig, self.lies_told + 1)
185 185
186 186 #-----------------------------------------------------------------------------
187 187 # Tests
188 188 #-----------------------------------------------------------------------------
189 189
190 190 def test_info():
191 191 "Check that Inspector.info fills out various fields as expected."
192 192 i = inspector.info(Call, oname="Call")
193 193 assert i["type_name"] == "type"
194 194 expected_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
195 195 assert i["base_class"] == expected_class
196 196 assert re.search(
197 197 "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>",
198 198 i["string_form"],
199 199 )
200 200 fname = __file__
201 201 if fname.endswith(".pyc"):
202 202 fname = fname[:-1]
203 203 # case-insensitive comparison needed on some filesystems
204 204 # e.g. Windows:
205 205 assert i["file"].lower() == compress_user(fname).lower()
206 206 assert i["definition"] == None
207 207 assert i["docstring"] == Call.__doc__
208 208 assert i["source"] == None
209 209 assert i["isclass"] is True
210 210 assert i["init_definition"] == "Call(x, y=1)"
211 211 assert i["init_docstring"] == Call.__init__.__doc__
212 212
213 213 i = inspector.info(Call, detail_level=1)
214 214 assert i["source"] is not None
215 215 assert i["docstring"] == None
216 216
217 217 c = Call(1)
218 218 c.__doc__ = "Modified instance docstring"
219 219 i = inspector.info(c)
220 220 assert i["type_name"] == "Call"
221 221 assert i["docstring"] == "Modified instance docstring"
222 222 assert i["class_docstring"] == Call.__doc__
223 223 assert i["init_docstring"] == Call.__init__.__doc__
224 224 assert i["call_docstring"] == Call.__call__.__doc__
225 225
226 226
227 227 def test_class_signature():
228 228 info = inspector.info(HasSignature, "HasSignature")
229 229 assert info["init_definition"] == "HasSignature(test)"
230 230 assert info["init_docstring"] == HasSignature.__init__.__doc__
231 231
232 232
233 233 def test_info_awkward():
234 234 # Just test that this doesn't throw an error.
235 235 inspector.info(Awkward())
236 236
237 237 def test_bool_raise():
238 238 inspector.info(NoBoolCall())
239 239
240 240 def test_info_serialliar():
241 241 fib_tracker = [0]
242 242 inspector.info(SerialLiar(fib_tracker))
243 243
244 244 # Nested attribute access should be cut off at 100 levels deep to avoid
245 245 # infinite loops: https://github.com/ipython/ipython/issues/9122
246 246 assert fib_tracker[0] < 9000
247 247
248 248 def support_function_one(x, y=2, *a, **kw):
249 249 """A simple function."""
250 250
251 251 def test_calldef_none():
252 252 # We should ignore __call__ for all of these.
253 253 for obj in [support_function_one, SimpleClass().method, any, str.upper]:
254 254 i = inspector.info(obj)
255 255 assert i["call_def"] is None
256 256
257 257
258 258 def f_kwarg(pos, *, kwonly):
259 259 pass
260 260
261 261 def test_definition_kwonlyargs():
262 262 i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore
263 263 assert i["definition"] == "f_kwarg(pos, *, kwonly)"
264 264
265 265
266 266 def test_getdoc():
267 267 class A(object):
268 268 """standard docstring"""
269 269 pass
270 270
271 271 class B(object):
272 272 """standard docstring"""
273 273 def getdoc(self):
274 274 return "custom docstring"
275 275
276 276 class C(object):
277 277 """standard docstring"""
278 278 def getdoc(self):
279 279 return None
280 280
281 281 a = A()
282 282 b = B()
283 283 c = C()
284 284
285 285 assert oinspect.getdoc(a) == "standard docstring"
286 286 assert oinspect.getdoc(b) == "custom docstring"
287 287 assert oinspect.getdoc(c) == "standard docstring"
288 288
289 289
290 290 def test_empty_property_has_no_source():
291 291 i = inspector.info(property(), detail_level=1)
292 292 assert i["source"] is None
293 293
294 294
295 295 def test_property_sources():
296 296 # A simple adder whose source and signature stays
297 297 # the same across Python distributions
298 298 def simple_add(a, b):
299 299 "Adds two numbers"
300 300 return a + b
301 301
302 302 class A(object):
303 303 @property
304 304 def foo(self):
305 305 return 'bar'
306 306
307 307 foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
308 308
309 309 dname = property(oinspect.getdoc)
310 310 adder = property(simple_add)
311 311
312 312 i = inspector.info(A.foo, detail_level=1)
313 313 assert "def foo(self):" in i["source"]
314 314 assert "lambda self, v:" in i["source"]
315 315
316 316 i = inspector.info(A.dname, detail_level=1)
317 317 assert "def getdoc(obj)" in i["source"]
318 318
319 319 i = inspector.info(A.adder, detail_level=1)
320 320 assert "def simple_add(a, b)" in i["source"]
321 321
322 322
323 323 def test_property_docstring_is_in_info_for_detail_level_0():
324 324 class A(object):
325 325 @property
326 326 def foobar(self):
327 327 """This is `foobar` property."""
328 328 pass
329 329
330 330 ip.user_ns["a_obj"] = A()
331 331 assert (
332 332 "This is `foobar` property."
333 333 == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"]
334 334 )
335 335
336 336 ip.user_ns["a_cls"] = A
337 337 assert (
338 338 "This is `foobar` property."
339 339 == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"]
340 340 )
341 341
342 342
343 343 def test_pdef():
344 344 # See gh-1914
345 345 def foo(): pass
346 346 inspector.pdef(foo, 'foo')
347 347
348 348
349 349 @contextmanager
350 350 def cleanup_user_ns(**kwargs):
351 351 """
352 352 On exit delete all the keys that were not in user_ns before entering.
353 353
354 354 It does not restore old values !
355 355
356 356 Parameters
357 357 ----------
358 358
359 359 **kwargs
360 360 used to update ip.user_ns
361 361
362 362 """
363 363 try:
364 364 known = set(ip.user_ns.keys())
365 365 ip.user_ns.update(kwargs)
366 366 yield
367 367 finally:
368 368 added = set(ip.user_ns.keys()) - known
369 369 for k in added:
370 370 del ip.user_ns[k]
371 371
372 372
373 373 def test_pinfo_bool_raise():
374 374 """
375 375 Test that bool method is not called on parent.
376 376 """
377 377
378 378 class RaiseBool:
379 379 attr = None
380 380
381 381 def __bool__(self):
382 382 raise ValueError("pinfo should not access this method")
383 383
384 384 raise_bool = RaiseBool()
385 385
386 386 with cleanup_user_ns(raise_bool=raise_bool):
387 387 ip._inspect("pinfo", "raise_bool.attr", detail_level=0)
388 388
389 389
390 390 def test_pinfo_getindex():
391 391 def dummy():
392 392 """
393 393 MARKER
394 394 """
395 395
396 396 container = [dummy]
397 397 with cleanup_user_ns(container=container):
398 398 with AssertPrints("MARKER"):
399 399 ip._inspect("pinfo", "container[0]", detail_level=0)
400 400 assert "container" not in ip.user_ns.keys()
401 401
402 402
403 403 def test_qmark_getindex():
404 404 def dummy():
405 405 """
406 406 MARKER 2
407 407 """
408 408
409 409 container = [dummy]
410 410 with cleanup_user_ns(container=container):
411 411 with AssertPrints("MARKER 2"):
412 412 ip.run_cell("container[0]?")
413 413 assert "container" not in ip.user_ns.keys()
414 414
415 415
416 416 def test_qmark_getindex_negatif():
417 417 def dummy():
418 418 """
419 419 MARKER 3
420 420 """
421 421
422 422 container = [dummy]
423 423 with cleanup_user_ns(container=container):
424 424 with AssertPrints("MARKER 3"):
425 425 ip.run_cell("container[-1]?")
426 426 assert "container" not in ip.user_ns.keys()
427 427
428 428
429 429
430 430 def test_pinfo_nonascii():
431 431 # See gh-1177
432 432 from . import nonascii2
433 433 ip.user_ns['nonascii2'] = nonascii2
434 434 ip._inspect('pinfo', 'nonascii2', detail_level=1)
435 435
436 436 def test_pinfo_type():
437 437 """
438 438 type can fail in various edge case, for example `type.__subclass__()`
439 439 """
440 440 ip._inspect('pinfo', 'type')
441 441
442 442
443 443 def test_pinfo_docstring_no_source():
444 444 """Docstring should be included with detail_level=1 if there is no source"""
445 445 with AssertPrints('Docstring:'):
446 446 ip._inspect('pinfo', 'str.format', detail_level=0)
447 447 with AssertPrints('Docstring:'):
448 448 ip._inspect('pinfo', 'str.format', detail_level=1)
449 449
450 450
451 451 def test_pinfo_no_docstring_if_source():
452 452 """Docstring should not be included with detail_level=1 if source is found"""
453 453 def foo():
454 454 """foo has a docstring"""
455 455
456 456 ip.user_ns['foo'] = foo
457 457
458 458 with AssertPrints('Docstring:'):
459 459 ip._inspect('pinfo', 'foo', detail_level=0)
460 460 with AssertPrints('Source:'):
461 461 ip._inspect('pinfo', 'foo', detail_level=1)
462 462 with AssertNotPrints('Docstring:'):
463 463 ip._inspect('pinfo', 'foo', detail_level=1)
464 464
465 465
466 466 def test_pinfo_docstring_if_detail_and_no_source():
467 467 """ Docstring should be displayed if source info not available """
468 468 obj_def = '''class Foo(object):
469 469 """ This is a docstring for Foo """
470 470 def bar(self):
471 471 """ This is a docstring for Foo.bar """
472 472 pass
473 473 '''
474 474
475 475 ip.run_cell(obj_def)
476 476 ip.run_cell('foo = Foo()')
477 477
478 478 with AssertNotPrints("Source:"):
479 479 with AssertPrints('Docstring:'):
480 480 ip._inspect('pinfo', 'foo', detail_level=0)
481 481 with AssertPrints('Docstring:'):
482 482 ip._inspect('pinfo', 'foo', detail_level=1)
483 483 with AssertPrints('Docstring:'):
484 484 ip._inspect('pinfo', 'foo.bar', detail_level=0)
485 485
486 486 with AssertNotPrints('Docstring:'):
487 487 with AssertPrints('Source:'):
488 488 ip._inspect('pinfo', 'foo.bar', detail_level=1)
489 489
490 490
491 491 def test_pinfo_docstring_dynamic():
492 492 obj_def = """class Bar:
493 493 __custom_documentations__ = {
494 494 "prop" : "cdoc for prop",
495 495 "non_exist" : "cdoc for non_exist",
496 496 }
497 497 @property
498 498 def prop(self):
499 499 '''
500 500 Docstring for prop
501 501 '''
502 502 return self._prop
503 503
504 504 @prop.setter
505 505 def prop(self, v):
506 506 self._prop = v
507 507 """
508 508 ip.run_cell(obj_def)
509 509
510 510 ip.run_cell("b = Bar()")
511 511
512 512 with AssertPrints("Docstring: cdoc for prop"):
513 513 ip.run_line_magic("pinfo", "b.prop")
514 514
515 515 with AssertPrints("Docstring: cdoc for non_exist"):
516 516 ip.run_line_magic("pinfo", "b.non_exist")
517 517
518 518 with AssertPrints("Docstring: cdoc for prop"):
519 519 ip.run_cell("b.prop?")
520 520
521 521 with AssertPrints("Docstring: cdoc for non_exist"):
522 522 ip.run_cell("b.non_exist?")
523 523
524 524 with AssertPrints("Docstring: <no docstring>"):
525 525 ip.run_cell("b.undefined?")
526 526
527 527
528 528 def test_pinfo_magic():
529 529 with AssertPrints("Docstring:"):
530 530 ip._inspect("pinfo", "lsmagic", detail_level=0)
531 531
532 532 with AssertPrints("Source:"):
533 533 ip._inspect("pinfo", "lsmagic", detail_level=1)
534 534
535 535
536 536 def test_init_colors():
537 537 # ensure colors are not present in signature info
538 538 info = inspector.info(HasSignature)
539 539 init_def = info["init_definition"]
540 540 assert "[0m" not in init_def
541 541
542 542
543 543 def test_builtin_init():
544 544 info = inspector.info(list)
545 545 init_def = info['init_definition']
546 546 assert init_def is not None
547 547
548 548
549 549 def test_render_signature_short():
550 550 def short_fun(a=1): pass
551 551 sig = oinspect._render_signature(
552 552 signature(short_fun),
553 553 short_fun.__name__,
554 554 )
555 555 assert sig == "short_fun(a=1)"
556 556
557 557
558 558 def test_render_signature_long():
559 559 from typing import Optional
560 560
561 561 def long_function(
562 562 a_really_long_parameter: int,
563 563 and_another_long_one: bool = False,
564 564 let_us_make_sure_this_is_looong: Optional[str] = None,
565 565 ) -> bool: pass
566 566
567 567 sig = oinspect._render_signature(
568 568 signature(long_function),
569 569 long_function.__name__,
570 570 )
571 if sys.version_info >= (3, 9):
572 expected = """\
571 expected = """\
573 572 long_function(
574 573 a_really_long_parameter: int,
575 574 and_another_long_one: bool = False,
576 575 let_us_make_sure_this_is_looong: Optional[str] = None,
577 576 ) -> bool\
578 577 """
579 else:
580 expected = """\
581 long_function(
582 a_really_long_parameter: int,
583 and_another_long_one: bool = False,
584 let_us_make_sure_this_is_looong: Union[str, NoneType] = None,
585 ) -> bool\
586 """
578
587 579 assert sig == expected
@@ -1,408 +1,407 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.core.ultratb
3 3 """
4 4 import io
5 5 import os.path
6 6 import platform
7 7 import re
8 8 import sys
9 9 import traceback
10 10 import unittest
11 11 from textwrap import dedent
12 12
13 13 from tempfile import TemporaryDirectory
14 14
15 15 from IPython.core.ultratb import ColorTB, VerboseTB
16 16 from IPython.testing import tools as tt
17 17 from IPython.testing.decorators import onlyif_unicode_paths
18 18 from IPython.utils.syspathcontext import prepended_to_syspath
19 19
20 20 file_1 = """1
21 21 2
22 22 3
23 23 def f():
24 24 1/0
25 25 """
26 26
27 27 file_2 = """def f():
28 28 1/0
29 29 """
30 30
31 31
32 32 def recursionlimit(frames):
33 33 """
34 34 decorator to set the recursion limit temporarily
35 35 """
36 36
37 37 def inner(test_function):
38 38 def wrapper(*args, **kwargs):
39 39 rl = sys.getrecursionlimit()
40 40 sys.setrecursionlimit(frames)
41 41 try:
42 42 return test_function(*args, **kwargs)
43 43 finally:
44 44 sys.setrecursionlimit(rl)
45 45
46 46 return wrapper
47 47
48 48 return inner
49 49
50 50
51 51 class ChangedPyFileTest(unittest.TestCase):
52 52 def test_changing_py_file(self):
53 53 """Traceback produced if the line where the error occurred is missing?
54 54
55 55 https://github.com/ipython/ipython/issues/1456
56 56 """
57 57 with TemporaryDirectory() as td:
58 58 fname = os.path.join(td, "foo.py")
59 59 with open(fname, "w", encoding="utf-8") as f:
60 60 f.write(file_1)
61 61
62 62 with prepended_to_syspath(td):
63 63 ip.run_cell("import foo")
64 64
65 65 with tt.AssertPrints("ZeroDivisionError"):
66 66 ip.run_cell("foo.f()")
67 67
68 68 # Make the file shorter, so the line of the error is missing.
69 69 with open(fname, "w", encoding="utf-8") as f:
70 70 f.write(file_2)
71 71
72 72 # For some reason, this was failing on the *second* call after
73 73 # changing the file, so we call f() twice.
74 74 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
75 75 with tt.AssertPrints("ZeroDivisionError"):
76 76 ip.run_cell("foo.f()")
77 77 with tt.AssertPrints("ZeroDivisionError"):
78 78 ip.run_cell("foo.f()")
79 79
80 80 iso_8859_5_file = u'''# coding: iso-8859-5
81 81
82 82 def fail():
83 83 """Π΄Π±Π˜Π–"""
84 84 1/0 # Π΄Π±Π˜Π–
85 85 '''
86 86
87 87 class NonAsciiTest(unittest.TestCase):
88 88 @onlyif_unicode_paths
89 89 def test_nonascii_path(self):
90 90 # Non-ascii directory name as well.
91 91 with TemporaryDirectory(suffix=u'Γ©') as td:
92 92 fname = os.path.join(td, u"fooΓ©.py")
93 93 with open(fname, "w", encoding="utf-8") as f:
94 94 f.write(file_1)
95 95
96 96 with prepended_to_syspath(td):
97 97 ip.run_cell("import foo")
98 98
99 99 with tt.AssertPrints("ZeroDivisionError"):
100 100 ip.run_cell("foo.f()")
101 101
102 102 def test_iso8859_5(self):
103 103 with TemporaryDirectory() as td:
104 104 fname = os.path.join(td, 'dfghjkl.py')
105 105
106 106 with io.open(fname, 'w', encoding='iso-8859-5') as f:
107 107 f.write(iso_8859_5_file)
108 108
109 109 with prepended_to_syspath(td):
110 110 ip.run_cell("from dfghjkl import fail")
111 111
112 112 with tt.AssertPrints("ZeroDivisionError"):
113 113 with tt.AssertPrints(u'Π΄Π±Π˜Π–', suppress=False):
114 114 ip.run_cell('fail()')
115 115
116 116 def test_nonascii_msg(self):
117 117 cell = u"raise Exception('Γ©')"
118 118 expected = u"Exception('Γ©')"
119 119 ip.run_cell("%xmode plain")
120 120 with tt.AssertPrints(expected):
121 121 ip.run_cell(cell)
122 122
123 123 ip.run_cell("%xmode verbose")
124 124 with tt.AssertPrints(expected):
125 125 ip.run_cell(cell)
126 126
127 127 ip.run_cell("%xmode context")
128 128 with tt.AssertPrints(expected):
129 129 ip.run_cell(cell)
130 130
131 131 ip.run_cell("%xmode minimal")
132 132 with tt.AssertPrints(u"Exception: Γ©"):
133 133 ip.run_cell(cell)
134 134
135 135 # Put this back into Context mode for later tests.
136 136 ip.run_cell("%xmode context")
137 137
138 138 class NestedGenExprTestCase(unittest.TestCase):
139 139 """
140 140 Regression test for the following issues:
141 141 https://github.com/ipython/ipython/issues/8293
142 142 https://github.com/ipython/ipython/issues/8205
143 143 """
144 144 def test_nested_genexpr(self):
145 145 code = dedent(
146 146 """\
147 147 class SpecificException(Exception):
148 148 pass
149 149
150 150 def foo(x):
151 151 raise SpecificException("Success!")
152 152
153 153 sum(sum(foo(x) for _ in [0]) for x in [0])
154 154 """
155 155 )
156 156 with tt.AssertPrints('SpecificException: Success!', suppress=False):
157 157 ip.run_cell(code)
158 158
159 159
160 160 indentationerror_file = """if True:
161 161 zoon()
162 162 """
163 163
164 164 class IndentationErrorTest(unittest.TestCase):
165 165 def test_indentationerror_shows_line(self):
166 166 # See issue gh-2398
167 167 with tt.AssertPrints("IndentationError"):
168 168 with tt.AssertPrints("zoon()", suppress=False):
169 169 ip.run_cell(indentationerror_file)
170 170
171 171 with TemporaryDirectory() as td:
172 172 fname = os.path.join(td, "foo.py")
173 173 with open(fname, "w", encoding="utf-8") as f:
174 174 f.write(indentationerror_file)
175 175
176 176 with tt.AssertPrints("IndentationError"):
177 177 with tt.AssertPrints("zoon()", suppress=False):
178 178 ip.magic('run %s' % fname)
179 179
180 180 se_file_1 = """1
181 181 2
182 182 7/
183 183 """
184 184
185 185 se_file_2 = """7/
186 186 """
187 187
188 188 class SyntaxErrorTest(unittest.TestCase):
189 189
190 190 def test_syntaxerror_no_stacktrace_at_compile_time(self):
191 191 syntax_error_at_compile_time = """
192 192 def foo():
193 193 ..
194 194 """
195 195 with tt.AssertPrints("SyntaxError"):
196 196 ip.run_cell(syntax_error_at_compile_time)
197 197
198 198 with tt.AssertNotPrints("foo()"):
199 199 ip.run_cell(syntax_error_at_compile_time)
200 200
201 201 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
202 202 syntax_error_at_runtime = """
203 203 def foo():
204 204 eval("..")
205 205
206 206 def bar():
207 207 foo()
208 208
209 209 bar()
210 210 """
211 211 with tt.AssertPrints("SyntaxError"):
212 212 ip.run_cell(syntax_error_at_runtime)
213 213 # Assert syntax error during runtime generate stacktrace
214 214 with tt.AssertPrints(["foo()", "bar()"]):
215 215 ip.run_cell(syntax_error_at_runtime)
216 216 del ip.user_ns['bar']
217 217 del ip.user_ns['foo']
218 218
219 219 def test_changing_py_file(self):
220 220 with TemporaryDirectory() as td:
221 221 fname = os.path.join(td, "foo.py")
222 222 with open(fname, "w", encoding="utf-8") as f:
223 223 f.write(se_file_1)
224 224
225 225 with tt.AssertPrints(["7/", "SyntaxError"]):
226 226 ip.magic("run " + fname)
227 227
228 228 # Modify the file
229 229 with open(fname, "w", encoding="utf-8") as f:
230 230 f.write(se_file_2)
231 231
232 232 # The SyntaxError should point to the correct line
233 233 with tt.AssertPrints(["7/", "SyntaxError"]):
234 234 ip.magic("run " + fname)
235 235
236 236 def test_non_syntaxerror(self):
237 237 # SyntaxTB may be called with an error other than a SyntaxError
238 238 # See e.g. gh-4361
239 239 try:
240 240 raise ValueError('QWERTY')
241 241 except ValueError:
242 242 with tt.AssertPrints('QWERTY'):
243 243 ip.showsyntaxerror()
244 244
245 245 import sys
246 246
247 if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy":
247 if platform.python_implementation() != "PyPy":
248 248 """
249 249 New 3.9 Pgen Parser does not raise Memory error, except on failed malloc.
250 250 """
251 251 class MemoryErrorTest(unittest.TestCase):
252 252 def test_memoryerror(self):
253 253 memoryerror_code = "(" * 200 + ")" * 200
254 with tt.AssertPrints("MemoryError"):
255 ip.run_cell(memoryerror_code)
254 ip.run_cell(memoryerror_code)
256 255
257 256
258 257 class Python3ChainedExceptionsTest(unittest.TestCase):
259 258 DIRECT_CAUSE_ERROR_CODE = """
260 259 try:
261 260 x = 1 + 2
262 261 print(not_defined_here)
263 262 except Exception as e:
264 263 x += 55
265 264 x - 1
266 265 y = {}
267 266 raise KeyError('uh') from e
268 267 """
269 268
270 269 EXCEPTION_DURING_HANDLING_CODE = """
271 270 try:
272 271 x = 1 + 2
273 272 print(not_defined_here)
274 273 except Exception as e:
275 274 x += 55
276 275 x - 1
277 276 y = {}
278 277 raise KeyError('uh')
279 278 """
280 279
281 280 SUPPRESS_CHAINING_CODE = """
282 281 try:
283 282 1/0
284 283 except Exception:
285 284 raise ValueError("Yikes") from None
286 285 """
287 286
288 287 def test_direct_cause_error(self):
289 288 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
290 289 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
291 290
292 291 def test_exception_during_handling_error(self):
293 292 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
294 293 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
295 294
296 295 def test_suppress_exception_chaining(self):
297 296 with tt.AssertNotPrints("ZeroDivisionError"), \
298 297 tt.AssertPrints("ValueError", suppress=False):
299 298 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
300 299
301 300 def test_plain_direct_cause_error(self):
302 301 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
303 302 ip.run_cell("%xmode Plain")
304 303 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
305 304 ip.run_cell("%xmode Verbose")
306 305
307 306 def test_plain_exception_during_handling_error(self):
308 307 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
309 308 ip.run_cell("%xmode Plain")
310 309 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
311 310 ip.run_cell("%xmode Verbose")
312 311
313 312 def test_plain_suppress_exception_chaining(self):
314 313 with tt.AssertNotPrints("ZeroDivisionError"), \
315 314 tt.AssertPrints("ValueError", suppress=False):
316 315 ip.run_cell("%xmode Plain")
317 316 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
318 317 ip.run_cell("%xmode Verbose")
319 318
320 319
321 320 class RecursionTest(unittest.TestCase):
322 321 DEFINITIONS = """
323 322 def non_recurs():
324 323 1/0
325 324
326 325 def r1():
327 326 r1()
328 327
329 328 def r3a():
330 329 r3b()
331 330
332 331 def r3b():
333 332 r3c()
334 333
335 334 def r3c():
336 335 r3a()
337 336
338 337 def r3o1():
339 338 r3a()
340 339
341 340 def r3o2():
342 341 r3o1()
343 342 """
344 343 def setUp(self):
345 344 ip.run_cell(self.DEFINITIONS)
346 345
347 346 def test_no_recursion(self):
348 347 with tt.AssertNotPrints("skipping similar frames"):
349 348 ip.run_cell("non_recurs()")
350 349
351 350 @recursionlimit(200)
352 351 def test_recursion_one_frame(self):
353 352 with tt.AssertPrints(re.compile(
354 353 r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]")
355 354 ):
356 355 ip.run_cell("r1()")
357 356
358 357 @recursionlimit(160)
359 358 def test_recursion_three_frames(self):
360 359 with tt.AssertPrints("[... skipping similar frames: "), \
361 360 tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \
362 361 tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \
363 362 tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False):
364 363 ip.run_cell("r3o2()")
365 364
366 365
367 366 #----------------------------------------------------------------------------
368 367
369 368 # module testing (minimal)
370 369 def test_handlers():
371 370 def spam(c, d_e):
372 371 (d, e) = d_e
373 372 x = c + d
374 373 y = c * d
375 374 foo(x, y)
376 375
377 376 def foo(a, b, bar=1):
378 377 eggs(a, b + bar)
379 378
380 379 def eggs(f, g, z=globals()):
381 380 h = f + g
382 381 i = f - g
383 382 return h / i
384 383
385 384 buff = io.StringIO()
386 385
387 386 buff.write('')
388 387 buff.write('*** Before ***')
389 388 try:
390 389 buff.write(spam(1, (2, 3)))
391 390 except:
392 391 traceback.print_exc(file=buff)
393 392
394 393 handler = ColorTB(ostream=buff)
395 394 buff.write('*** ColorTB ***')
396 395 try:
397 396 buff.write(spam(1, (2, 3)))
398 397 except:
399 398 handler(*sys.exc_info())
400 399 buff.write('')
401 400
402 401 handler = VerboseTB(ostream=buff)
403 402 buff.write('*** VerboseTB ***')
404 403 try:
405 404 buff.write(spam(1, (2, 3)))
406 405 except:
407 406 handler(*sys.exc_info())
408 407 buff.write('')
@@ -1,27 +1,27 b''
1 1 build: false
2 2 matrix:
3 3 fast_finish: true # immediately finish build once one of the jobs fails.
4 4
5 5 environment:
6 6 global:
7 7 APPVEYOR_BUILD_WORKER_IMAGE: 'Visual Studio 2022'
8 8 COLUMNS: 120 # Appveyor web viewer window width is 130 chars
9 9
10 10 matrix:
11 - PYTHON: "C:\\Python38"
12 PYTHON_VERSION: "3.8.x"
11 - PYTHON: "C:\\Python39"
12 PYTHON_VERSION: "3.9.x"
13 13 PYTHON_ARCH: "32"
14 14
15 15 init:
16 16 - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%"
17 17
18 18 install:
19 19 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
20 20 - python -m pip install --upgrade setuptools 'pip<22'
21 21 - pip install pytest-cov
22 22 - pip install -e .[test_extra]
23 23 test_script:
24 24 - pytest --color=yes -ra --cov --cov-report=xml
25 25 on_finish:
26 26 - curl -Os https://uploader.codecov.io/latest/windows/codecov.exe
27 27 - codecov -e PYTHON_VERSION,PYTHON_ARCH
@@ -1,32 +1,32 b''
1 1 [build-system]
2 2 requires = ["setuptools >= 51.0.0"]
3 3 build-backend = "setuptools.build_meta"
4 4 [tool.mypy]
5 python_version = 3.8
5 python_version = 3.9
6 6 ignore_missing_imports = true
7 7 follow_imports = 'silent'
8 8 exclude = [
9 9 'test_\.+\.py',
10 10 'IPython.utils.tests.test_wildcard',
11 11 'testing',
12 12 'tests',
13 13 'PyColorize.py',
14 14 '_process_win32_controller.py',
15 15 'IPython/core/application.py',
16 16 'IPython/core/completerlib.py',
17 17 'IPython/core/displaypub.py',
18 18 'IPython/core/historyapp.py',
19 19 #'IPython/core/interactiveshell.py',
20 20 'IPython/core/magic.py',
21 21 'IPython/core/profileapp.py',
22 22 # 'IPython/core/ultratb.py',
23 23 'IPython/lib/deepreload.py',
24 24 'IPython/lib/pretty.py',
25 25 'IPython/sphinxext/ipython_directive.py',
26 26 'IPython/terminal/ipapp.py',
27 27 'IPython/utils/_process_win32.py',
28 28 'IPython/utils/path.py',
29 29 'IPython/utils/timing.py',
30 30 'IPython/utils/text.py'
31 31 ]
32 32
@@ -1,157 +1,158 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Setup script for IPython.
3 3
4 4 Under Posix environments it works like a typical setup.py script.
5 5 Under Windows, the command sdist is not supported, since IPython
6 6 requires utilities which are not available under Windows."""
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (c) 2008-2011, IPython Development Team.
10 10 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
11 11 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
12 12 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
13 13 #
14 14 # Distributed under the terms of the Modified BSD License.
15 15 #
16 16 # The full license is in the file COPYING.rst, distributed with this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import os
20 20 import sys
21 21
22 22 # **Python version check**
23 23 #
24 24 # This check is also made in IPython/__init__, don't forget to update both when
25 25 # changing Python version requirements.
26 if sys.version_info < (3, 8):
26 if sys.version_info < (3, 9):
27 27 pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.'
28 28 try:
29 29 import pip
30 30 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
31 31 if pip_version < (9, 0, 1) :
32 32 pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\
33 33 'pip {} detected.'.format(pip.__version__)
34 34 else:
35 35 # pip is new enough - it must be something else
36 36 pip_message = ''
37 37 except Exception:
38 38 pass
39 39
40 40
41 41 error = """
42 IPython 8+ supports Python 3.8 and above, following NEP 29.
42 IPython 8.13+ supports Python 3.8 and above, following NEP 29.
43 IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29.
43 44 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
44 45 Python 3.3 and 3.4 were supported up to IPython 6.x.
45 46 Python 3.5 was supported with IPython 7.0 to 7.9.
46 47 Python 3.6 was supported with IPython up to 7.16.
47 48 Python 3.7 was still supported with the 7.x branch.
48 49
49 50 See IPython `README.rst` file for more information:
50 51
51 52 https://github.com/ipython/ipython/blob/main/README.rst
52 53
53 54 Python {py} detected.
54 55 {pip}
55 56 """.format(
56 57 py=sys.version_info, pip=pip_message
57 58 )
58 59
59 60 print(error, file=sys.stderr)
60 61 sys.exit(1)
61 62
62 63 # At least we're on the python version we need, move on.
63 64
64 65 from setuptools import setup
65 66
66 67 # Our own imports
67 68 sys.path.insert(0, ".")
68 69
69 70 from setupbase import target_update, find_entry_points
70 71
71 72 from setupbase import (
72 73 setup_args,
73 74 check_package_data_first,
74 75 find_data_files,
75 76 git_prebuild,
76 77 install_symlinked,
77 78 install_lib_symlink,
78 79 install_scripts_for_symlink,
79 80 unsymlink,
80 81 )
81 82
82 83 #-------------------------------------------------------------------------------
83 84 # Handle OS specific things
84 85 #-------------------------------------------------------------------------------
85 86
86 87 if os.name in ('nt','dos'):
87 88 os_name = 'windows'
88 89 else:
89 90 os_name = os.name
90 91
91 92 # Under Windows, 'sdist' has not been supported. Now that the docs build with
92 93 # Sphinx it might work, but let's not turn it on until someone confirms that it
93 94 # actually works.
94 95 if os_name == 'windows' and 'sdist' in sys.argv:
95 96 print('The sdist command is not available under Windows. Exiting.')
96 97 sys.exit(1)
97 98
98 99
99 100 #-------------------------------------------------------------------------------
100 101 # Things related to the IPython documentation
101 102 #-------------------------------------------------------------------------------
102 103
103 104 # update the manuals when building a source dist
104 105 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
105 106
106 107 # List of things to be updated. Each entry is a triplet of args for
107 108 # target_update()
108 109 to_update = [
109 110 (
110 111 "docs/man/ipython.1.gz",
111 112 ["docs/man/ipython.1"],
112 113 "cd docs/man && python -m gzip --best ipython.1",
113 114 ),
114 115 ]
115 116
116 117
117 118 [ target_update(*t) for t in to_update ]
118 119
119 120 #---------------------------------------------------------------------------
120 121 # Find all the packages, package data, and data_files
121 122 #---------------------------------------------------------------------------
122 123
123 124 data_files = find_data_files()
124 125
125 126 setup_args['data_files'] = data_files
126 127
127 128 #---------------------------------------------------------------------------
128 129 # custom distutils commands
129 130 #---------------------------------------------------------------------------
130 131 # imports here, so they are after setuptools import if there was one
131 132 from setuptools.command.sdist import sdist
132 133
133 134 setup_args['cmdclass'] = {
134 135 'build_py': \
135 136 check_package_data_first(git_prebuild('IPython')),
136 137 'sdist' : git_prebuild('IPython', sdist),
137 138 'symlink': install_symlinked,
138 139 'install_lib_symlink': install_lib_symlink,
139 140 'install_scripts_sym': install_scripts_for_symlink,
140 141 'unsymlink': unsymlink,
141 142 }
142 143
143 144 setup_args["entry_points"] = {
144 145 "console_scripts": find_entry_points(),
145 146 "pygments.lexers": [
146 147 "ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer",
147 148 "ipython = IPython.lib.lexers:IPythonLexer",
148 149 "ipython3 = IPython.lib.lexers:IPython3Lexer",
149 150 ],
150 151 }
151 152
152 153 #---------------------------------------------------------------------------
153 154 # Do the actual setup now
154 155 #---------------------------------------------------------------------------
155 156
156 157 if __name__ == "__main__":
157 158 setup(**setup_args)
General Comments 0
You need to be logged in to leave comments. Login now