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