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