Show More
The requested changes are too big and content was truncated. Show full diff
@@ -0,0 +1,26 b'' | |||||
|
1 | name: Build docs | |||
|
2 | ||||
|
3 | on: [push, pull_request] | |||
|
4 | ||||
|
5 | jobs: | |||
|
6 | build: | |||
|
7 | runs-on: ubuntu-latest | |||
|
8 | ||||
|
9 | steps: | |||
|
10 | - uses: actions/checkout@v2 | |||
|
11 | - name: Set up Python 3.8 | |||
|
12 | uses: actions/setup-python@v2 | |||
|
13 | with: | |||
|
14 | python-version: 3.8 | |||
|
15 | - name: Install Graphviz | |||
|
16 | run: | | |||
|
17 | sudo apt-get update | |||
|
18 | sudo apt-get install graphviz | |||
|
19 | - name: Install Python dependencies | |||
|
20 | run: | | |||
|
21 | python -m pip install --upgrade pip setuptools | |||
|
22 | pip install -r docs/requirements.txt | |||
|
23 | - name: Build docs | |||
|
24 | run: | | |||
|
25 | python tools/fixup_whats_new_pr.py | |||
|
26 | make -C docs/ html SPHINXOPTS="-W" |
@@ -0,0 +1,23 b'' | |||||
|
1 | name: Run tests on OSX | |||
|
2 | ||||
|
3 | on: [push, pull_request] | |||
|
4 | ||||
|
5 | jobs: | |||
|
6 | test: | |||
|
7 | runs-on: macos-latest | |||
|
8 | ||||
|
9 | steps: | |||
|
10 | - uses: actions/checkout@v2 | |||
|
11 | - name: Set up Python 3.7 | |||
|
12 | uses: actions/setup-python@v2 | |||
|
13 | with: | |||
|
14 | python-version: 3.7 | |||
|
15 | - name: Install and update Python dependencies | |||
|
16 | run: | | |||
|
17 | python -m pip install --upgrade pip setuptools wheel | |||
|
18 | python -m pip install --upgrade -e file://$PWD#egg=ipython[test] | |||
|
19 | python -m pip install --upgrade --upgrade-strategy eager trio curio | |||
|
20 | python -m pip install --upgrade pytest pytest-trio 'matplotlib!=3.2.0' | |||
|
21 | python -m pip install --upgrade anyio | |||
|
22 | - name: pytest | |||
|
23 | run: pytest |
@@ -0,0 +1,36 b'' | |||||
|
1 | name: Run tests | |||
|
2 | ||||
|
3 | on: [push, pull_request] | |||
|
4 | ||||
|
5 | jobs: | |||
|
6 | test: | |||
|
7 | runs-on: ubuntu-latest | |||
|
8 | strategy: | |||
|
9 | matrix: | |||
|
10 | python-version: [3.7, 3.8, 3.9] | |||
|
11 | ||||
|
12 | steps: | |||
|
13 | - uses: actions/checkout@v2 | |||
|
14 | - name: Set up Python ${{ matrix.python-version }} | |||
|
15 | uses: actions/setup-python@v2 | |||
|
16 | with: | |||
|
17 | python-version: ${{ matrix.python-version }} | |||
|
18 | - name: Install and update Python dependencies | |||
|
19 | run: | | |||
|
20 | python -m pip install --upgrade pip setuptools wheel | |||
|
21 | python -m pip install --upgrade -e file://$PWD#egg=ipython[test] | |||
|
22 | python -m pip install --upgrade --upgrade-strategy eager trio curio | |||
|
23 | python -m pip install --upgrade pytest pytest-trio 'matplotlib!=3.2.0' | |||
|
24 | python -m pip install --upgrade check-manifest pytest-cov anyio | |||
|
25 | - name: Check manifest | |||
|
26 | run: check-manifest | |||
|
27 | - name: iptest | |||
|
28 | run: | | |||
|
29 | cd /tmp && iptest --coverage xml && cd - | |||
|
30 | cp /tmp/ipy_coverage.xml ./ | |||
|
31 | cp /tmp/.coverage ./ | |||
|
32 | - name: pytest | |||
|
33 | run: | | |||
|
34 | pytest | |||
|
35 | - name: Upload coverage to Codecov | |||
|
36 | uses: codecov/codecov-action@v1 |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 | ||
The requested commit or file is too big and content was truncated. Show full diff |
@@ -1,15 +1,21 b'' | |||||
1 | # When making commits that are strictly formatting/style changes, add the |
|
1 | # When making commits that are strictly formatting/style changes, add the | |
2 | # commit hash here, so git blame can ignore the change. See docs for more |
|
2 | # commit hash here, so git blame can ignore the change. See docs for more | |
3 | # details: |
|
3 | # details: | |
4 | # https://git-scm.com/docs/git-config#Documentation/git-config.txt-blameignoreRevsFile |
|
4 | # https://git-scm.com/docs/git-config#Documentation/git-config.txt-blameignoreRevsFile | |
5 | # |
|
5 | # | |
6 | # |
|
6 | # | |
7 | # You should be able to execute either |
|
7 | # You should be able to execute either | |
8 | # ./tools/configure-git-blame-ignore-revs.bat or |
|
8 | # ./tools/configure-git-blame-ignore-revs.bat or | |
9 | # ./tools/configure-git-blame-ignore-revs.sh |
|
9 | # ./tools/configure-git-blame-ignore-revs.sh | |
10 | # |
|
10 | # | |
11 | # Example entries: |
|
11 | # Example entries: | |
12 | # |
|
12 | # | |
13 | # <full commit hash> # initial black-format |
|
13 | # <full commit hash> # initial black-format | |
14 | # <full commit hash> # rename something internal |
|
14 | # <full commit hash> # rename something internal | |
15 | 6e748726282d1acb9a4f9f264ee679c474c4b8f5 # Apply pygrade --36plus on IPython/core/tests/test_inputtransformer.py. |
|
15 | 6e748726282d1acb9a4f9f264ee679c474c4b8f5 # Apply pygrade --36plus on IPython/core/tests/test_inputtransformer.py. | |
|
16 | 0233e65d8086d0ec34acb8685b7a5411633f0899 # apply pyupgrade to IPython/extensions/tests/test_autoreload.py | |||
|
17 | a6a7e4dd7e51b892147895006d3a2a6c34b79ae6 # apply black to IPython/extensions/tests/test_autoreload.py | |||
|
18 | c5ca5a8f25432dfd6b9eccbbe446a8348bf37cfa # apply pyupgrade to IPython/extensions/autoreload.py | |||
|
19 | 50624b84ccdece781750f5eb635a9efbf2fe30d6 # apply black to IPython/extensions/autoreload.py | |||
|
20 | b7aaa47412b96379198705955004930c57f9d74a # apply pyupgrade to IPython/extensions/autoreload.py | |||
|
21 | 9c7476a88af3e567426b412f1b3c778401d8f6aa # apply black to IPython/extensions/autoreload.py |
@@ -1,19 +1,16 b'' | |||||
1 | --- |
|
1 | --- | |
2 | name: Bug report / Question / Feature |
|
2 | name: Bug report / Question / Feature | |
3 | about: Anything related to IPython itsel |
|
3 | about: Anything related to IPython itsel | |
4 | title: '' |
|
4 | title: '' | |
5 | labels: '' |
|
5 | labels: '' | |
6 | assignees: '' |
|
6 | assignees: '' | |
7 |
|
7 | |||
8 | --- |
|
8 | --- | |
9 |
|
9 | |||
10 | <!-- This is the repository for IPython command line, if you can try to make sure this question/bug/feature belong here and not on one of the Jupyter repositories. |
|
10 | <!-- This is the repository for IPython command line, if you can try to make sure this question/bug/feature belong here and not on one of the Jupyter repositories. | |
11 |
|
11 | |||
12 | If it's a generic Python/Jupyter question, try other forums or discourse.jupyter.org. |
|
12 | If it's a generic Python/Jupyter question, try other forums or discourse.jupyter.org. | |
13 |
|
13 | |||
14 | If you are unsure, it's ok to post here, though, there are few maintainer so you might not get a fast response. |
|
14 | If you are unsure, it's ok to post here, though, there are few maintainer so you might not get a fast response. | |
15 |
|
15 | |||
16 | Ability of maintainers to spend time and resources on project like IPython is heavily influenced by US politics, and the current government policies have been harmful to the IPython Maintainers and Community. |
|
|||
17 |
|
||||
18 | If you are on the fence on who to vote for or wether to vote, please cast your vote in for the democrat party in the US. |
|
|||
19 | --> |
|
16 | --> |
@@ -1,33 +1,34 b'' | |||||
1 | name: Run MyPy |
|
1 | name: Run MyPy | |
2 |
|
2 | |||
3 | on: |
|
3 | on: | |
4 | push: |
|
4 | push: | |
5 | branches: [ master ] |
|
5 | branches: [ master, 7.x] | |
6 | pull_request: |
|
6 | pull_request: | |
7 | branches: [ master ] |
|
7 | branches: [ master, 7.x] | |
8 |
|
8 | |||
9 | jobs: |
|
9 | jobs: | |
10 | build: |
|
10 | build: | |
11 |
|
11 | |||
12 | runs-on: ubuntu-latest |
|
12 | runs-on: ubuntu-latest | |
13 | strategy: |
|
13 | strategy: | |
14 | matrix: |
|
14 | matrix: | |
15 | python-version: [3.8] |
|
15 | python-version: [3.8] | |
16 |
|
16 | |||
17 | steps: |
|
17 | steps: | |
18 | - uses: actions/checkout@v2 |
|
18 | - uses: actions/checkout@v2 | |
19 | - name: Set up Python ${{ matrix.python-version }} |
|
19 | - name: Set up Python ${{ matrix.python-version }} | |
20 | uses: actions/setup-python@v2 |
|
20 | uses: actions/setup-python@v2 | |
21 | with: |
|
21 | with: | |
22 | python-version: ${{ matrix.python-version }} |
|
22 | python-version: ${{ matrix.python-version }} | |
23 | - name: Install dependencies |
|
23 | - name: Install dependencies | |
24 | run: | |
|
24 | run: | | |
25 | python -m pip install --upgrade pip |
|
25 | python -m pip install --upgrade pip | |
26 | pip install mypy pyflakes flake8 |
|
26 | pip install mypy pyflakes flake8 | |
27 | - name: Lint with mypy |
|
27 | - name: Lint with mypy | |
28 | run: | |
|
28 | run: | | |
29 |
mypy IPython |
|
29 | mypy -p IPython.terminal | |
30 |
mypy IPython |
|
30 | mypy -p IPython.core.magics | |
31 | - name: Lint with pyflakes |
|
31 | - name: Lint with pyflakes | |
32 | run: | |
|
32 | run: | | |
33 | flake8 IPython/core/magics/script.py |
|
33 | flake8 IPython/core/magics/script.py | |
|
34 | flake8 IPython/core/magics/packaging.py |
@@ -1,39 +1,39 b'' | |||||
1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions |
|
1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions | |
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions |
|
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions | |
3 |
|
3 | |||
4 | name: Python package |
|
4 | name: Python package | |
5 |
|
5 | |||
6 | on: |
|
6 | on: | |
7 | push: |
|
7 | push: | |
8 | branches: [ master ] |
|
8 | branches: [ master, 7.x ] | |
9 | pull_request: |
|
9 | pull_request: | |
10 | branches: [ master ] |
|
10 | branches: [ master, 7.x ] | |
11 |
|
11 | |||
12 | jobs: |
|
12 | jobs: | |
13 | build: |
|
13 | build: | |
14 |
|
14 | |||
15 | runs-on: ubuntu-latest |
|
15 | runs-on: ubuntu-latest | |
16 | strategy: |
|
16 | strategy: | |
17 | matrix: |
|
17 | matrix: | |
18 | python-version: [3.8] |
|
18 | python-version: [3.8] | |
19 |
|
19 | |||
20 | steps: |
|
20 | steps: | |
21 | - uses: actions/checkout@v2 |
|
21 | - uses: actions/checkout@v2 | |
22 | with: |
|
22 | with: | |
23 | fetch-depth: 0 |
|
23 | fetch-depth: 0 | |
24 | - name: Set up Python ${{ matrix.python-version }} |
|
24 | - name: Set up Python ${{ matrix.python-version }} | |
25 | uses: actions/setup-python@v2 |
|
25 | uses: actions/setup-python@v2 | |
26 | with: |
|
26 | with: | |
27 | python-version: ${{ matrix.python-version }} |
|
27 | python-version: ${{ matrix.python-version }} | |
28 | - name: Install dependencies |
|
28 | - name: Install dependencies | |
29 | run: | |
|
29 | run: | | |
30 | python -m pip install --upgrade pip |
|
30 | python -m pip install --upgrade pip | |
31 | pip install darker |
|
31 | pip install darker isort | |
32 | - name: Lint with darker |
|
32 | - name: Lint with darker | |
33 | run: | |
|
33 | run: | | |
34 | darker -r 60625f241f298b5039cb2debc365db38aa7bb522 --check --diff . || ( |
|
34 | darker -r 60625f241f298b5039cb2debc365db38aa7bb522 --check --diff . || ( | |
35 | echo "Changes need auto-formatting. Run:" |
|
35 | echo "Changes need auto-formatting. Run:" | |
36 | echo " darker -r 60625f241f298b5039cb2debc365db38aa7bb522" |
|
36 | echo " darker -r 60625f241f298b5039cb2debc365db38aa7bb522" | |
37 | echo "then commit and push changes to fix." |
|
37 | echo "then commit and push changes to fix." | |
38 | exit 1 |
|
38 | exit 1 | |
39 | ) |
|
39 | ) |
@@ -1,30 +1,32 b'' | |||||
1 | MANIFEST |
|
1 | MANIFEST | |
2 | build |
|
2 | build | |
3 | dist |
|
3 | dist | |
4 | _build |
|
4 | _build | |
5 | docs/man/*.gz |
|
5 | docs/man/*.gz | |
6 | docs/source/api/generated |
|
6 | docs/source/api/generated | |
7 | docs/source/config/options |
|
7 | docs/source/config/options | |
8 | docs/source/config/shortcuts/*.csv |
|
8 | docs/source/config/shortcuts/*.csv | |
|
9 | docs/source/savefig | |||
9 | docs/source/interactive/magics-generated.txt |
|
10 | docs/source/interactive/magics-generated.txt | |
10 | docs/gh-pages |
|
11 | docs/gh-pages | |
11 | jupyter_notebook/notebook/static/mathjax |
|
12 | jupyter_notebook/notebook/static/mathjax | |
12 | jupyter_notebook/static/style/*.map |
|
13 | jupyter_notebook/static/style/*.map | |
13 | *.py[co] |
|
14 | *.py[co] | |
14 | __pycache__ |
|
15 | __pycache__ | |
15 | *.egg-info |
|
16 | *.egg-info | |
16 | *~ |
|
17 | *~ | |
17 | *.bak |
|
18 | *.bak | |
18 | .ipynb_checkpoints |
|
19 | .ipynb_checkpoints | |
19 | .tox |
|
20 | .tox | |
20 | .DS_Store |
|
21 | .DS_Store | |
21 | \#*# |
|
22 | \#*# | |
22 | .#* |
|
23 | .#* | |
23 | .cache |
|
24 | .cache | |
24 | .coverage |
|
25 | .coverage | |
25 | *.swp |
|
26 | *.swp | |
26 | .vscode |
|
27 | .vscode | |
27 | .pytest_cache |
|
28 | .pytest_cache | |
28 | .python-version |
|
29 | .python-version | |
29 | venv*/ |
|
30 | venv*/ | |
30 | .idea/ |
|
31 | .idea/ | |
|
32 | .mypy_cache/ |
@@ -1,152 +1,149 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
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 os |
|
22 | import os | |
23 | import sys |
|
23 | import sys | |
24 |
|
24 | |||
25 | #----------------------------------------------------------------------------- |
|
25 | #----------------------------------------------------------------------------- | |
26 | # Setup everything |
|
26 | # Setup everything | |
27 | #----------------------------------------------------------------------------- |
|
27 | #----------------------------------------------------------------------------- | |
28 |
|
28 | |||
29 | # Don't forget to also update setup.py when this changes! |
|
29 | # Don't forget to also update setup.py when this changes! | |
30 | if sys.version_info < (3, 6): |
|
30 | if sys.version_info < (3, 6): | |
31 | raise ImportError( |
|
31 | raise ImportError( | |
32 | """ |
|
32 | """ | |
33 | IPython 7.10+ supports Python 3.6 and above. |
|
33 | IPython 7.10+ supports Python 3.6 and above. | |
34 | When using Python 2.7, please install IPython 5.x LTS Long Term Support version. |
|
34 | When using Python 2.7, please install IPython 5.x LTS Long Term Support version. | |
35 | Python 3.3 and 3.4 were supported up to IPython 6.x. |
|
35 | Python 3.3 and 3.4 were supported up to IPython 6.x. | |
36 | Python 3.5 was supported with IPython 7.0 to 7.9. |
|
36 | Python 3.5 was supported with IPython 7.0 to 7.9. | |
37 |
|
37 | |||
38 | See IPython `README.rst` file for more information: |
|
38 | See IPython `README.rst` file for more information: | |
39 |
|
39 | |||
40 | https://github.com/ipython/ipython/blob/master/README.rst |
|
40 | https://github.com/ipython/ipython/blob/master/README.rst | |
41 |
|
41 | |||
42 | """) |
|
42 | """) | |
43 |
|
43 | |||
44 | # Make it easy to import extensions - they are always directly on pythonpath. |
|
44 | # Make it easy to import extensions - they are always directly on pythonpath. | |
45 | # Therefore, non-IPython modules can be added to extensions directory. |
|
45 | # Therefore, non-IPython modules can be added to extensions directory. | |
46 | # This should probably be in ipapp.py. |
|
46 | # This should probably be in ipapp.py. | |
47 | sys.path.append(os.path.join(os.path.dirname(__file__), "extensions")) |
|
47 | sys.path.append(os.path.join(os.path.dirname(__file__), "extensions")) | |
48 |
|
48 | |||
49 | #----------------------------------------------------------------------------- |
|
49 | #----------------------------------------------------------------------------- | |
50 | # Setup the top level names |
|
50 | # Setup the top level names | |
51 | #----------------------------------------------------------------------------- |
|
51 | #----------------------------------------------------------------------------- | |
52 |
|
52 | |||
53 | from .core.getipython import get_ipython |
|
53 | from .core.getipython import get_ipython | |
54 | from .core import release |
|
54 | from .core import release | |
55 | from .core.application import Application |
|
55 | from .core.application import Application | |
56 | from .terminal.embed import embed |
|
56 | from .terminal.embed import embed | |
57 |
|
57 | |||
58 | from .core.interactiveshell import InteractiveShell |
|
58 | from .core.interactiveshell import InteractiveShell | |
59 | from .testing import test |
|
59 | from .testing import test | |
60 | from .utils.sysinfo import sys_info |
|
60 | from .utils.sysinfo import sys_info | |
61 | from .utils.frame import extract_module_locals |
|
61 | from .utils.frame import extract_module_locals | |
62 |
|
62 | |||
63 | # Release data |
|
63 | # Release data | |
64 | __author__ = '%s <%s>' % (release.author, release.author_email) |
|
64 | __author__ = '%s <%s>' % (release.author, release.author_email) | |
65 | __license__ = release.license |
|
65 | __license__ = release.license | |
66 | __version__ = release.version |
|
66 | __version__ = release.version | |
67 | version_info = release.version_info |
|
67 | version_info = release.version_info | |
68 |
|
68 | |||
69 | def embed_kernel(module=None, local_ns=None, **kwargs): |
|
69 | def embed_kernel(module=None, local_ns=None, **kwargs): | |
70 | """Embed and start an IPython kernel in a given scope. |
|
70 | """Embed and start an IPython kernel in a given scope. | |
71 |
|
71 | |||
72 | If you don't want the kernel to initialize the namespace |
|
72 | If you don't want the kernel to initialize the namespace | |
73 | from the scope of the surrounding function, |
|
73 | from the scope of the surrounding function, | |
74 | and/or you want to load full IPython configuration, |
|
74 | and/or you want to load full IPython configuration, | |
75 | you probably want `IPython.start_kernel()` instead. |
|
75 | you probably want `IPython.start_kernel()` instead. | |
76 |
|
76 | |||
77 | Parameters |
|
77 | Parameters | |
78 | ---------- |
|
78 | ---------- | |
79 | module : types.ModuleType, optional |
|
79 | module : types.ModuleType, optional | |
80 | The module to load into IPython globals (default: caller) |
|
80 | The module to load into IPython globals (default: caller) | |
81 | local_ns : dict, optional |
|
81 | local_ns : dict, optional | |
82 | The namespace to load into IPython user namespace (default: caller) |
|
82 | The namespace to load into IPython user namespace (default: caller) | |
83 |
|
83 | **kwargs : various, optional | ||
84 | kwargs : various, optional |
|
|||
85 | Further keyword args are relayed to the IPKernelApp constructor, |
|
84 | Further keyword args are relayed to the IPKernelApp constructor, | |
86 | allowing configuration of the Kernel. Will only have an effect |
|
85 | allowing configuration of the Kernel. Will only have an effect | |
87 | on the first embed_kernel call for a given process. |
|
86 | on the first embed_kernel call for a given process. | |
88 | """ |
|
87 | """ | |
89 |
|
88 | |||
90 | (caller_module, caller_locals) = extract_module_locals(1) |
|
89 | (caller_module, caller_locals) = extract_module_locals(1) | |
91 | if module is None: |
|
90 | if module is None: | |
92 | module = caller_module |
|
91 | module = caller_module | |
93 | if local_ns is None: |
|
92 | if local_ns is None: | |
94 | local_ns = caller_locals |
|
93 | local_ns = caller_locals | |
95 |
|
94 | |||
96 | # Only import .zmq when we really need it |
|
95 | # Only import .zmq when we really need it | |
97 | from ipykernel.embed import embed_kernel as real_embed_kernel |
|
96 | from ipykernel.embed import embed_kernel as real_embed_kernel | |
98 | real_embed_kernel(module=module, local_ns=local_ns, **kwargs) |
|
97 | real_embed_kernel(module=module, local_ns=local_ns, **kwargs) | |
99 |
|
98 | |||
100 | def start_ipython(argv=None, **kwargs): |
|
99 | def start_ipython(argv=None, **kwargs): | |
101 | """Launch a normal IPython instance (as opposed to embedded) |
|
100 | """Launch a normal IPython instance (as opposed to embedded) | |
102 |
|
101 | |||
103 | `IPython.embed()` puts a shell in a particular calling scope, |
|
102 | `IPython.embed()` puts a shell in a particular calling scope, | |
104 | such as a function or method for debugging purposes, |
|
103 | such as a function or method for debugging purposes, | |
105 | which is often not desirable. |
|
104 | which is often not desirable. | |
106 |
|
105 | |||
107 | `start_ipython()` does full, regular IPython initialization, |
|
106 | `start_ipython()` does full, regular IPython initialization, | |
108 | including loading startup files, configuration, etc. |
|
107 | including loading startup files, configuration, etc. | |
109 | much of which is skipped by `embed()`. |
|
108 | much of which is skipped by `embed()`. | |
110 |
|
109 | |||
111 | This is a public API method, and will survive implementation changes. |
|
110 | This is a public API method, and will survive implementation changes. | |
112 |
|
111 | |||
113 | Parameters |
|
112 | Parameters | |
114 | ---------- |
|
113 | ---------- | |
115 |
|
||||
116 | argv : list or None, optional |
|
114 | argv : list or None, optional | |
117 | If unspecified or None, IPython will parse command-line options from sys.argv. |
|
115 | If unspecified or None, IPython will parse command-line options from sys.argv. | |
118 | To prevent any command-line parsing, pass an empty list: `argv=[]`. |
|
116 | To prevent any command-line parsing, pass an empty list: `argv=[]`. | |
119 | user_ns : dict, optional |
|
117 | user_ns : dict, optional | |
120 | specify this dictionary to initialize the IPython user namespace with particular values. |
|
118 | specify this dictionary to initialize the IPython user namespace with particular values. | |
121 | kwargs : various, optional |
|
119 | **kwargs : various, optional | |
122 | Any other kwargs will be passed to the Application constructor, |
|
120 | Any other kwargs will be passed to the Application constructor, | |
123 | such as `config`. |
|
121 | such as `config`. | |
124 | """ |
|
122 | """ | |
125 | from IPython.terminal.ipapp import launch_new_instance |
|
123 | from IPython.terminal.ipapp import launch_new_instance | |
126 | return launch_new_instance(argv=argv, **kwargs) |
|
124 | return launch_new_instance(argv=argv, **kwargs) | |
127 |
|
125 | |||
128 | def start_kernel(argv=None, **kwargs): |
|
126 | def start_kernel(argv=None, **kwargs): | |
129 | """Launch a normal IPython kernel instance (as opposed to embedded) |
|
127 | """Launch a normal IPython kernel instance (as opposed to embedded) | |
130 |
|
128 | |||
131 | `IPython.embed_kernel()` puts a shell in a particular calling scope, |
|
129 | `IPython.embed_kernel()` puts a shell in a particular calling scope, | |
132 | such as a function or method for debugging purposes, |
|
130 | such as a function or method for debugging purposes, | |
133 | which is often not desirable. |
|
131 | which is often not desirable. | |
134 |
|
132 | |||
135 | `start_kernel()` does full, regular IPython initialization, |
|
133 | `start_kernel()` does full, regular IPython initialization, | |
136 | including loading startup files, configuration, etc. |
|
134 | including loading startup files, configuration, etc. | |
137 | much of which is skipped by `embed()`. |
|
135 | much of which is skipped by `embed()`. | |
138 |
|
136 | |||
139 | Parameters |
|
137 | Parameters | |
140 | ---------- |
|
138 | ---------- | |
141 |
|
||||
142 | argv : list or None, optional |
|
139 | argv : list or None, optional | |
143 | If unspecified or None, IPython will parse command-line options from sys.argv. |
|
140 | If unspecified or None, IPython will parse command-line options from sys.argv. | |
144 | To prevent any command-line parsing, pass an empty list: `argv=[]`. |
|
141 | To prevent any command-line parsing, pass an empty list: `argv=[]`. | |
145 | user_ns : dict, optional |
|
142 | user_ns : dict, optional | |
146 | specify this dictionary to initialize the IPython user namespace with particular values. |
|
143 | specify this dictionary to initialize the IPython user namespace with particular values. | |
147 | kwargs : various, optional |
|
144 | **kwargs : various, optional | |
148 | Any other kwargs will be passed to the Application constructor, |
|
145 | Any other kwargs will be passed to the Application constructor, | |
149 | such as `config`. |
|
146 | such as `config`. | |
150 | """ |
|
147 | """ | |
151 | from IPython.kernel.zmq.kernelapp import launch_new_instance |
|
148 | from IPython.kernel.zmq.kernelapp import launch_new_instance | |
152 | return launch_new_instance(argv=argv, **kwargs) |
|
149 | return launch_new_instance(argv=argv, **kwargs) |
@@ -1,464 +1,486 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | An application for IPython. |
|
3 | An application for IPython. | |
4 |
|
4 | |||
5 | All top-level applications should use the classes in this module for |
|
5 | All top-level applications should use the classes in this module for | |
6 | handling configuration and creating configurables. |
|
6 | handling configuration and creating configurables. | |
7 |
|
7 | |||
8 | The job of an :class:`Application` is to create the master configuration |
|
8 | The job of an :class:`Application` is to create the master configuration | |
9 | object and then create the configurable objects, passing the config to them. |
|
9 | object and then create the configurable objects, passing the config to them. | |
10 | """ |
|
10 | """ | |
11 |
|
11 | |||
12 | # Copyright (c) IPython Development Team. |
|
12 | # Copyright (c) IPython Development Team. | |
13 | # Distributed under the terms of the Modified BSD License. |
|
13 | # Distributed under the terms of the Modified BSD License. | |
14 |
|
14 | |||
15 | import atexit |
|
15 | import atexit | |
16 | from copy import deepcopy |
|
16 | from copy import deepcopy | |
17 | import glob |
|
17 | import glob | |
18 | import logging |
|
18 | import logging | |
19 | import os |
|
19 | import os | |
20 | import shutil |
|
20 | import shutil | |
21 | import sys |
|
21 | import sys | |
22 |
|
22 | |||
23 | from pathlib import Path |
|
23 | from pathlib import Path | |
24 |
|
24 | |||
25 | from traitlets.config.application import Application, catch_config_error |
|
25 | from traitlets.config.application import Application, catch_config_error | |
26 | from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader |
|
26 | from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader | |
27 | from IPython.core import release, crashhandler |
|
27 | from IPython.core import release, crashhandler | |
28 | from IPython.core.profiledir import ProfileDir, ProfileDirError |
|
28 | from IPython.core.profiledir import ProfileDir, ProfileDirError | |
29 | from IPython.paths import get_ipython_dir, get_ipython_package_dir |
|
29 | from IPython.paths import get_ipython_dir, get_ipython_package_dir | |
30 | from IPython.utils.path import ensure_dir_exists |
|
30 | from IPython.utils.path import ensure_dir_exists | |
31 | from traitlets import ( |
|
31 | from traitlets import ( | |
32 | List, Unicode, Type, Bool, Set, Instance, Undefined, |
|
32 | List, Unicode, Type, Bool, Set, Instance, Undefined, | |
33 | default, observe, |
|
33 | default, observe, | |
34 | ) |
|
34 | ) | |
35 |
|
35 | |||
36 | if os.name == "nt": |
|
36 | if os.name == "nt": | |
37 | programdata = Path(os.environ.get("PROGRAMDATA", None)) |
|
37 | programdata = Path(os.environ.get("PROGRAMDATA", None)) | |
38 | if programdata: |
|
38 | if programdata: | |
39 | SYSTEM_CONFIG_DIRS = [str(programdata / "ipython")] |
|
39 | SYSTEM_CONFIG_DIRS = [str(programdata / "ipython")] | |
40 | else: # PROGRAMDATA is not defined by default on XP. |
|
40 | else: # PROGRAMDATA is not defined by default on XP. | |
41 | SYSTEM_CONFIG_DIRS = [] |
|
41 | SYSTEM_CONFIG_DIRS = [] | |
42 | else: |
|
42 | else: | |
43 | SYSTEM_CONFIG_DIRS = [ |
|
43 | SYSTEM_CONFIG_DIRS = [ | |
44 | "/usr/local/etc/ipython", |
|
44 | "/usr/local/etc/ipython", | |
45 | "/etc/ipython", |
|
45 | "/etc/ipython", | |
46 | ] |
|
46 | ] | |
47 |
|
47 | |||
48 |
|
48 | |||
49 | ENV_CONFIG_DIRS = [] |
|
49 | ENV_CONFIG_DIRS = [] | |
50 | _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython') |
|
50 | _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython') | |
51 | if _env_config_dir not in SYSTEM_CONFIG_DIRS: |
|
51 | if _env_config_dir not in SYSTEM_CONFIG_DIRS: | |
52 | # only add ENV_CONFIG if sys.prefix is not already included |
|
52 | # only add ENV_CONFIG if sys.prefix is not already included | |
53 | ENV_CONFIG_DIRS.append(_env_config_dir) |
|
53 | ENV_CONFIG_DIRS.append(_env_config_dir) | |
54 |
|
54 | |||
55 |
|
55 | |||
56 | _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS') |
|
56 | _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS') | |
57 | if _envvar in {None, ''}: |
|
57 | if _envvar in {None, ''}: | |
58 | IPYTHON_SUPPRESS_CONFIG_ERRORS = None |
|
58 | IPYTHON_SUPPRESS_CONFIG_ERRORS = None | |
59 | else: |
|
59 | else: | |
60 | if _envvar.lower() in {'1','true'}: |
|
60 | if _envvar.lower() in {'1','true'}: | |
61 | IPYTHON_SUPPRESS_CONFIG_ERRORS = True |
|
61 | IPYTHON_SUPPRESS_CONFIG_ERRORS = True | |
62 | elif _envvar.lower() in {'0','false'} : |
|
62 | elif _envvar.lower() in {'0','false'} : | |
63 | IPYTHON_SUPPRESS_CONFIG_ERRORS = False |
|
63 | IPYTHON_SUPPRESS_CONFIG_ERRORS = False | |
64 | else: |
|
64 | else: | |
65 | sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar ) |
|
65 | sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar ) | |
66 |
|
66 | |||
67 | # aliases and flags |
|
67 | # aliases and flags | |
68 |
|
68 | |||
69 | base_aliases = { |
|
69 | base_aliases = {} | |
70 | 'profile-dir' : 'ProfileDir.location', |
|
70 | if isinstance(Application.aliases, dict): | |
71 | 'profile' : 'BaseIPythonApplication.profile', |
|
71 | # traitlets 5 | |
72 | 'ipython-dir' : 'BaseIPythonApplication.ipython_dir', |
|
72 | base_aliases.update(Application.aliases) | |
73 | 'log-level' : 'Application.log_level', |
|
73 | base_aliases.update( | |
74 | 'config' : 'BaseIPythonApplication.extra_config_file', |
|
74 | { | |
75 | } |
|
75 | "profile-dir": "ProfileDir.location", | |
76 |
|
76 | "profile": "BaseIPythonApplication.profile", | ||
77 | base_flags = dict( |
|
77 | "ipython-dir": "BaseIPythonApplication.ipython_dir", | |
78 | debug = ({'Application' : {'log_level' : logging.DEBUG}}, |
|
78 | "log-level": "Application.log_level", | |
79 | "set log level to logging.DEBUG (maximize logging output)"), |
|
79 | "config": "BaseIPythonApplication.extra_config_file", | |
80 | quiet = ({'Application' : {'log_level' : logging.CRITICAL}}, |
|
80 | } | |
81 | "set log level to logging.CRITICAL (minimize logging output)"), |
|
81 | ) | |
82 | init = ({'BaseIPythonApplication' : { |
|
82 | ||
83 | 'copy_config_files' : True, |
|
83 | base_flags = dict() | |
84 | 'auto_create' : True} |
|
84 | if isinstance(Application.flags, dict): | |
85 | }, """Initialize profile with default config files. This is equivalent |
|
85 | # traitlets 5 | |
|
86 | base_flags.update(Application.flags) | |||
|
87 | base_flags.update( | |||
|
88 | dict( | |||
|
89 | debug=( | |||
|
90 | {"Application": {"log_level": logging.DEBUG}}, | |||
|
91 | "set log level to logging.DEBUG (maximize logging output)", | |||
|
92 | ), | |||
|
93 | quiet=( | |||
|
94 | {"Application": {"log_level": logging.CRITICAL}}, | |||
|
95 | "set log level to logging.CRITICAL (minimize logging output)", | |||
|
96 | ), | |||
|
97 | init=( | |||
|
98 | { | |||
|
99 | "BaseIPythonApplication": { | |||
|
100 | "copy_config_files": True, | |||
|
101 | "auto_create": True, | |||
|
102 | } | |||
|
103 | }, | |||
|
104 | """Initialize profile with default config files. This is equivalent | |||
86 |
|
|
105 | to running `ipython profile create <profile>` prior to startup. | |
87 |
|
|
106 | """, | |
|
107 | ), | |||
|
108 | ) | |||
88 | ) |
|
109 | ) | |
89 |
|
110 | |||
|
111 | ||||
90 | class ProfileAwareConfigLoader(PyFileConfigLoader): |
|
112 | class ProfileAwareConfigLoader(PyFileConfigLoader): | |
91 | """A Python file config loader that is aware of IPython profiles.""" |
|
113 | """A Python file config loader that is aware of IPython profiles.""" | |
92 | def load_subconfig(self, fname, path=None, profile=None): |
|
114 | def load_subconfig(self, fname, path=None, profile=None): | |
93 | if profile is not None: |
|
115 | if profile is not None: | |
94 | try: |
|
116 | try: | |
95 | profile_dir = ProfileDir.find_profile_dir_by_name( |
|
117 | profile_dir = ProfileDir.find_profile_dir_by_name( | |
96 | get_ipython_dir(), |
|
118 | get_ipython_dir(), | |
97 | profile, |
|
119 | profile, | |
98 | ) |
|
120 | ) | |
99 | except ProfileDirError: |
|
121 | except ProfileDirError: | |
100 | return |
|
122 | return | |
101 | path = profile_dir.location |
|
123 | path = profile_dir.location | |
102 | return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path) |
|
124 | return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path) | |
103 |
|
125 | |||
104 | class BaseIPythonApplication(Application): |
|
126 | class BaseIPythonApplication(Application): | |
105 |
|
127 | |||
106 | name = u'ipython' |
|
128 | name = u'ipython' | |
107 | description = Unicode(u'IPython: an enhanced interactive Python shell.') |
|
129 | description = Unicode(u'IPython: an enhanced interactive Python shell.') | |
108 | version = Unicode(release.version) |
|
130 | version = Unicode(release.version) | |
109 |
|
131 | |||
110 | aliases = base_aliases |
|
132 | aliases = base_aliases | |
111 | flags = base_flags |
|
133 | flags = base_flags | |
112 | classes = List([ProfileDir]) |
|
134 | classes = List([ProfileDir]) | |
113 |
|
135 | |||
114 | # enable `load_subconfig('cfg.py', profile='name')` |
|
136 | # enable `load_subconfig('cfg.py', profile='name')` | |
115 | python_config_loader_class = ProfileAwareConfigLoader |
|
137 | python_config_loader_class = ProfileAwareConfigLoader | |
116 |
|
138 | |||
117 | # Track whether the config_file has changed, |
|
139 | # Track whether the config_file has changed, | |
118 | # because some logic happens only if we aren't using the default. |
|
140 | # because some logic happens only if we aren't using the default. | |
119 | config_file_specified = Set() |
|
141 | config_file_specified = Set() | |
120 |
|
142 | |||
121 | config_file_name = Unicode() |
|
143 | config_file_name = Unicode() | |
122 | @default('config_file_name') |
|
144 | @default('config_file_name') | |
123 | def _config_file_name_default(self): |
|
145 | def _config_file_name_default(self): | |
124 | return self.name.replace('-','_') + u'_config.py' |
|
146 | return self.name.replace('-','_') + u'_config.py' | |
125 | @observe('config_file_name') |
|
147 | @observe('config_file_name') | |
126 | def _config_file_name_changed(self, change): |
|
148 | def _config_file_name_changed(self, change): | |
127 | if change['new'] != change['old']: |
|
149 | if change['new'] != change['old']: | |
128 | self.config_file_specified.add(change['new']) |
|
150 | self.config_file_specified.add(change['new']) | |
129 |
|
151 | |||
130 | # The directory that contains IPython's builtin profiles. |
|
152 | # The directory that contains IPython's builtin profiles. | |
131 | builtin_profile_dir = Unicode( |
|
153 | builtin_profile_dir = Unicode( | |
132 | os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') |
|
154 | os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') | |
133 | ) |
|
155 | ) | |
134 |
|
156 | |||
135 | config_file_paths = List(Unicode()) |
|
157 | config_file_paths = List(Unicode()) | |
136 | @default('config_file_paths') |
|
158 | @default('config_file_paths') | |
137 | def _config_file_paths_default(self): |
|
159 | def _config_file_paths_default(self): | |
138 | return [os.getcwd()] |
|
160 | return [os.getcwd()] | |
139 |
|
161 | |||
140 | extra_config_file = Unicode( |
|
162 | extra_config_file = Unicode( | |
141 | help="""Path to an extra config file to load. |
|
163 | help="""Path to an extra config file to load. | |
142 |
|
164 | |||
143 | If specified, load this config file in addition to any other IPython config. |
|
165 | If specified, load this config file in addition to any other IPython config. | |
144 | """).tag(config=True) |
|
166 | """).tag(config=True) | |
145 | @observe('extra_config_file') |
|
167 | @observe('extra_config_file') | |
146 | def _extra_config_file_changed(self, change): |
|
168 | def _extra_config_file_changed(self, change): | |
147 | old = change['old'] |
|
169 | old = change['old'] | |
148 | new = change['new'] |
|
170 | new = change['new'] | |
149 | try: |
|
171 | try: | |
150 | self.config_files.remove(old) |
|
172 | self.config_files.remove(old) | |
151 | except ValueError: |
|
173 | except ValueError: | |
152 | pass |
|
174 | pass | |
153 | self.config_file_specified.add(new) |
|
175 | self.config_file_specified.add(new) | |
154 | self.config_files.append(new) |
|
176 | self.config_files.append(new) | |
155 |
|
177 | |||
156 | profile = Unicode(u'default', |
|
178 | profile = Unicode(u'default', | |
157 | help="""The IPython profile to use.""" |
|
179 | help="""The IPython profile to use.""" | |
158 | ).tag(config=True) |
|
180 | ).tag(config=True) | |
159 |
|
181 | |||
160 | @observe('profile') |
|
182 | @observe('profile') | |
161 | def _profile_changed(self, change): |
|
183 | def _profile_changed(self, change): | |
162 | self.builtin_profile_dir = os.path.join( |
|
184 | self.builtin_profile_dir = os.path.join( | |
163 | get_ipython_package_dir(), u'config', u'profile', change['new'] |
|
185 | get_ipython_package_dir(), u'config', u'profile', change['new'] | |
164 | ) |
|
186 | ) | |
165 |
|
187 | |||
166 | ipython_dir = Unicode( |
|
188 | ipython_dir = Unicode( | |
167 | help=""" |
|
189 | help=""" | |
168 | The name of the IPython directory. This directory is used for logging |
|
190 | The name of the IPython directory. This directory is used for logging | |
169 | configuration (through profiles), history storage, etc. The default |
|
191 | configuration (through profiles), history storage, etc. The default | |
170 | is usually $HOME/.ipython. This option can also be specified through |
|
192 | is usually $HOME/.ipython. This option can also be specified through | |
171 | the environment variable IPYTHONDIR. |
|
193 | the environment variable IPYTHONDIR. | |
172 | """ |
|
194 | """ | |
173 | ).tag(config=True) |
|
195 | ).tag(config=True) | |
174 | @default('ipython_dir') |
|
196 | @default('ipython_dir') | |
175 | def _ipython_dir_default(self): |
|
197 | def _ipython_dir_default(self): | |
176 | d = get_ipython_dir() |
|
198 | d = get_ipython_dir() | |
177 | self._ipython_dir_changed({ |
|
199 | self._ipython_dir_changed({ | |
178 | 'name': 'ipython_dir', |
|
200 | 'name': 'ipython_dir', | |
179 | 'old': d, |
|
201 | 'old': d, | |
180 | 'new': d, |
|
202 | 'new': d, | |
181 | }) |
|
203 | }) | |
182 | return d |
|
204 | return d | |
183 |
|
205 | |||
184 | _in_init_profile_dir = False |
|
206 | _in_init_profile_dir = False | |
185 | profile_dir = Instance(ProfileDir, allow_none=True) |
|
207 | profile_dir = Instance(ProfileDir, allow_none=True) | |
186 | @default('profile_dir') |
|
208 | @default('profile_dir') | |
187 | def _profile_dir_default(self): |
|
209 | def _profile_dir_default(self): | |
188 | # avoid recursion |
|
210 | # avoid recursion | |
189 | if self._in_init_profile_dir: |
|
211 | if self._in_init_profile_dir: | |
190 | return |
|
212 | return | |
191 | # profile_dir requested early, force initialization |
|
213 | # profile_dir requested early, force initialization | |
192 | self.init_profile_dir() |
|
214 | self.init_profile_dir() | |
193 | return self.profile_dir |
|
215 | return self.profile_dir | |
194 |
|
216 | |||
195 | overwrite = Bool(False, |
|
217 | overwrite = Bool(False, | |
196 | help="""Whether to overwrite existing config files when copying""" |
|
218 | help="""Whether to overwrite existing config files when copying""" | |
197 | ).tag(config=True) |
|
219 | ).tag(config=True) | |
198 | auto_create = Bool(False, |
|
220 | auto_create = Bool(False, | |
199 | help="""Whether to create profile dir if it doesn't exist""" |
|
221 | help="""Whether to create profile dir if it doesn't exist""" | |
200 | ).tag(config=True) |
|
222 | ).tag(config=True) | |
201 |
|
223 | |||
202 | config_files = List(Unicode()) |
|
224 | config_files = List(Unicode()) | |
203 | @default('config_files') |
|
225 | @default('config_files') | |
204 | def _config_files_default(self): |
|
226 | def _config_files_default(self): | |
205 | return [self.config_file_name] |
|
227 | return [self.config_file_name] | |
206 |
|
228 | |||
207 | copy_config_files = Bool(False, |
|
229 | copy_config_files = Bool(False, | |
208 | help="""Whether to install the default config files into the profile dir. |
|
230 | help="""Whether to install the default config files into the profile dir. | |
209 | If a new profile is being created, and IPython contains config files for that |
|
231 | If a new profile is being created, and IPython contains config files for that | |
210 | profile, then they will be staged into the new directory. Otherwise, |
|
232 | profile, then they will be staged into the new directory. Otherwise, | |
211 | default config files will be automatically generated. |
|
233 | default config files will be automatically generated. | |
212 | """).tag(config=True) |
|
234 | """).tag(config=True) | |
213 |
|
235 | |||
214 | verbose_crash = Bool(False, |
|
236 | verbose_crash = Bool(False, | |
215 | help="""Create a massive crash report when IPython encounters what may be an |
|
237 | help="""Create a massive crash report when IPython encounters what may be an | |
216 | internal error. The default is to append a short message to the |
|
238 | internal error. The default is to append a short message to the | |
217 | usual traceback""").tag(config=True) |
|
239 | usual traceback""").tag(config=True) | |
218 |
|
240 | |||
219 | # The class to use as the crash handler. |
|
241 | # The class to use as the crash handler. | |
220 | crash_handler_class = Type(crashhandler.CrashHandler) |
|
242 | crash_handler_class = Type(crashhandler.CrashHandler) | |
221 |
|
243 | |||
222 | @catch_config_error |
|
244 | @catch_config_error | |
223 | def __init__(self, **kwargs): |
|
245 | def __init__(self, **kwargs): | |
224 | super(BaseIPythonApplication, self).__init__(**kwargs) |
|
246 | super(BaseIPythonApplication, self).__init__(**kwargs) | |
225 | # ensure current working directory exists |
|
247 | # ensure current working directory exists | |
226 | try: |
|
248 | try: | |
227 | os.getcwd() |
|
249 | os.getcwd() | |
228 | except: |
|
250 | except: | |
229 | # exit if cwd doesn't exist |
|
251 | # exit if cwd doesn't exist | |
230 | self.log.error("Current working directory doesn't exist.") |
|
252 | self.log.error("Current working directory doesn't exist.") | |
231 | self.exit(1) |
|
253 | self.exit(1) | |
232 |
|
254 | |||
233 | #------------------------------------------------------------------------- |
|
255 | #------------------------------------------------------------------------- | |
234 | # Various stages of Application creation |
|
256 | # Various stages of Application creation | |
235 | #------------------------------------------------------------------------- |
|
257 | #------------------------------------------------------------------------- | |
236 |
|
258 | |||
237 | deprecated_subcommands = {} |
|
259 | deprecated_subcommands = {} | |
238 |
|
260 | |||
239 | def initialize_subcommand(self, subc, argv=None): |
|
261 | def initialize_subcommand(self, subc, argv=None): | |
240 | if subc in self.deprecated_subcommands: |
|
262 | if subc in self.deprecated_subcommands: | |
241 | self.log.warning("Subcommand `ipython {sub}` is deprecated and will be removed " |
|
263 | self.log.warning("Subcommand `ipython {sub}` is deprecated and will be removed " | |
242 | "in future versions.".format(sub=subc)) |
|
264 | "in future versions.".format(sub=subc)) | |
243 | self.log.warning("You likely want to use `jupyter {sub}` in the " |
|
265 | self.log.warning("You likely want to use `jupyter {sub}` in the " | |
244 | "future".format(sub=subc)) |
|
266 | "future".format(sub=subc)) | |
245 | return super(BaseIPythonApplication, self).initialize_subcommand(subc, argv) |
|
267 | return super(BaseIPythonApplication, self).initialize_subcommand(subc, argv) | |
246 |
|
268 | |||
247 | def init_crash_handler(self): |
|
269 | def init_crash_handler(self): | |
248 | """Create a crash handler, typically setting sys.excepthook to it.""" |
|
270 | """Create a crash handler, typically setting sys.excepthook to it.""" | |
249 | self.crash_handler = self.crash_handler_class(self) |
|
271 | self.crash_handler = self.crash_handler_class(self) | |
250 | sys.excepthook = self.excepthook |
|
272 | sys.excepthook = self.excepthook | |
251 | def unset_crashhandler(): |
|
273 | def unset_crashhandler(): | |
252 | sys.excepthook = sys.__excepthook__ |
|
274 | sys.excepthook = sys.__excepthook__ | |
253 | atexit.register(unset_crashhandler) |
|
275 | atexit.register(unset_crashhandler) | |
254 |
|
276 | |||
255 | def excepthook(self, etype, evalue, tb): |
|
277 | def excepthook(self, etype, evalue, tb): | |
256 | """this is sys.excepthook after init_crashhandler |
|
278 | """this is sys.excepthook after init_crashhandler | |
257 |
|
279 | |||
258 | set self.verbose_crash=True to use our full crashhandler, instead of |
|
280 | set self.verbose_crash=True to use our full crashhandler, instead of | |
259 | a regular traceback with a short message (crash_handler_lite) |
|
281 | a regular traceback with a short message (crash_handler_lite) | |
260 | """ |
|
282 | """ | |
261 |
|
283 | |||
262 | if self.verbose_crash: |
|
284 | if self.verbose_crash: | |
263 | return self.crash_handler(etype, evalue, tb) |
|
285 | return self.crash_handler(etype, evalue, tb) | |
264 | else: |
|
286 | else: | |
265 | return crashhandler.crash_handler_lite(etype, evalue, tb) |
|
287 | return crashhandler.crash_handler_lite(etype, evalue, tb) | |
266 |
|
288 | |||
267 | @observe('ipython_dir') |
|
289 | @observe('ipython_dir') | |
268 | def _ipython_dir_changed(self, change): |
|
290 | def _ipython_dir_changed(self, change): | |
269 | old = change['old'] |
|
291 | old = change['old'] | |
270 | new = change['new'] |
|
292 | new = change['new'] | |
271 | if old is not Undefined: |
|
293 | if old is not Undefined: | |
272 | str_old = os.path.abspath(old) |
|
294 | str_old = os.path.abspath(old) | |
273 | if str_old in sys.path: |
|
295 | if str_old in sys.path: | |
274 | sys.path.remove(str_old) |
|
296 | sys.path.remove(str_old) | |
275 | str_path = os.path.abspath(new) |
|
297 | str_path = os.path.abspath(new) | |
276 | sys.path.append(str_path) |
|
298 | sys.path.append(str_path) | |
277 | ensure_dir_exists(new) |
|
299 | ensure_dir_exists(new) | |
278 | readme = os.path.join(new, 'README') |
|
300 | readme = os.path.join(new, 'README') | |
279 | readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') |
|
301 | readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') | |
280 | if not os.path.exists(readme) and os.path.exists(readme_src): |
|
302 | if not os.path.exists(readme) and os.path.exists(readme_src): | |
281 | shutil.copy(readme_src, readme) |
|
303 | shutil.copy(readme_src, readme) | |
282 | for d in ('extensions', 'nbextensions'): |
|
304 | for d in ('extensions', 'nbextensions'): | |
283 | path = os.path.join(new, d) |
|
305 | path = os.path.join(new, d) | |
284 | try: |
|
306 | try: | |
285 | ensure_dir_exists(path) |
|
307 | ensure_dir_exists(path) | |
286 | except OSError as e: |
|
308 | except OSError as e: | |
287 | # this will not be EEXIST |
|
309 | # this will not be EEXIST | |
288 | self.log.error("couldn't create path %s: %s", path, e) |
|
310 | self.log.error("couldn't create path %s: %s", path, e) | |
289 | self.log.debug("IPYTHONDIR set to: %s" % new) |
|
311 | self.log.debug("IPYTHONDIR set to: %s" % new) | |
290 |
|
312 | |||
291 | def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): |
|
313 | def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): | |
292 | """Load the config file. |
|
314 | """Load the config file. | |
293 |
|
315 | |||
294 | By default, errors in loading config are handled, and a warning |
|
316 | By default, errors in loading config are handled, and a warning | |
295 | printed on screen. For testing, the suppress_errors option is set |
|
317 | printed on screen. For testing, the suppress_errors option is set | |
296 | to False, so errors will make tests fail. |
|
318 | to False, so errors will make tests fail. | |
297 |
|
319 | |||
298 | `suppress_errors` default value is to be `None` in which case the |
|
320 | `suppress_errors` default value is to be `None` in which case the | |
299 | behavior default to the one of `traitlets.Application`. |
|
321 | behavior default to the one of `traitlets.Application`. | |
300 |
|
322 | |||
301 | The default value can be set : |
|
323 | The default value can be set : | |
302 | - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive). |
|
324 | - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive). | |
303 | - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive). |
|
325 | - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive). | |
304 | - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset. |
|
326 | - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset. | |
305 |
|
327 | |||
306 | Any other value are invalid, and will make IPython exit with a non-zero return code. |
|
328 | Any other value are invalid, and will make IPython exit with a non-zero return code. | |
307 | """ |
|
329 | """ | |
308 |
|
330 | |||
309 |
|
331 | |||
310 | self.log.debug("Searching path %s for config files", self.config_file_paths) |
|
332 | self.log.debug("Searching path %s for config files", self.config_file_paths) | |
311 | base_config = 'ipython_config.py' |
|
333 | base_config = 'ipython_config.py' | |
312 | self.log.debug("Attempting to load config file: %s" % |
|
334 | self.log.debug("Attempting to load config file: %s" % | |
313 | base_config) |
|
335 | base_config) | |
314 | try: |
|
336 | try: | |
315 | if suppress_errors is not None: |
|
337 | if suppress_errors is not None: | |
316 | old_value = Application.raise_config_file_errors |
|
338 | old_value = Application.raise_config_file_errors | |
317 | Application.raise_config_file_errors = not suppress_errors; |
|
339 | Application.raise_config_file_errors = not suppress_errors; | |
318 | Application.load_config_file( |
|
340 | Application.load_config_file( | |
319 | self, |
|
341 | self, | |
320 | base_config, |
|
342 | base_config, | |
321 | path=self.config_file_paths |
|
343 | path=self.config_file_paths | |
322 | ) |
|
344 | ) | |
323 | except ConfigFileNotFound: |
|
345 | except ConfigFileNotFound: | |
324 | # ignore errors loading parent |
|
346 | # ignore errors loading parent | |
325 | self.log.debug("Config file %s not found", base_config) |
|
347 | self.log.debug("Config file %s not found", base_config) | |
326 | pass |
|
348 | pass | |
327 | if suppress_errors is not None: |
|
349 | if suppress_errors is not None: | |
328 | Application.raise_config_file_errors = old_value |
|
350 | Application.raise_config_file_errors = old_value | |
329 |
|
351 | |||
330 | for config_file_name in self.config_files: |
|
352 | for config_file_name in self.config_files: | |
331 | if not config_file_name or config_file_name == base_config: |
|
353 | if not config_file_name or config_file_name == base_config: | |
332 | continue |
|
354 | continue | |
333 | self.log.debug("Attempting to load config file: %s" % |
|
355 | self.log.debug("Attempting to load config file: %s" % | |
334 | self.config_file_name) |
|
356 | self.config_file_name) | |
335 | try: |
|
357 | try: | |
336 | Application.load_config_file( |
|
358 | Application.load_config_file( | |
337 | self, |
|
359 | self, | |
338 | config_file_name, |
|
360 | config_file_name, | |
339 | path=self.config_file_paths |
|
361 | path=self.config_file_paths | |
340 | ) |
|
362 | ) | |
341 | except ConfigFileNotFound: |
|
363 | except ConfigFileNotFound: | |
342 | # Only warn if the default config file was NOT being used. |
|
364 | # Only warn if the default config file was NOT being used. | |
343 | if config_file_name in self.config_file_specified: |
|
365 | if config_file_name in self.config_file_specified: | |
344 | msg = self.log.warning |
|
366 | msg = self.log.warning | |
345 | else: |
|
367 | else: | |
346 | msg = self.log.debug |
|
368 | msg = self.log.debug | |
347 | msg("Config file not found, skipping: %s", config_file_name) |
|
369 | msg("Config file not found, skipping: %s", config_file_name) | |
348 | except Exception: |
|
370 | except Exception: | |
349 | # For testing purposes. |
|
371 | # For testing purposes. | |
350 | if not suppress_errors: |
|
372 | if not suppress_errors: | |
351 | raise |
|
373 | raise | |
352 | self.log.warning("Error loading config file: %s" % |
|
374 | self.log.warning("Error loading config file: %s" % | |
353 | self.config_file_name, exc_info=True) |
|
375 | self.config_file_name, exc_info=True) | |
354 |
|
376 | |||
355 | def init_profile_dir(self): |
|
377 | def init_profile_dir(self): | |
356 | """initialize the profile dir""" |
|
378 | """initialize the profile dir""" | |
357 | self._in_init_profile_dir = True |
|
379 | self._in_init_profile_dir = True | |
358 | if self.profile_dir is not None: |
|
380 | if self.profile_dir is not None: | |
359 | # already ran |
|
381 | # already ran | |
360 | return |
|
382 | return | |
361 | if 'ProfileDir.location' not in self.config: |
|
383 | if 'ProfileDir.location' not in self.config: | |
362 | # location not specified, find by profile name |
|
384 | # location not specified, find by profile name | |
363 | try: |
|
385 | try: | |
364 | p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) |
|
386 | p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config) | |
365 | except ProfileDirError: |
|
387 | except ProfileDirError: | |
366 | # not found, maybe create it (always create default profile) |
|
388 | # not found, maybe create it (always create default profile) | |
367 | if self.auto_create or self.profile == 'default': |
|
389 | if self.auto_create or self.profile == 'default': | |
368 | try: |
|
390 | try: | |
369 | p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) |
|
391 | p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config) | |
370 | except ProfileDirError: |
|
392 | except ProfileDirError: | |
371 | self.log.fatal("Could not create profile: %r"%self.profile) |
|
393 | self.log.fatal("Could not create profile: %r"%self.profile) | |
372 | self.exit(1) |
|
394 | self.exit(1) | |
373 | else: |
|
395 | else: | |
374 | self.log.info("Created profile dir: %r"%p.location) |
|
396 | self.log.info("Created profile dir: %r"%p.location) | |
375 | else: |
|
397 | else: | |
376 | self.log.fatal("Profile %r not found."%self.profile) |
|
398 | self.log.fatal("Profile %r not found."%self.profile) | |
377 | self.exit(1) |
|
399 | self.exit(1) | |
378 | else: |
|
400 | else: | |
379 | self.log.debug("Using existing profile dir: %r"%p.location) |
|
401 | self.log.debug("Using existing profile dir: %r"%p.location) | |
380 | else: |
|
402 | else: | |
381 | location = self.config.ProfileDir.location |
|
403 | location = self.config.ProfileDir.location | |
382 | # location is fully specified |
|
404 | # location is fully specified | |
383 | try: |
|
405 | try: | |
384 | p = ProfileDir.find_profile_dir(location, self.config) |
|
406 | p = ProfileDir.find_profile_dir(location, self.config) | |
385 | except ProfileDirError: |
|
407 | except ProfileDirError: | |
386 | # not found, maybe create it |
|
408 | # not found, maybe create it | |
387 | if self.auto_create: |
|
409 | if self.auto_create: | |
388 | try: |
|
410 | try: | |
389 | p = ProfileDir.create_profile_dir(location, self.config) |
|
411 | p = ProfileDir.create_profile_dir(location, self.config) | |
390 | except ProfileDirError: |
|
412 | except ProfileDirError: | |
391 | self.log.fatal("Could not create profile directory: %r"%location) |
|
413 | self.log.fatal("Could not create profile directory: %r"%location) | |
392 | self.exit(1) |
|
414 | self.exit(1) | |
393 | else: |
|
415 | else: | |
394 | self.log.debug("Creating new profile dir: %r"%location) |
|
416 | self.log.debug("Creating new profile dir: %r"%location) | |
395 | else: |
|
417 | else: | |
396 | self.log.fatal("Profile directory %r not found."%location) |
|
418 | self.log.fatal("Profile directory %r not found."%location) | |
397 | self.exit(1) |
|
419 | self.exit(1) | |
398 | else: |
|
420 | else: | |
399 | self.log.info("Using existing profile dir: %r"%location) |
|
421 | self.log.info("Using existing profile dir: %r"%location) | |
400 | # if profile_dir is specified explicitly, set profile name |
|
422 | # if profile_dir is specified explicitly, set profile name | |
401 | dir_name = os.path.basename(p.location) |
|
423 | dir_name = os.path.basename(p.location) | |
402 | if dir_name.startswith('profile_'): |
|
424 | if dir_name.startswith('profile_'): | |
403 | self.profile = dir_name[8:] |
|
425 | self.profile = dir_name[8:] | |
404 |
|
426 | |||
405 | self.profile_dir = p |
|
427 | self.profile_dir = p | |
406 | self.config_file_paths.append(p.location) |
|
428 | self.config_file_paths.append(p.location) | |
407 | self._in_init_profile_dir = False |
|
429 | self._in_init_profile_dir = False | |
408 |
|
430 | |||
409 | def init_config_files(self): |
|
431 | def init_config_files(self): | |
410 | """[optionally] copy default config files into profile dir.""" |
|
432 | """[optionally] copy default config files into profile dir.""" | |
411 | self.config_file_paths.extend(ENV_CONFIG_DIRS) |
|
433 | self.config_file_paths.extend(ENV_CONFIG_DIRS) | |
412 | self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) |
|
434 | self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) | |
413 | # copy config files |
|
435 | # copy config files | |
414 | path = Path(self.builtin_profile_dir) |
|
436 | path = Path(self.builtin_profile_dir) | |
415 | if self.copy_config_files: |
|
437 | if self.copy_config_files: | |
416 | src = self.profile |
|
438 | src = self.profile | |
417 |
|
439 | |||
418 | cfg = self.config_file_name |
|
440 | cfg = self.config_file_name | |
419 | if path and (path / cfg).exists(): |
|
441 | if path and (path / cfg).exists(): | |
420 | self.log.warning( |
|
442 | self.log.warning( | |
421 | "Staging %r from %s into %r [overwrite=%s]" |
|
443 | "Staging %r from %s into %r [overwrite=%s]" | |
422 | % (cfg, src, self.profile_dir.location, self.overwrite) |
|
444 | % (cfg, src, self.profile_dir.location, self.overwrite) | |
423 | ) |
|
445 | ) | |
424 | self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) |
|
446 | self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) | |
425 | else: |
|
447 | else: | |
426 | self.stage_default_config_file() |
|
448 | self.stage_default_config_file() | |
427 | else: |
|
449 | else: | |
428 | # Still stage *bundled* config files, but not generated ones |
|
450 | # Still stage *bundled* config files, but not generated ones | |
429 | # This is necessary for `ipython profile=sympy` to load the profile |
|
451 | # This is necessary for `ipython profile=sympy` to load the profile | |
430 | # on the first go |
|
452 | # on the first go | |
431 | files = path.glob("*.py") |
|
453 | files = path.glob("*.py") | |
432 | for fullpath in files: |
|
454 | for fullpath in files: | |
433 | cfg = fullpath.name |
|
455 | cfg = fullpath.name | |
434 | if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): |
|
456 | if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): | |
435 | # file was copied |
|
457 | # file was copied | |
436 | self.log.warning("Staging bundled %s from %s into %r"%( |
|
458 | self.log.warning("Staging bundled %s from %s into %r"%( | |
437 | cfg, self.profile, self.profile_dir.location) |
|
459 | cfg, self.profile, self.profile_dir.location) | |
438 | ) |
|
460 | ) | |
439 |
|
461 | |||
440 |
|
462 | |||
441 | def stage_default_config_file(self): |
|
463 | def stage_default_config_file(self): | |
442 | """auto generate default config file, and stage it into the profile.""" |
|
464 | """auto generate default config file, and stage it into the profile.""" | |
443 | s = self.generate_config_file() |
|
465 | s = self.generate_config_file() | |
444 | config_file = Path(self.profile_dir.location) / self.config_file_name |
|
466 | config_file = Path(self.profile_dir.location) / self.config_file_name | |
445 | if self.overwrite or not config_file.exists(): |
|
467 | if self.overwrite or not config_file.exists(): | |
446 | self.log.warning("Generating default config file: %r" % (config_file)) |
|
468 | self.log.warning("Generating default config file: %r" % (config_file)) | |
447 | config_file.write_text(s) |
|
469 | config_file.write_text(s) | |
448 |
|
470 | |||
449 | @catch_config_error |
|
471 | @catch_config_error | |
450 | def initialize(self, argv=None): |
|
472 | def initialize(self, argv=None): | |
451 | # don't hook up crash handler before parsing command-line |
|
473 | # don't hook up crash handler before parsing command-line | |
452 | self.parse_command_line(argv) |
|
474 | self.parse_command_line(argv) | |
453 | self.init_crash_handler() |
|
475 | self.init_crash_handler() | |
454 | if self.subapp is not None: |
|
476 | if self.subapp is not None: | |
455 | # stop here if subapp is taking over |
|
477 | # stop here if subapp is taking over | |
456 | return |
|
478 | return | |
457 | # save a copy of CLI config to re-load after config files |
|
479 | # save a copy of CLI config to re-load after config files | |
458 | # so that it has highest priority |
|
480 | # so that it has highest priority | |
459 | cl_config = deepcopy(self.config) |
|
481 | cl_config = deepcopy(self.config) | |
460 | self.init_profile_dir() |
|
482 | self.init_profile_dir() | |
461 | self.init_config_files() |
|
483 | self.init_config_files() | |
462 | self.load_config_file() |
|
484 | self.load_config_file() | |
463 | # enforce cl-opts override configfile opts: |
|
485 | # enforce cl-opts override configfile opts: | |
464 | self.update_config(cl_config) |
|
486 | self.update_config(cl_config) |
@@ -1,70 +1,70 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """ | |
3 | Autocall capabilities for IPython.core. |
|
3 | Autocall capabilities for IPython.core. | |
4 |
|
4 | |||
5 | Authors: |
|
5 | Authors: | |
6 |
|
6 | |||
7 | * Brian Granger |
|
7 | * Brian Granger | |
8 | * Fernando Perez |
|
8 | * Fernando Perez | |
9 | * Thomas Kluyver |
|
9 | * Thomas Kluyver | |
10 |
|
10 | |||
11 | Notes |
|
11 | Notes | |
12 | ----- |
|
12 | ----- | |
13 | """ |
|
13 | """ | |
14 |
|
14 | |||
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | # Copyright (C) 2008-2011 The IPython Development Team |
|
16 | # Copyright (C) 2008-2011 The IPython Development Team | |
17 | # |
|
17 | # | |
18 | # Distributed under the terms of the BSD License. The full license is in |
|
18 | # Distributed under the terms of the BSD License. The full license is in | |
19 | # the file COPYING, distributed as part of this software. |
|
19 | # the file COPYING, distributed as part of this software. | |
20 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
21 |
|
21 | |||
22 | #----------------------------------------------------------------------------- |
|
22 | #----------------------------------------------------------------------------- | |
23 | # Imports |
|
23 | # Imports | |
24 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
25 |
|
25 | |||
26 |
|
26 | |||
27 | #----------------------------------------------------------------------------- |
|
27 | #----------------------------------------------------------------------------- | |
28 | # Code |
|
28 | # Code | |
29 | #----------------------------------------------------------------------------- |
|
29 | #----------------------------------------------------------------------------- | |
30 |
|
30 | |||
31 | class IPyAutocall(object): |
|
31 | class IPyAutocall(object): | |
32 | """ Instances of this class are always autocalled |
|
32 | """ Instances of this class are always autocalled | |
33 |
|
33 | |||
34 | This happens regardless of 'autocall' variable state. Use this to |
|
34 | This happens regardless of 'autocall' variable state. Use this to | |
35 | develop macro-like mechanisms. |
|
35 | develop macro-like mechanisms. | |
36 | """ |
|
36 | """ | |
37 | _ip = None |
|
37 | _ip = None | |
38 | rewrite = True |
|
38 | rewrite = True | |
39 | def __init__(self, ip=None): |
|
39 | def __init__(self, ip=None): | |
40 | self._ip = ip |
|
40 | self._ip = ip | |
41 |
|
41 | |||
42 | def set_ip(self, ip): |
|
42 | def set_ip(self, ip): | |
43 | """ Will be used to set _ip point to current ipython instance b/f call |
|
43 | """ Will be used to set _ip point to current ipython instance b/f call | |
44 |
|
44 | |||
45 | Override this method if you don't want this to happen. |
|
45 | Override this method if you don't want this to happen. | |
46 |
|
46 | |||
47 | """ |
|
47 | """ | |
48 | self._ip = ip |
|
48 | self._ip = ip | |
49 |
|
49 | |||
50 |
|
50 | |||
51 | class ExitAutocall(IPyAutocall): |
|
51 | class ExitAutocall(IPyAutocall): | |
52 | """An autocallable object which will be added to the user namespace so that |
|
52 | """An autocallable object which will be added to the user namespace so that | |
53 | exit, exit(), quit or quit() are all valid ways to close the shell.""" |
|
53 | exit, exit(), quit or quit() are all valid ways to close the shell.""" | |
54 | rewrite = False |
|
54 | rewrite = False | |
55 |
|
55 | |||
56 | def __call__(self): |
|
56 | def __call__(self): | |
57 | self._ip.ask_exit() |
|
57 | self._ip.ask_exit() | |
58 |
|
58 | |||
59 | class ZMQExitAutocall(ExitAutocall): |
|
59 | class ZMQExitAutocall(ExitAutocall): | |
60 | """Exit IPython. Autocallable, so it needn't be explicitly called. |
|
60 | """Exit IPython. Autocallable, so it needn't be explicitly called. | |
61 |
|
61 | |||
62 | Parameters |
|
62 | Parameters | |
63 | ---------- |
|
63 | ---------- | |
64 | keep_kernel : bool |
|
64 | keep_kernel : bool | |
65 | If True, leave the kernel alive. Otherwise, tell the kernel to exit too |
|
65 | If True, leave the kernel alive. Otherwise, tell the kernel to exit too | |
66 | (default). |
|
66 | (default). | |
67 | """ |
|
67 | """ | |
68 | def __call__(self, keep_kernel=False): |
|
68 | def __call__(self, keep_kernel=False): | |
69 | self._ip.keepkernel_on_exit = keep_kernel |
|
69 | self._ip.keepkernel_on_exit = keep_kernel | |
70 | self._ip.ask_exit() |
|
70 | self._ip.ask_exit() |
@@ -1,188 +1,196 b'' | |||||
1 | """Compiler tools with improved interactive support. |
|
1 | """Compiler tools with improved interactive support. | |
2 |
|
2 | |||
3 | Provides compilation machinery similar to codeop, but with caching support so |
|
3 | Provides compilation machinery similar to codeop, but with caching support so | |
4 | we can provide interactive tracebacks. |
|
4 | we can provide interactive tracebacks. | |
5 |
|
5 | |||
6 | Authors |
|
6 | Authors | |
7 | ------- |
|
7 | ------- | |
8 | * Robert Kern |
|
8 | * Robert Kern | |
9 | * Fernando Perez |
|
9 | * Fernando Perez | |
10 | * Thomas Kluyver |
|
10 | * Thomas Kluyver | |
11 | """ |
|
11 | """ | |
12 |
|
12 | |||
13 | # Note: though it might be more natural to name this module 'compiler', that |
|
13 | # Note: though it might be more natural to name this module 'compiler', that | |
14 | # name is in the stdlib and name collisions with the stdlib tend to produce |
|
14 | # name is in the stdlib and name collisions with the stdlib tend to produce | |
15 | # weird problems (often with third-party tools). |
|
15 | # weird problems (often with third-party tools). | |
16 |
|
16 | |||
17 | #----------------------------------------------------------------------------- |
|
17 | #----------------------------------------------------------------------------- | |
18 | # Copyright (C) 2010-2011 The IPython Development Team. |
|
18 | # Copyright (C) 2010-2011 The IPython Development Team. | |
19 | # |
|
19 | # | |
20 | # Distributed under the terms of the BSD License. |
|
20 | # Distributed under the terms of the BSD License. | |
21 | # |
|
21 | # | |
22 | # The full license is in the file COPYING.txt, distributed with this software. |
|
22 | # The full license is in the file COPYING.txt, distributed with this software. | |
23 | #----------------------------------------------------------------------------- |
|
23 | #----------------------------------------------------------------------------- | |
24 |
|
24 | |||
25 | #----------------------------------------------------------------------------- |
|
25 | #----------------------------------------------------------------------------- | |
26 | # Imports |
|
26 | # Imports | |
27 | #----------------------------------------------------------------------------- |
|
27 | #----------------------------------------------------------------------------- | |
28 |
|
28 | |||
29 | # Stdlib imports |
|
29 | # Stdlib imports | |
30 | import __future__ |
|
30 | import __future__ | |
31 | from ast import PyCF_ONLY_AST |
|
31 | from ast import PyCF_ONLY_AST | |
32 | import codeop |
|
32 | import codeop | |
33 | import functools |
|
33 | import functools | |
34 | import hashlib |
|
34 | import hashlib | |
35 | import linecache |
|
35 | import linecache | |
36 | import operator |
|
36 | import operator | |
37 | import time |
|
37 | import time | |
38 | from contextlib import contextmanager |
|
38 | from contextlib import contextmanager | |
39 |
|
39 | |||
40 | #----------------------------------------------------------------------------- |
|
40 | #----------------------------------------------------------------------------- | |
41 | # Constants |
|
41 | # Constants | |
42 | #----------------------------------------------------------------------------- |
|
42 | #----------------------------------------------------------------------------- | |
43 |
|
43 | |||
44 | # Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h, |
|
44 | # Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h, | |
45 | # this is used as a bitmask to extract future-related code flags. |
|
45 | # this is used as a bitmask to extract future-related code flags. | |
46 | PyCF_MASK = functools.reduce(operator.or_, |
|
46 | PyCF_MASK = functools.reduce(operator.or_, | |
47 | (getattr(__future__, fname).compiler_flag |
|
47 | (getattr(__future__, fname).compiler_flag | |
48 | for fname in __future__.all_feature_names)) |
|
48 | for fname in __future__.all_feature_names)) | |
49 |
|
49 | |||
50 | #----------------------------------------------------------------------------- |
|
50 | #----------------------------------------------------------------------------- | |
51 | # Local utilities |
|
51 | # Local utilities | |
52 | #----------------------------------------------------------------------------- |
|
52 | #----------------------------------------------------------------------------- | |
53 |
|
53 | |||
54 | def code_name(code, number=0): |
|
54 | def code_name(code, number=0): | |
55 | """ Compute a (probably) unique name for code for caching. |
|
55 | """ Compute a (probably) unique name for code for caching. | |
56 |
|
56 | |||
57 | This now expects code to be unicode. |
|
57 | This now expects code to be unicode. | |
58 | """ |
|
58 | """ | |
59 | hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest() |
|
59 | hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest() | |
60 | # Include the number and 12 characters of the hash in the name. It's |
|
60 | # Include the number and 12 characters of the hash in the name. It's | |
61 | # pretty much impossible that in a single session we'll have collisions |
|
61 | # pretty much impossible that in a single session we'll have collisions | |
62 | # even with truncated hashes, and the full one makes tracebacks too long |
|
62 | # even with truncated hashes, and the full one makes tracebacks too long | |
63 | return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12]) |
|
63 | return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12]) | |
64 |
|
64 | |||
65 | #----------------------------------------------------------------------------- |
|
65 | #----------------------------------------------------------------------------- | |
66 | # Classes and functions |
|
66 | # Classes and functions | |
67 | #----------------------------------------------------------------------------- |
|
67 | #----------------------------------------------------------------------------- | |
68 |
|
68 | |||
69 | class CachingCompiler(codeop.Compile): |
|
69 | class CachingCompiler(codeop.Compile): | |
70 | """A compiler that caches code compiled from interactive statements. |
|
70 | """A compiler that caches code compiled from interactive statements. | |
71 | """ |
|
71 | """ | |
72 |
|
72 | |||
73 | def __init__(self): |
|
73 | def __init__(self): | |
74 | codeop.Compile.__init__(self) |
|
74 | codeop.Compile.__init__(self) | |
75 |
|
75 | |||
76 | # This is ugly, but it must be done this way to allow multiple |
|
76 | # This is ugly, but it must be done this way to allow multiple | |
77 | # simultaneous ipython instances to coexist. Since Python itself |
|
77 | # simultaneous ipython instances to coexist. Since Python itself | |
78 | # directly accesses the data structures in the linecache module, and |
|
78 | # directly accesses the data structures in the linecache module, and | |
79 | # the cache therein is global, we must work with that data structure. |
|
79 | # the cache therein is global, we must work with that data structure. | |
80 | # We must hold a reference to the original checkcache routine and call |
|
80 | # We must hold a reference to the original checkcache routine and call | |
81 | # that in our own check_cache() below, but the special IPython cache |
|
81 | # that in our own check_cache() below, but the special IPython cache | |
82 | # must also be shared by all IPython instances. If we were to hold |
|
82 | # must also be shared by all IPython instances. If we were to hold | |
83 | # separate caches (one in each CachingCompiler instance), any call made |
|
83 | # separate caches (one in each CachingCompiler instance), any call made | |
84 | # by Python itself to linecache.checkcache() would obliterate the |
|
84 | # by Python itself to linecache.checkcache() would obliterate the | |
85 | # cached data from the other IPython instances. |
|
85 | # cached data from the other IPython instances. | |
86 | if not hasattr(linecache, '_ipython_cache'): |
|
86 | if not hasattr(linecache, '_ipython_cache'): | |
87 | linecache._ipython_cache = {} |
|
87 | linecache._ipython_cache = {} | |
88 | if not hasattr(linecache, '_checkcache_ori'): |
|
88 | if not hasattr(linecache, '_checkcache_ori'): | |
89 | linecache._checkcache_ori = linecache.checkcache |
|
89 | linecache._checkcache_ori = linecache.checkcache | |
90 | # Now, we must monkeypatch the linecache directly so that parts of the |
|
90 | # Now, we must monkeypatch the linecache directly so that parts of the | |
91 | # stdlib that call it outside our control go through our codepath |
|
91 | # stdlib that call it outside our control go through our codepath | |
92 | # (otherwise we'd lose our tracebacks). |
|
92 | # (otherwise we'd lose our tracebacks). | |
93 | linecache.checkcache = check_linecache_ipython |
|
93 | linecache.checkcache = check_linecache_ipython | |
94 |
|
94 | |||
|
95 | # Caching a dictionary { filename: execution_count } for nicely | |||
|
96 | # rendered tracebacks. The filename corresponds to the filename | |||
|
97 | # argument used for the builtins.compile function. | |||
|
98 | self._filename_map = {} | |||
95 |
|
99 | |||
96 | def ast_parse(self, source, filename='<unknown>', symbol='exec'): |
|
100 | def ast_parse(self, source, filename='<unknown>', symbol='exec'): | |
97 | """Parse code to an AST with the current compiler flags active. |
|
101 | """Parse code to an AST with the current compiler flags active. | |
98 |
|
102 | |||
99 | Arguments are exactly the same as ast.parse (in the standard library), |
|
103 | Arguments are exactly the same as ast.parse (in the standard library), | |
100 | and are passed to the built-in compile function.""" |
|
104 | and are passed to the built-in compile function.""" | |
101 | return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1) |
|
105 | return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1) | |
102 |
|
106 | |||
103 | def reset_compiler_flags(self): |
|
107 | def reset_compiler_flags(self): | |
104 | """Reset compiler flags to default state.""" |
|
108 | """Reset compiler flags to default state.""" | |
105 | # This value is copied from codeop.Compile.__init__, so if that ever |
|
109 | # This value is copied from codeop.Compile.__init__, so if that ever | |
106 | # changes, it will need to be updated. |
|
110 | # changes, it will need to be updated. | |
107 | self.flags = codeop.PyCF_DONT_IMPLY_DEDENT |
|
111 | self.flags = codeop.PyCF_DONT_IMPLY_DEDENT | |
108 |
|
112 | |||
109 | @property |
|
113 | @property | |
110 | def compiler_flags(self): |
|
114 | def compiler_flags(self): | |
111 | """Flags currently active in the compilation process. |
|
115 | """Flags currently active in the compilation process. | |
112 | """ |
|
116 | """ | |
113 | return self.flags |
|
117 | return self.flags | |
114 |
|
118 | |||
115 | def get_code_name(self, raw_code, transformed_code, number): |
|
119 | def get_code_name(self, raw_code, transformed_code, number): | |
116 | """Compute filename given the code, and the cell number. |
|
120 | """Compute filename given the code, and the cell number. | |
117 |
|
121 | |||
118 | Parameters |
|
122 | Parameters | |
119 | ---------- |
|
123 | ---------- | |
120 | raw_code : str |
|
124 | raw_code : str | |
121 | The raw cell code. |
|
125 | The raw cell code. | |
122 | transformed_code : str |
|
126 | transformed_code : str | |
123 | The executable Python source code to cache and compile. |
|
127 | The executable Python source code to cache and compile. | |
124 | number : int |
|
128 | number : int | |
125 | A number which forms part of the code's name. Used for the execution |
|
129 | A number which forms part of the code's name. Used for the execution | |
126 | counter. |
|
130 | counter. | |
127 |
|
131 | |||
128 | Returns |
|
132 | Returns | |
129 | ------- |
|
133 | ------- | |
130 | The computed filename. |
|
134 | The computed filename. | |
131 | """ |
|
135 | """ | |
132 | return code_name(transformed_code, number) |
|
136 | return code_name(transformed_code, number) | |
133 |
|
137 | |||
134 | def cache(self, transformed_code, number=0, raw_code=None): |
|
138 | def cache(self, transformed_code, number=0, raw_code=None): | |
135 | """Make a name for a block of code, and cache the code. |
|
139 | """Make a name for a block of code, and cache the code. | |
136 |
|
140 | |||
137 | Parameters |
|
141 | Parameters | |
138 | ---------- |
|
142 | ---------- | |
139 | transformed_code : str |
|
143 | transformed_code : str | |
140 | The executable Python source code to cache and compile. |
|
144 | The executable Python source code to cache and compile. | |
141 | number : int |
|
145 | number : int | |
142 | A number which forms part of the code's name. Used for the execution |
|
146 | A number which forms part of the code's name. Used for the execution | |
143 | counter. |
|
147 | counter. | |
144 | raw_code : str |
|
148 | raw_code : str | |
145 | The raw code before transformation, if None, set to `transformed_code`. |
|
149 | The raw code before transformation, if None, set to `transformed_code`. | |
146 |
|
150 | |||
147 | Returns |
|
151 | Returns | |
148 | ------- |
|
152 | ------- | |
149 | The name of the cached code (as a string). Pass this as the filename |
|
153 | The name of the cached code (as a string). Pass this as the filename | |
150 | argument to compilation, so that tracebacks are correctly hooked up. |
|
154 | argument to compilation, so that tracebacks are correctly hooked up. | |
151 | """ |
|
155 | """ | |
152 | if raw_code is None: |
|
156 | if raw_code is None: | |
153 | raw_code = transformed_code |
|
157 | raw_code = transformed_code | |
154 |
|
158 | |||
155 | name = self.get_code_name(raw_code, transformed_code, number) |
|
159 | name = self.get_code_name(raw_code, transformed_code, number) | |
|
160 | ||||
|
161 | # Save the execution count | |||
|
162 | self._filename_map[name] = number | |||
|
163 | ||||
156 | entry = ( |
|
164 | entry = ( | |
157 | len(transformed_code), |
|
165 | len(transformed_code), | |
158 | time.time(), |
|
166 | time.time(), | |
159 | [line + "\n" for line in transformed_code.splitlines()], |
|
167 | [line + "\n" for line in transformed_code.splitlines()], | |
160 | name, |
|
168 | name, | |
161 | ) |
|
169 | ) | |
162 | linecache.cache[name] = entry |
|
170 | linecache.cache[name] = entry | |
163 | linecache._ipython_cache[name] = entry |
|
171 | linecache._ipython_cache[name] = entry | |
164 | return name |
|
172 | return name | |
165 |
|
173 | |||
166 | @contextmanager |
|
174 | @contextmanager | |
167 | def extra_flags(self, flags): |
|
175 | def extra_flags(self, flags): | |
168 | ## bits that we'll set to 1 |
|
176 | ## bits that we'll set to 1 | |
169 | turn_on_bits = ~self.flags & flags |
|
177 | turn_on_bits = ~self.flags & flags | |
170 |
|
178 | |||
171 |
|
179 | |||
172 | self.flags = self.flags | flags |
|
180 | self.flags = self.flags | flags | |
173 | try: |
|
181 | try: | |
174 | yield |
|
182 | yield | |
175 | finally: |
|
183 | finally: | |
176 | # turn off only the bits we turned on so that something like |
|
184 | # turn off only the bits we turned on so that something like | |
177 | # __future__ that set flags stays. |
|
185 | # __future__ that set flags stays. | |
178 | self.flags &= ~turn_on_bits |
|
186 | self.flags &= ~turn_on_bits | |
179 |
|
187 | |||
180 |
|
188 | |||
181 | def check_linecache_ipython(*args): |
|
189 | def check_linecache_ipython(*args): | |
182 | """Call linecache.checkcache() safely protecting our cached values. |
|
190 | """Call linecache.checkcache() safely protecting our cached values. | |
183 | """ |
|
191 | """ | |
184 | # First call the original checkcache as intended |
|
192 | # First call the original checkcache as intended | |
185 | linecache._checkcache_ori(*args) |
|
193 | linecache._checkcache_ori(*args) | |
186 | # Then, update back the cache with our data, so that tracebacks related |
|
194 | # Then, update back the cache with our data, so that tracebacks related | |
187 | # to our compiled codes can be produced. |
|
195 | # to our compiled codes can be produced. | |
188 | linecache.cache.update(linecache._ipython_cache) |
|
196 | linecache.cache.update(linecache._ipython_cache) |
@@ -1,2239 +1,2239 b'' | |||||
1 | """Completion for IPython. |
|
1 | """Completion for IPython. | |
2 |
|
2 | |||
3 | This module started as fork of the rlcompleter module in the Python standard |
|
3 | This module started as fork of the rlcompleter module in the Python standard | |
4 | library. The original enhancements made to rlcompleter have been sent |
|
4 | library. The original enhancements made to rlcompleter have been sent | |
5 | upstream and were accepted as of Python 2.3, |
|
5 | upstream and were accepted as of Python 2.3, | |
6 |
|
6 | |||
7 | This module now support a wide variety of completion mechanism both available |
|
7 | This module now support a wide variety of completion mechanism both available | |
8 | for normal classic Python code, as well as completer for IPython specific |
|
8 | for normal classic Python code, as well as completer for IPython specific | |
9 | Syntax like magics. |
|
9 | Syntax like magics. | |
10 |
|
10 | |||
11 | Latex and Unicode completion |
|
11 | Latex and Unicode completion | |
12 | ============================ |
|
12 | ============================ | |
13 |
|
13 | |||
14 | IPython and compatible frontends not only can complete your code, but can help |
|
14 | IPython and compatible frontends not only can complete your code, but can help | |
15 | you to input a wide range of characters. In particular we allow you to insert |
|
15 | you to input a wide range of characters. In particular we allow you to insert | |
16 | a unicode character using the tab completion mechanism. |
|
16 | a unicode character using the tab completion mechanism. | |
17 |
|
17 | |||
18 | Forward latex/unicode completion |
|
18 | Forward latex/unicode completion | |
19 | -------------------------------- |
|
19 | -------------------------------- | |
20 |
|
20 | |||
21 | Forward completion allows you to easily type a unicode character using its latex |
|
21 | Forward completion allows you to easily type a unicode character using its latex | |
22 | name, or unicode long description. To do so type a backslash follow by the |
|
22 | name, or unicode long description. To do so type a backslash follow by the | |
23 | relevant name and press tab: |
|
23 | relevant name and press tab: | |
24 |
|
24 | |||
25 |
|
25 | |||
26 | Using latex completion: |
|
26 | Using latex completion: | |
27 |
|
27 | |||
28 | .. code:: |
|
28 | .. code:: | |
29 |
|
29 | |||
30 | \\alpha<tab> |
|
30 | \\alpha<tab> | |
31 | α |
|
31 | α | |
32 |
|
32 | |||
33 | or using unicode completion: |
|
33 | or using unicode completion: | |
34 |
|
34 | |||
35 |
|
35 | |||
36 | .. code:: |
|
36 | .. code:: | |
37 |
|
37 | |||
38 | \\GREEK SMALL LETTER ALPHA<tab> |
|
38 | \\GREEK SMALL LETTER ALPHA<tab> | |
39 | α |
|
39 | α | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | Only valid Python identifiers will complete. Combining characters (like arrow or |
|
42 | Only valid Python identifiers will complete. Combining characters (like arrow or | |
43 | dots) are also available, unlike latex they need to be put after the their |
|
43 | dots) are also available, unlike latex they need to be put after the their | |
44 | counterpart that is to say, `F\\\\vec<tab>` is correct, not `\\\\vec<tab>F`. |
|
44 | counterpart that is to say, `F\\\\vec<tab>` is correct, not `\\\\vec<tab>F`. | |
45 |
|
45 | |||
46 | Some browsers are known to display combining characters incorrectly. |
|
46 | Some browsers are known to display combining characters incorrectly. | |
47 |
|
47 | |||
48 | Backward latex completion |
|
48 | Backward latex completion | |
49 | ------------------------- |
|
49 | ------------------------- | |
50 |
|
50 | |||
51 | It is sometime challenging to know how to type a character, if you are using |
|
51 | It is sometime challenging to know how to type a character, if you are using | |
52 | IPython, or any compatible frontend you can prepend backslash to the character |
|
52 | IPython, or any compatible frontend you can prepend backslash to the character | |
53 | and press `<tab>` to expand it to its latex form. |
|
53 | and press `<tab>` to expand it to its latex form. | |
54 |
|
54 | |||
55 | .. code:: |
|
55 | .. code:: | |
56 |
|
56 | |||
57 | \\α<tab> |
|
57 | \\α<tab> | |
58 | \\alpha |
|
58 | \\alpha | |
59 |
|
59 | |||
60 |
|
60 | |||
61 | Both forward and backward completions can be deactivated by setting the |
|
61 | Both forward and backward completions can be deactivated by setting the | |
62 | ``Completer.backslash_combining_completions`` option to ``False``. |
|
62 | ``Completer.backslash_combining_completions`` option to ``False``. | |
63 |
|
63 | |||
64 |
|
64 | |||
65 | Experimental |
|
65 | Experimental | |
66 | ============ |
|
66 | ============ | |
67 |
|
67 | |||
68 | Starting with IPython 6.0, this module can make use of the Jedi library to |
|
68 | Starting with IPython 6.0, this module can make use of the Jedi library to | |
69 | generate completions both using static analysis of the code, and dynamically |
|
69 | generate completions both using static analysis of the code, and dynamically | |
70 | inspecting multiple namespaces. Jedi is an autocompletion and static analysis |
|
70 | inspecting multiple namespaces. Jedi is an autocompletion and static analysis | |
71 | for Python. The APIs attached to this new mechanism is unstable and will |
|
71 | for Python. The APIs attached to this new mechanism is unstable and will | |
72 | raise unless use in an :any:`provisionalcompleter` context manager. |
|
72 | raise unless use in an :any:`provisionalcompleter` context manager. | |
73 |
|
73 | |||
74 | You will find that the following are experimental: |
|
74 | You will find that the following are experimental: | |
75 |
|
75 | |||
76 | - :any:`provisionalcompleter` |
|
76 | - :any:`provisionalcompleter` | |
77 | - :any:`IPCompleter.completions` |
|
77 | - :any:`IPCompleter.completions` | |
78 | - :any:`Completion` |
|
78 | - :any:`Completion` | |
79 | - :any:`rectify_completions` |
|
79 | - :any:`rectify_completions` | |
80 |
|
80 | |||
81 | .. note:: |
|
81 | .. note:: | |
82 |
|
82 | |||
83 | better name for :any:`rectify_completions` ? |
|
83 | better name for :any:`rectify_completions` ? | |
84 |
|
84 | |||
85 | We welcome any feedback on these new API, and we also encourage you to try this |
|
85 | We welcome any feedback on these new API, and we also encourage you to try this | |
86 | module in debug mode (start IPython with ``--Completer.debug=True``) in order |
|
86 | module in debug mode (start IPython with ``--Completer.debug=True``) in order | |
87 | to have extra logging information if :any:`jedi` is crashing, or if current |
|
87 | to have extra logging information if :any:`jedi` is crashing, or if current | |
88 | IPython completer pending deprecations are returning results not yet handled |
|
88 | IPython completer pending deprecations are returning results not yet handled | |
89 | by :any:`jedi` |
|
89 | by :any:`jedi` | |
90 |
|
90 | |||
91 | Using Jedi for tab completion allow snippets like the following to work without |
|
91 | Using Jedi for tab completion allow snippets like the following to work without | |
92 | having to execute any code: |
|
92 | having to execute any code: | |
93 |
|
93 | |||
94 | >>> myvar = ['hello', 42] |
|
94 | >>> myvar = ['hello', 42] | |
95 | ... myvar[1].bi<tab> |
|
95 | ... myvar[1].bi<tab> | |
96 |
|
96 | |||
97 | Tab completion will be able to infer that ``myvar[1]`` is a real number without |
|
97 | Tab completion will be able to infer that ``myvar[1]`` is a real number without | |
98 | executing any code unlike the previously available ``IPCompleter.greedy`` |
|
98 | executing any code unlike the previously available ``IPCompleter.greedy`` | |
99 | option. |
|
99 | option. | |
100 |
|
100 | |||
101 | Be sure to update :any:`jedi` to the latest stable version or to try the |
|
101 | Be sure to update :any:`jedi` to the latest stable version or to try the | |
102 | current development version to get better completions. |
|
102 | current development version to get better completions. | |
103 | """ |
|
103 | """ | |
104 |
|
104 | |||
105 |
|
105 | |||
106 | # Copyright (c) IPython Development Team. |
|
106 | # Copyright (c) IPython Development Team. | |
107 | # Distributed under the terms of the Modified BSD License. |
|
107 | # Distributed under the terms of the Modified BSD License. | |
108 | # |
|
108 | # | |
109 | # Some of this code originated from rlcompleter in the Python standard library |
|
109 | # Some of this code originated from rlcompleter in the Python standard library | |
110 | # Copyright (C) 2001 Python Software Foundation, www.python.org |
|
110 | # Copyright (C) 2001 Python Software Foundation, www.python.org | |
111 |
|
111 | |||
112 |
|
112 | |||
113 | import builtins as builtin_mod |
|
113 | import builtins as builtin_mod | |
114 | import glob |
|
114 | import glob | |
115 | import inspect |
|
115 | import inspect | |
116 | import itertools |
|
116 | import itertools | |
117 | import keyword |
|
117 | import keyword | |
118 | import os |
|
118 | import os | |
119 | import re |
|
119 | import re | |
120 | import string |
|
120 | import string | |
121 | import sys |
|
121 | import sys | |
122 | import time |
|
122 | import time | |
123 | import unicodedata |
|
123 | import unicodedata | |
124 | import uuid |
|
124 | import uuid | |
125 | import warnings |
|
125 | import warnings | |
126 | from contextlib import contextmanager |
|
126 | from contextlib import contextmanager | |
127 | from importlib import import_module |
|
127 | from importlib import import_module | |
128 | from types import SimpleNamespace |
|
128 | from types import SimpleNamespace | |
129 | from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional |
|
129 | from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional | |
130 |
|
130 | |||
131 | from IPython.core.error import TryNext |
|
131 | from IPython.core.error import TryNext | |
132 | from IPython.core.inputtransformer2 import ESC_MAGIC |
|
132 | from IPython.core.inputtransformer2 import ESC_MAGIC | |
133 | from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol |
|
133 | from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol | |
134 | from IPython.core.oinspect import InspectColors |
|
134 | from IPython.core.oinspect import InspectColors | |
135 | from IPython.utils import generics |
|
135 | from IPython.utils import generics | |
136 | from IPython.utils.dir2 import dir2, get_real_method |
|
136 | from IPython.utils.dir2 import dir2, get_real_method | |
137 | from IPython.utils.path import ensure_dir_exists |
|
137 | from IPython.utils.path import ensure_dir_exists | |
138 | from IPython.utils.process import arg_split |
|
138 | from IPython.utils.process import arg_split | |
139 | from traitlets import Bool, Enum, Int, List as ListTrait, Unicode, default, observe |
|
139 | from traitlets import Bool, Enum, Int, List as ListTrait, Unicode, default, observe | |
140 | from traitlets.config.configurable import Configurable |
|
140 | from traitlets.config.configurable import Configurable | |
141 |
|
141 | |||
142 | import __main__ |
|
142 | import __main__ | |
143 |
|
143 | |||
144 | # skip module docstests |
|
144 | # skip module docstests | |
145 | skip_doctest = True |
|
145 | skip_doctest = True | |
146 |
|
146 | |||
147 | try: |
|
147 | try: | |
148 | import jedi |
|
148 | import jedi | |
149 | jedi.settings.case_insensitive_completion = False |
|
149 | jedi.settings.case_insensitive_completion = False | |
150 | import jedi.api.helpers |
|
150 | import jedi.api.helpers | |
151 | import jedi.api.classes |
|
151 | import jedi.api.classes | |
152 | JEDI_INSTALLED = True |
|
152 | JEDI_INSTALLED = True | |
153 | except ImportError: |
|
153 | except ImportError: | |
154 | JEDI_INSTALLED = False |
|
154 | JEDI_INSTALLED = False | |
155 | #----------------------------------------------------------------------------- |
|
155 | #----------------------------------------------------------------------------- | |
156 | # Globals |
|
156 | # Globals | |
157 | #----------------------------------------------------------------------------- |
|
157 | #----------------------------------------------------------------------------- | |
158 |
|
158 | |||
159 | # ranges where we have most of the valid unicode names. We could be more finer |
|
159 | # ranges where we have most of the valid unicode names. We could be more finer | |
160 | # grained but is it worth it for performace While unicode have character in the |
|
160 | # grained but is it worth it for performace While unicode have character in the | |
161 | # rage 0, 0x110000, we seem to have name for about 10% of those. (131808 as I |
|
161 | # rage 0, 0x110000, we seem to have name for about 10% of those. (131808 as I | |
162 | # write this). With below range we cover them all, with a density of ~67% |
|
162 | # write this). With below range we cover them all, with a density of ~67% | |
163 | # biggest next gap we consider only adds up about 1% density and there are 600 |
|
163 | # biggest next gap we consider only adds up about 1% density and there are 600 | |
164 | # gaps that would need hard coding. |
|
164 | # gaps that would need hard coding. | |
165 | _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)] |
|
165 | _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)] | |
166 |
|
166 | |||
167 | # Public API |
|
167 | # Public API | |
168 | __all__ = ['Completer','IPCompleter'] |
|
168 | __all__ = ['Completer','IPCompleter'] | |
169 |
|
169 | |||
170 | if sys.platform == 'win32': |
|
170 | if sys.platform == 'win32': | |
171 | PROTECTABLES = ' ' |
|
171 | PROTECTABLES = ' ' | |
172 | else: |
|
172 | else: | |
173 | PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&' |
|
173 | PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&' | |
174 |
|
174 | |||
175 | # Protect against returning an enormous number of completions which the frontend |
|
175 | # Protect against returning an enormous number of completions which the frontend | |
176 | # may have trouble processing. |
|
176 | # may have trouble processing. | |
177 | MATCHES_LIMIT = 500 |
|
177 | MATCHES_LIMIT = 500 | |
178 |
|
178 | |||
179 | _deprecation_readline_sentinel = object() |
|
179 | _deprecation_readline_sentinel = object() | |
180 |
|
180 | |||
181 |
|
181 | |||
182 | class ProvisionalCompleterWarning(FutureWarning): |
|
182 | class ProvisionalCompleterWarning(FutureWarning): | |
183 | """ |
|
183 | """ | |
184 | Exception raise by an experimental feature in this module. |
|
184 | Exception raise by an experimental feature in this module. | |
185 |
|
185 | |||
186 | Wrap code in :any:`provisionalcompleter` context manager if you |
|
186 | Wrap code in :any:`provisionalcompleter` context manager if you | |
187 | are certain you want to use an unstable feature. |
|
187 | are certain you want to use an unstable feature. | |
188 | """ |
|
188 | """ | |
189 | pass |
|
189 | pass | |
190 |
|
190 | |||
191 | warnings.filterwarnings('error', category=ProvisionalCompleterWarning) |
|
191 | warnings.filterwarnings('error', category=ProvisionalCompleterWarning) | |
192 |
|
192 | |||
193 | @contextmanager |
|
193 | @contextmanager | |
194 | def provisionalcompleter(action='ignore'): |
|
194 | def provisionalcompleter(action='ignore'): | |
195 | """ |
|
195 | """ | |
196 | This context manager has to be used in any place where unstable completer |
|
196 | This context manager has to be used in any place where unstable completer | |
197 | behavior and API may be called. |
|
197 | behavior and API may be called. | |
198 |
|
198 | |||
199 | >>> with provisionalcompleter(): |
|
199 | >>> with provisionalcompleter(): | |
200 | ... completer.do_experimental_things() # works |
|
200 | ... completer.do_experimental_things() # works | |
201 |
|
201 | |||
202 | >>> completer.do_experimental_things() # raises. |
|
202 | >>> completer.do_experimental_things() # raises. | |
203 |
|
203 | |||
204 | .. note:: |
|
204 | .. note:: | |
205 |
|
205 | |||
206 | Unstable |
|
206 | Unstable | |
207 |
|
207 | |||
208 | By using this context manager you agree that the API in use may change |
|
208 | By using this context manager you agree that the API in use may change | |
209 | without warning, and that you won't complain if they do so. |
|
209 | without warning, and that you won't complain if they do so. | |
210 |
|
210 | |||
211 | You also understand that, if the API is not to your liking, you should report |
|
211 | You also understand that, if the API is not to your liking, you should report | |
212 | a bug to explain your use case upstream. |
|
212 | a bug to explain your use case upstream. | |
213 |
|
213 | |||
214 | We'll be happy to get your feedback, feature requests, and improvements on |
|
214 | We'll be happy to get your feedback, feature requests, and improvements on | |
215 | any of the unstable APIs! |
|
215 | any of the unstable APIs! | |
216 | """ |
|
216 | """ | |
217 | with warnings.catch_warnings(): |
|
217 | with warnings.catch_warnings(): | |
218 | warnings.filterwarnings(action, category=ProvisionalCompleterWarning) |
|
218 | warnings.filterwarnings(action, category=ProvisionalCompleterWarning) | |
219 | yield |
|
219 | yield | |
220 |
|
220 | |||
221 |
|
221 | |||
222 | def has_open_quotes(s): |
|
222 | def has_open_quotes(s): | |
223 | """Return whether a string has open quotes. |
|
223 | """Return whether a string has open quotes. | |
224 |
|
224 | |||
225 | This simply counts whether the number of quote characters of either type in |
|
225 | This simply counts whether the number of quote characters of either type in | |
226 | the string is odd. |
|
226 | the string is odd. | |
227 |
|
227 | |||
228 | Returns |
|
228 | Returns | |
229 | ------- |
|
229 | ------- | |
230 | If there is an open quote, the quote character is returned. Else, return |
|
230 | If there is an open quote, the quote character is returned. Else, return | |
231 | False. |
|
231 | False. | |
232 | """ |
|
232 | """ | |
233 | # We check " first, then ', so complex cases with nested quotes will get |
|
233 | # We check " first, then ', so complex cases with nested quotes will get | |
234 | # the " to take precedence. |
|
234 | # the " to take precedence. | |
235 | if s.count('"') % 2: |
|
235 | if s.count('"') % 2: | |
236 | return '"' |
|
236 | return '"' | |
237 | elif s.count("'") % 2: |
|
237 | elif s.count("'") % 2: | |
238 | return "'" |
|
238 | return "'" | |
239 | else: |
|
239 | else: | |
240 | return False |
|
240 | return False | |
241 |
|
241 | |||
242 |
|
242 | |||
243 | def protect_filename(s, protectables=PROTECTABLES): |
|
243 | def protect_filename(s, protectables=PROTECTABLES): | |
244 | """Escape a string to protect certain characters.""" |
|
244 | """Escape a string to protect certain characters.""" | |
245 | if set(s) & set(protectables): |
|
245 | if set(s) & set(protectables): | |
246 | if sys.platform == "win32": |
|
246 | if sys.platform == "win32": | |
247 | return '"' + s + '"' |
|
247 | return '"' + s + '"' | |
248 | else: |
|
248 | else: | |
249 | return "".join(("\\" + c if c in protectables else c) for c in s) |
|
249 | return "".join(("\\" + c if c in protectables else c) for c in s) | |
250 | else: |
|
250 | else: | |
251 | return s |
|
251 | return s | |
252 |
|
252 | |||
253 |
|
253 | |||
254 | def expand_user(path:str) -> Tuple[str, bool, str]: |
|
254 | def expand_user(path:str) -> Tuple[str, bool, str]: | |
255 | """Expand ``~``-style usernames in strings. |
|
255 | """Expand ``~``-style usernames in strings. | |
256 |
|
256 | |||
257 | This is similar to :func:`os.path.expanduser`, but it computes and returns |
|
257 | This is similar to :func:`os.path.expanduser`, but it computes and returns | |
258 | extra information that will be useful if the input was being used in |
|
258 | extra information that will be useful if the input was being used in | |
259 | computing completions, and you wish to return the completions with the |
|
259 | computing completions, and you wish to return the completions with the | |
260 | original '~' instead of its expanded value. |
|
260 | original '~' instead of its expanded value. | |
261 |
|
261 | |||
262 | Parameters |
|
262 | Parameters | |
263 | ---------- |
|
263 | ---------- | |
264 | path : str |
|
264 | path : str | |
265 | String to be expanded. If no ~ is present, the output is the same as the |
|
265 | String to be expanded. If no ~ is present, the output is the same as the | |
266 | input. |
|
266 | input. | |
267 |
|
267 | |||
268 | Returns |
|
268 | Returns | |
269 | ------- |
|
269 | ------- | |
270 | newpath : str |
|
270 | newpath : str | |
271 | Result of ~ expansion in the input path. |
|
271 | Result of ~ expansion in the input path. | |
272 | tilde_expand : bool |
|
272 | tilde_expand : bool | |
273 | Whether any expansion was performed or not. |
|
273 | Whether any expansion was performed or not. | |
274 | tilde_val : str |
|
274 | tilde_val : str | |
275 | The value that ~ was replaced with. |
|
275 | The value that ~ was replaced with. | |
276 | """ |
|
276 | """ | |
277 | # Default values |
|
277 | # Default values | |
278 | tilde_expand = False |
|
278 | tilde_expand = False | |
279 | tilde_val = '' |
|
279 | tilde_val = '' | |
280 | newpath = path |
|
280 | newpath = path | |
281 |
|
281 | |||
282 | if path.startswith('~'): |
|
282 | if path.startswith('~'): | |
283 | tilde_expand = True |
|
283 | tilde_expand = True | |
284 | rest = len(path)-1 |
|
284 | rest = len(path)-1 | |
285 | newpath = os.path.expanduser(path) |
|
285 | newpath = os.path.expanduser(path) | |
286 | if rest: |
|
286 | if rest: | |
287 | tilde_val = newpath[:-rest] |
|
287 | tilde_val = newpath[:-rest] | |
288 | else: |
|
288 | else: | |
289 | tilde_val = newpath |
|
289 | tilde_val = newpath | |
290 |
|
290 | |||
291 | return newpath, tilde_expand, tilde_val |
|
291 | return newpath, tilde_expand, tilde_val | |
292 |
|
292 | |||
293 |
|
293 | |||
294 | def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str: |
|
294 | def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str: | |
295 | """Does the opposite of expand_user, with its outputs. |
|
295 | """Does the opposite of expand_user, with its outputs. | |
296 | """ |
|
296 | """ | |
297 | if tilde_expand: |
|
297 | if tilde_expand: | |
298 | return path.replace(tilde_val, '~') |
|
298 | return path.replace(tilde_val, '~') | |
299 | else: |
|
299 | else: | |
300 | return path |
|
300 | return path | |
301 |
|
301 | |||
302 |
|
302 | |||
303 | def completions_sorting_key(word): |
|
303 | def completions_sorting_key(word): | |
304 | """key for sorting completions |
|
304 | """key for sorting completions | |
305 |
|
305 | |||
306 | This does several things: |
|
306 | This does several things: | |
307 |
|
307 | |||
308 | - Demote any completions starting with underscores to the end |
|
308 | - Demote any completions starting with underscores to the end | |
309 | - Insert any %magic and %%cellmagic completions in the alphabetical order |
|
309 | - Insert any %magic and %%cellmagic completions in the alphabetical order | |
310 | by their name |
|
310 | by their name | |
311 | """ |
|
311 | """ | |
312 | prio1, prio2 = 0, 0 |
|
312 | prio1, prio2 = 0, 0 | |
313 |
|
313 | |||
314 | if word.startswith('__'): |
|
314 | if word.startswith('__'): | |
315 | prio1 = 2 |
|
315 | prio1 = 2 | |
316 | elif word.startswith('_'): |
|
316 | elif word.startswith('_'): | |
317 | prio1 = 1 |
|
317 | prio1 = 1 | |
318 |
|
318 | |||
319 | if word.endswith('='): |
|
319 | if word.endswith('='): | |
320 | prio1 = -1 |
|
320 | prio1 = -1 | |
321 |
|
321 | |||
322 | if word.startswith('%%'): |
|
322 | if word.startswith('%%'): | |
323 | # If there's another % in there, this is something else, so leave it alone |
|
323 | # If there's another % in there, this is something else, so leave it alone | |
324 | if not "%" in word[2:]: |
|
324 | if not "%" in word[2:]: | |
325 | word = word[2:] |
|
325 | word = word[2:] | |
326 | prio2 = 2 |
|
326 | prio2 = 2 | |
327 | elif word.startswith('%'): |
|
327 | elif word.startswith('%'): | |
328 | if not "%" in word[1:]: |
|
328 | if not "%" in word[1:]: | |
329 | word = word[1:] |
|
329 | word = word[1:] | |
330 | prio2 = 1 |
|
330 | prio2 = 1 | |
331 |
|
331 | |||
332 | return prio1, word, prio2 |
|
332 | return prio1, word, prio2 | |
333 |
|
333 | |||
334 |
|
334 | |||
335 | class _FakeJediCompletion: |
|
335 | class _FakeJediCompletion: | |
336 | """ |
|
336 | """ | |
337 | This is a workaround to communicate to the UI that Jedi has crashed and to |
|
337 | This is a workaround to communicate to the UI that Jedi has crashed and to | |
338 | report a bug. Will be used only id :any:`IPCompleter.debug` is set to true. |
|
338 | report a bug. Will be used only id :any:`IPCompleter.debug` is set to true. | |
339 |
|
339 | |||
340 | Added in IPython 6.0 so should likely be removed for 7.0 |
|
340 | Added in IPython 6.0 so should likely be removed for 7.0 | |
341 |
|
341 | |||
342 | """ |
|
342 | """ | |
343 |
|
343 | |||
344 | def __init__(self, name): |
|
344 | def __init__(self, name): | |
345 |
|
345 | |||
346 | self.name = name |
|
346 | self.name = name | |
347 | self.complete = name |
|
347 | self.complete = name | |
348 | self.type = 'crashed' |
|
348 | self.type = 'crashed' | |
349 | self.name_with_symbols = name |
|
349 | self.name_with_symbols = name | |
350 | self.signature = '' |
|
350 | self.signature = '' | |
351 | self._origin = 'fake' |
|
351 | self._origin = 'fake' | |
352 |
|
352 | |||
353 | def __repr__(self): |
|
353 | def __repr__(self): | |
354 | return '<Fake completion object jedi has crashed>' |
|
354 | return '<Fake completion object jedi has crashed>' | |
355 |
|
355 | |||
356 |
|
356 | |||
357 | class Completion: |
|
357 | class Completion: | |
358 | """ |
|
358 | """ | |
359 | Completion object used and return by IPython completers. |
|
359 | Completion object used and return by IPython completers. | |
360 |
|
360 | |||
361 | .. warning:: |
|
361 | .. warning:: | |
362 |
|
362 | |||
363 | Unstable |
|
363 | Unstable | |
364 |
|
364 | |||
365 | This function is unstable, API may change without warning. |
|
365 | This function is unstable, API may change without warning. | |
366 | It will also raise unless use in proper context manager. |
|
366 | It will also raise unless use in proper context manager. | |
367 |
|
367 | |||
368 | This act as a middle ground :any:`Completion` object between the |
|
368 | This act as a middle ground :any:`Completion` object between the | |
369 | :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion |
|
369 | :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion | |
370 | object. While Jedi need a lot of information about evaluator and how the |
|
370 | object. While Jedi need a lot of information about evaluator and how the | |
371 | code should be ran/inspected, PromptToolkit (and other frontend) mostly |
|
371 | code should be ran/inspected, PromptToolkit (and other frontend) mostly | |
372 | need user facing information. |
|
372 | need user facing information. | |
373 |
|
373 | |||
374 | - Which range should be replaced replaced by what. |
|
374 | - Which range should be replaced replaced by what. | |
375 | - Some metadata (like completion type), or meta information to displayed to |
|
375 | - Some metadata (like completion type), or meta information to displayed to | |
376 | the use user. |
|
376 | the use user. | |
377 |
|
377 | |||
378 | For debugging purpose we can also store the origin of the completion (``jedi``, |
|
378 | For debugging purpose we can also store the origin of the completion (``jedi``, | |
379 | ``IPython.python_matches``, ``IPython.magics_matches``...). |
|
379 | ``IPython.python_matches``, ``IPython.magics_matches``...). | |
380 | """ |
|
380 | """ | |
381 |
|
381 | |||
382 | __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin'] |
|
382 | __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin'] | |
383 |
|
383 | |||
384 | def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None: |
|
384 | def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None: | |
385 | warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). " |
|
385 | warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). " | |
386 | "It may change without warnings. " |
|
386 | "It may change without warnings. " | |
387 | "Use in corresponding context manager.", |
|
387 | "Use in corresponding context manager.", | |
388 | category=ProvisionalCompleterWarning, stacklevel=2) |
|
388 | category=ProvisionalCompleterWarning, stacklevel=2) | |
389 |
|
389 | |||
390 | self.start = start |
|
390 | self.start = start | |
391 | self.end = end |
|
391 | self.end = end | |
392 | self.text = text |
|
392 | self.text = text | |
393 | self.type = type |
|
393 | self.type = type | |
394 | self.signature = signature |
|
394 | self.signature = signature | |
395 | self._origin = _origin |
|
395 | self._origin = _origin | |
396 |
|
396 | |||
397 | def __repr__(self): |
|
397 | def __repr__(self): | |
398 | return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \ |
|
398 | return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \ | |
399 | (self.start, self.end, self.text, self.type or '?', self.signature or '?') |
|
399 | (self.start, self.end, self.text, self.type or '?', self.signature or '?') | |
400 |
|
400 | |||
401 | def __eq__(self, other)->Bool: |
|
401 | def __eq__(self, other)->Bool: | |
402 | """ |
|
402 | """ | |
403 | Equality and hash do not hash the type (as some completer may not be |
|
403 | Equality and hash do not hash the type (as some completer may not be | |
404 | able to infer the type), but are use to (partially) de-duplicate |
|
404 | able to infer the type), but are use to (partially) de-duplicate | |
405 | completion. |
|
405 | completion. | |
406 |
|
406 | |||
407 | Completely de-duplicating completion is a bit tricker that just |
|
407 | Completely de-duplicating completion is a bit tricker that just | |
408 | comparing as it depends on surrounding text, which Completions are not |
|
408 | comparing as it depends on surrounding text, which Completions are not | |
409 | aware of. |
|
409 | aware of. | |
410 | """ |
|
410 | """ | |
411 | return self.start == other.start and \ |
|
411 | return self.start == other.start and \ | |
412 | self.end == other.end and \ |
|
412 | self.end == other.end and \ | |
413 | self.text == other.text |
|
413 | self.text == other.text | |
414 |
|
414 | |||
415 | def __hash__(self): |
|
415 | def __hash__(self): | |
416 | return hash((self.start, self.end, self.text)) |
|
416 | return hash((self.start, self.end, self.text)) | |
417 |
|
417 | |||
418 |
|
418 | |||
419 | _IC = Iterable[Completion] |
|
419 | _IC = Iterable[Completion] | |
420 |
|
420 | |||
421 |
|
421 | |||
422 | def _deduplicate_completions(text: str, completions: _IC)-> _IC: |
|
422 | def _deduplicate_completions(text: str, completions: _IC)-> _IC: | |
423 | """ |
|
423 | """ | |
424 | Deduplicate a set of completions. |
|
424 | Deduplicate a set of completions. | |
425 |
|
425 | |||
426 | .. warning:: |
|
426 | .. warning:: | |
427 |
|
427 | |||
428 | Unstable |
|
428 | Unstable | |
429 |
|
429 | |||
430 | This function is unstable, API may change without warning. |
|
430 | This function is unstable, API may change without warning. | |
431 |
|
431 | |||
432 | Parameters |
|
432 | Parameters | |
433 | ---------- |
|
433 | ---------- | |
434 | text: str |
|
434 | text : str | |
435 | text that should be completed. |
|
435 | text that should be completed. | |
436 | completions: Iterator[Completion] |
|
436 | completions : Iterator[Completion] | |
437 | iterator over the completions to deduplicate |
|
437 | iterator over the completions to deduplicate | |
438 |
|
438 | |||
439 | Yields |
|
439 | Yields | |
440 | ------ |
|
440 | ------ | |
441 | `Completions` objects |
|
441 | `Completions` objects | |
442 | Completions coming from multiple sources, may be different but end up having |
|
442 | Completions coming from multiple sources, may be different but end up having | |
443 | the same effect when applied to ``text``. If this is the case, this will |
|
443 | the same effect when applied to ``text``. If this is the case, this will | |
444 | consider completions as equal and only emit the first encountered. |
|
444 | consider completions as equal and only emit the first encountered. | |
445 | Not folded in `completions()` yet for debugging purpose, and to detect when |
|
445 | Not folded in `completions()` yet for debugging purpose, and to detect when | |
446 | the IPython completer does return things that Jedi does not, but should be |
|
446 | the IPython completer does return things that Jedi does not, but should be | |
447 | at some point. |
|
447 | at some point. | |
448 | """ |
|
448 | """ | |
449 | completions = list(completions) |
|
449 | completions = list(completions) | |
450 | if not completions: |
|
450 | if not completions: | |
451 | return |
|
451 | return | |
452 |
|
452 | |||
453 | new_start = min(c.start for c in completions) |
|
453 | new_start = min(c.start for c in completions) | |
454 | new_end = max(c.end for c in completions) |
|
454 | new_end = max(c.end for c in completions) | |
455 |
|
455 | |||
456 | seen = set() |
|
456 | seen = set() | |
457 | for c in completions: |
|
457 | for c in completions: | |
458 | new_text = text[new_start:c.start] + c.text + text[c.end:new_end] |
|
458 | new_text = text[new_start:c.start] + c.text + text[c.end:new_end] | |
459 | if new_text not in seen: |
|
459 | if new_text not in seen: | |
460 | yield c |
|
460 | yield c | |
461 | seen.add(new_text) |
|
461 | seen.add(new_text) | |
462 |
|
462 | |||
463 |
|
463 | |||
464 | def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC: |
|
464 | def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC: | |
465 | """ |
|
465 | """ | |
466 | Rectify a set of completions to all have the same ``start`` and ``end`` |
|
466 | Rectify a set of completions to all have the same ``start`` and ``end`` | |
467 |
|
467 | |||
468 | .. warning:: |
|
468 | .. warning:: | |
469 |
|
469 | |||
470 | Unstable |
|
470 | Unstable | |
471 |
|
471 | |||
472 | This function is unstable, API may change without warning. |
|
472 | This function is unstable, API may change without warning. | |
473 | It will also raise unless use in proper context manager. |
|
473 | It will also raise unless use in proper context manager. | |
474 |
|
474 | |||
475 | Parameters |
|
475 | Parameters | |
476 | ---------- |
|
476 | ---------- | |
477 | text: str |
|
477 | text : str | |
478 | text that should be completed. |
|
478 | text that should be completed. | |
479 | completions: Iterator[Completion] |
|
479 | completions : Iterator[Completion] | |
480 | iterator over the completions to rectify |
|
480 | iterator over the completions to rectify | |
481 |
|
481 | |||
482 | Notes |
|
482 | Notes | |
483 | ----- |
|
483 | ----- | |
484 | :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though |
|
484 | :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though | |
485 | the Jupyter Protocol requires them to behave like so. This will readjust |
|
485 | the Jupyter Protocol requires them to behave like so. This will readjust | |
486 | the completion to have the same ``start`` and ``end`` by padding both |
|
486 | the completion to have the same ``start`` and ``end`` by padding both | |
487 | extremities with surrounding text. |
|
487 | extremities with surrounding text. | |
488 |
|
488 | |||
489 | During stabilisation should support a ``_debug`` option to log which |
|
489 | During stabilisation should support a ``_debug`` option to log which | |
490 | completion are return by the IPython completer and not found in Jedi in |
|
490 | completion are return by the IPython completer and not found in Jedi in | |
491 | order to make upstream bug report. |
|
491 | order to make upstream bug report. | |
492 | """ |
|
492 | """ | |
493 | warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). " |
|
493 | warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). " | |
494 | "It may change without warnings. " |
|
494 | "It may change without warnings. " | |
495 | "Use in corresponding context manager.", |
|
495 | "Use in corresponding context manager.", | |
496 | category=ProvisionalCompleterWarning, stacklevel=2) |
|
496 | category=ProvisionalCompleterWarning, stacklevel=2) | |
497 |
|
497 | |||
498 | completions = list(completions) |
|
498 | completions = list(completions) | |
499 | if not completions: |
|
499 | if not completions: | |
500 | return |
|
500 | return | |
501 | starts = (c.start for c in completions) |
|
501 | starts = (c.start for c in completions) | |
502 | ends = (c.end for c in completions) |
|
502 | ends = (c.end for c in completions) | |
503 |
|
503 | |||
504 | new_start = min(starts) |
|
504 | new_start = min(starts) | |
505 | new_end = max(ends) |
|
505 | new_end = max(ends) | |
506 |
|
506 | |||
507 | seen_jedi = set() |
|
507 | seen_jedi = set() | |
508 | seen_python_matches = set() |
|
508 | seen_python_matches = set() | |
509 | for c in completions: |
|
509 | for c in completions: | |
510 | new_text = text[new_start:c.start] + c.text + text[c.end:new_end] |
|
510 | new_text = text[new_start:c.start] + c.text + text[c.end:new_end] | |
511 | if c._origin == 'jedi': |
|
511 | if c._origin == 'jedi': | |
512 | seen_jedi.add(new_text) |
|
512 | seen_jedi.add(new_text) | |
513 | elif c._origin == 'IPCompleter.python_matches': |
|
513 | elif c._origin == 'IPCompleter.python_matches': | |
514 | seen_python_matches.add(new_text) |
|
514 | seen_python_matches.add(new_text) | |
515 | yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature) |
|
515 | yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature) | |
516 | diff = seen_python_matches.difference(seen_jedi) |
|
516 | diff = seen_python_matches.difference(seen_jedi) | |
517 | if diff and _debug: |
|
517 | if diff and _debug: | |
518 | print('IPython.python matches have extras:', diff) |
|
518 | print('IPython.python matches have extras:', diff) | |
519 |
|
519 | |||
520 |
|
520 | |||
521 | if sys.platform == 'win32': |
|
521 | if sys.platform == 'win32': | |
522 | DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?' |
|
522 | DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?' | |
523 | else: |
|
523 | else: | |
524 | DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' |
|
524 | DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' | |
525 |
|
525 | |||
526 | GREEDY_DELIMS = ' =\r\n' |
|
526 | GREEDY_DELIMS = ' =\r\n' | |
527 |
|
527 | |||
528 |
|
528 | |||
529 | class CompletionSplitter(object): |
|
529 | class CompletionSplitter(object): | |
530 | """An object to split an input line in a manner similar to readline. |
|
530 | """An object to split an input line in a manner similar to readline. | |
531 |
|
531 | |||
532 | By having our own implementation, we can expose readline-like completion in |
|
532 | By having our own implementation, we can expose readline-like completion in | |
533 | a uniform manner to all frontends. This object only needs to be given the |
|
533 | a uniform manner to all frontends. This object only needs to be given the | |
534 | line of text to be split and the cursor position on said line, and it |
|
534 | line of text to be split and the cursor position on said line, and it | |
535 | returns the 'word' to be completed on at the cursor after splitting the |
|
535 | returns the 'word' to be completed on at the cursor after splitting the | |
536 | entire line. |
|
536 | entire line. | |
537 |
|
537 | |||
538 | What characters are used as splitting delimiters can be controlled by |
|
538 | What characters are used as splitting delimiters can be controlled by | |
539 | setting the ``delims`` attribute (this is a property that internally |
|
539 | setting the ``delims`` attribute (this is a property that internally | |
540 | automatically builds the necessary regular expression)""" |
|
540 | automatically builds the necessary regular expression)""" | |
541 |
|
541 | |||
542 | # Private interface |
|
542 | # Private interface | |
543 |
|
543 | |||
544 | # A string of delimiter characters. The default value makes sense for |
|
544 | # A string of delimiter characters. The default value makes sense for | |
545 | # IPython's most typical usage patterns. |
|
545 | # IPython's most typical usage patterns. | |
546 | _delims = DELIMS |
|
546 | _delims = DELIMS | |
547 |
|
547 | |||
548 | # The expression (a normal string) to be compiled into a regular expression |
|
548 | # The expression (a normal string) to be compiled into a regular expression | |
549 | # for actual splitting. We store it as an attribute mostly for ease of |
|
549 | # for actual splitting. We store it as an attribute mostly for ease of | |
550 | # debugging, since this type of code can be so tricky to debug. |
|
550 | # debugging, since this type of code can be so tricky to debug. | |
551 | _delim_expr = None |
|
551 | _delim_expr = None | |
552 |
|
552 | |||
553 | # The regular expression that does the actual splitting |
|
553 | # The regular expression that does the actual splitting | |
554 | _delim_re = None |
|
554 | _delim_re = None | |
555 |
|
555 | |||
556 | def __init__(self, delims=None): |
|
556 | def __init__(self, delims=None): | |
557 | delims = CompletionSplitter._delims if delims is None else delims |
|
557 | delims = CompletionSplitter._delims if delims is None else delims | |
558 | self.delims = delims |
|
558 | self.delims = delims | |
559 |
|
559 | |||
560 | @property |
|
560 | @property | |
561 | def delims(self): |
|
561 | def delims(self): | |
562 | """Return the string of delimiter characters.""" |
|
562 | """Return the string of delimiter characters.""" | |
563 | return self._delims |
|
563 | return self._delims | |
564 |
|
564 | |||
565 | @delims.setter |
|
565 | @delims.setter | |
566 | def delims(self, delims): |
|
566 | def delims(self, delims): | |
567 | """Set the delimiters for line splitting.""" |
|
567 | """Set the delimiters for line splitting.""" | |
568 | expr = '[' + ''.join('\\'+ c for c in delims) + ']' |
|
568 | expr = '[' + ''.join('\\'+ c for c in delims) + ']' | |
569 | self._delim_re = re.compile(expr) |
|
569 | self._delim_re = re.compile(expr) | |
570 | self._delims = delims |
|
570 | self._delims = delims | |
571 | self._delim_expr = expr |
|
571 | self._delim_expr = expr | |
572 |
|
572 | |||
573 | def split_line(self, line, cursor_pos=None): |
|
573 | def split_line(self, line, cursor_pos=None): | |
574 | """Split a line of text with a cursor at the given position. |
|
574 | """Split a line of text with a cursor at the given position. | |
575 | """ |
|
575 | """ | |
576 | l = line if cursor_pos is None else line[:cursor_pos] |
|
576 | l = line if cursor_pos is None else line[:cursor_pos] | |
577 | return self._delim_re.split(l)[-1] |
|
577 | return self._delim_re.split(l)[-1] | |
578 |
|
578 | |||
579 |
|
579 | |||
580 |
|
580 | |||
581 | class Completer(Configurable): |
|
581 | class Completer(Configurable): | |
582 |
|
582 | |||
583 | greedy = Bool(False, |
|
583 | greedy = Bool(False, | |
584 | help="""Activate greedy completion |
|
584 | help="""Activate greedy completion | |
585 | PENDING DEPRECTION. this is now mostly taken care of with Jedi. |
|
585 | PENDING DEPRECTION. this is now mostly taken care of with Jedi. | |
586 |
|
586 | |||
587 | This will enable completion on elements of lists, results of function calls, etc., |
|
587 | This will enable completion on elements of lists, results of function calls, etc., | |
588 | but can be unsafe because the code is actually evaluated on TAB. |
|
588 | but can be unsafe because the code is actually evaluated on TAB. | |
589 | """ |
|
589 | """ | |
590 | ).tag(config=True) |
|
590 | ).tag(config=True) | |
591 |
|
591 | |||
592 | use_jedi = Bool(default_value=JEDI_INSTALLED, |
|
592 | use_jedi = Bool(default_value=JEDI_INSTALLED, | |
593 | help="Experimental: Use Jedi to generate autocompletions. " |
|
593 | help="Experimental: Use Jedi to generate autocompletions. " | |
594 | "Default to True if jedi is installed.").tag(config=True) |
|
594 | "Default to True if jedi is installed.").tag(config=True) | |
595 |
|
595 | |||
596 | jedi_compute_type_timeout = Int(default_value=400, |
|
596 | jedi_compute_type_timeout = Int(default_value=400, | |
597 | help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types. |
|
597 | help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types. | |
598 | Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt |
|
598 | Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt | |
599 | performance by preventing jedi to build its cache. |
|
599 | performance by preventing jedi to build its cache. | |
600 | """).tag(config=True) |
|
600 | """).tag(config=True) | |
601 |
|
601 | |||
602 | debug = Bool(default_value=False, |
|
602 | debug = Bool(default_value=False, | |
603 | help='Enable debug for the Completer. Mostly print extra ' |
|
603 | help='Enable debug for the Completer. Mostly print extra ' | |
604 | 'information for experimental jedi integration.')\ |
|
604 | 'information for experimental jedi integration.')\ | |
605 | .tag(config=True) |
|
605 | .tag(config=True) | |
606 |
|
606 | |||
607 | backslash_combining_completions = Bool(True, |
|
607 | backslash_combining_completions = Bool(True, | |
608 | help="Enable unicode completions, e.g. \\alpha<tab> . " |
|
608 | help="Enable unicode completions, e.g. \\alpha<tab> . " | |
609 | "Includes completion of latex commands, unicode names, and expanding " |
|
609 | "Includes completion of latex commands, unicode names, and expanding " | |
610 | "unicode characters back to latex commands.").tag(config=True) |
|
610 | "unicode characters back to latex commands.").tag(config=True) | |
611 |
|
611 | |||
612 |
|
612 | |||
613 |
|
613 | |||
614 | def __init__(self, namespace=None, global_namespace=None, **kwargs): |
|
614 | def __init__(self, namespace=None, global_namespace=None, **kwargs): | |
615 | """Create a new completer for the command line. |
|
615 | """Create a new completer for the command line. | |
616 |
|
616 | |||
617 | Completer(namespace=ns, global_namespace=ns2) -> completer instance. |
|
617 | Completer(namespace=ns, global_namespace=ns2) -> completer instance. | |
618 |
|
618 | |||
619 | If unspecified, the default namespace where completions are performed |
|
619 | If unspecified, the default namespace where completions are performed | |
620 | is __main__ (technically, __main__.__dict__). Namespaces should be |
|
620 | is __main__ (technically, __main__.__dict__). Namespaces should be | |
621 | given as dictionaries. |
|
621 | given as dictionaries. | |
622 |
|
622 | |||
623 | An optional second namespace can be given. This allows the completer |
|
623 | An optional second namespace can be given. This allows the completer | |
624 | to handle cases where both the local and global scopes need to be |
|
624 | to handle cases where both the local and global scopes need to be | |
625 | distinguished. |
|
625 | distinguished. | |
626 | """ |
|
626 | """ | |
627 |
|
627 | |||
628 | # Don't bind to namespace quite yet, but flag whether the user wants a |
|
628 | # Don't bind to namespace quite yet, but flag whether the user wants a | |
629 | # specific namespace or to use __main__.__dict__. This will allow us |
|
629 | # specific namespace or to use __main__.__dict__. This will allow us | |
630 | # to bind to __main__.__dict__ at completion time, not now. |
|
630 | # to bind to __main__.__dict__ at completion time, not now. | |
631 | if namespace is None: |
|
631 | if namespace is None: | |
632 | self.use_main_ns = True |
|
632 | self.use_main_ns = True | |
633 | else: |
|
633 | else: | |
634 | self.use_main_ns = False |
|
634 | self.use_main_ns = False | |
635 | self.namespace = namespace |
|
635 | self.namespace = namespace | |
636 |
|
636 | |||
637 | # The global namespace, if given, can be bound directly |
|
637 | # The global namespace, if given, can be bound directly | |
638 | if global_namespace is None: |
|
638 | if global_namespace is None: | |
639 | self.global_namespace = {} |
|
639 | self.global_namespace = {} | |
640 | else: |
|
640 | else: | |
641 | self.global_namespace = global_namespace |
|
641 | self.global_namespace = global_namespace | |
642 |
|
642 | |||
643 | self.custom_matchers = [] |
|
643 | self.custom_matchers = [] | |
644 |
|
644 | |||
645 | super(Completer, self).__init__(**kwargs) |
|
645 | super(Completer, self).__init__(**kwargs) | |
646 |
|
646 | |||
647 | def complete(self, text, state): |
|
647 | def complete(self, text, state): | |
648 | """Return the next possible completion for 'text'. |
|
648 | """Return the next possible completion for 'text'. | |
649 |
|
649 | |||
650 | This is called successively with state == 0, 1, 2, ... until it |
|
650 | This is called successively with state == 0, 1, 2, ... until it | |
651 | returns None. The completion should begin with 'text'. |
|
651 | returns None. The completion should begin with 'text'. | |
652 |
|
652 | |||
653 | """ |
|
653 | """ | |
654 | if self.use_main_ns: |
|
654 | if self.use_main_ns: | |
655 | self.namespace = __main__.__dict__ |
|
655 | self.namespace = __main__.__dict__ | |
656 |
|
656 | |||
657 | if state == 0: |
|
657 | if state == 0: | |
658 | if "." in text: |
|
658 | if "." in text: | |
659 | self.matches = self.attr_matches(text) |
|
659 | self.matches = self.attr_matches(text) | |
660 | else: |
|
660 | else: | |
661 | self.matches = self.global_matches(text) |
|
661 | self.matches = self.global_matches(text) | |
662 | try: |
|
662 | try: | |
663 | return self.matches[state] |
|
663 | return self.matches[state] | |
664 | except IndexError: |
|
664 | except IndexError: | |
665 | return None |
|
665 | return None | |
666 |
|
666 | |||
667 | def global_matches(self, text): |
|
667 | def global_matches(self, text): | |
668 | """Compute matches when text is a simple name. |
|
668 | """Compute matches when text is a simple name. | |
669 |
|
669 | |||
670 | Return a list of all keywords, built-in functions and names currently |
|
670 | Return a list of all keywords, built-in functions and names currently | |
671 | defined in self.namespace or self.global_namespace that match. |
|
671 | defined in self.namespace or self.global_namespace that match. | |
672 |
|
672 | |||
673 | """ |
|
673 | """ | |
674 | matches = [] |
|
674 | matches = [] | |
675 | match_append = matches.append |
|
675 | match_append = matches.append | |
676 | n = len(text) |
|
676 | n = len(text) | |
677 | for lst in [keyword.kwlist, |
|
677 | for lst in [keyword.kwlist, | |
678 | builtin_mod.__dict__.keys(), |
|
678 | builtin_mod.__dict__.keys(), | |
679 | self.namespace.keys(), |
|
679 | self.namespace.keys(), | |
680 | self.global_namespace.keys()]: |
|
680 | self.global_namespace.keys()]: | |
681 | for word in lst: |
|
681 | for word in lst: | |
682 | if word[:n] == text and word != "__builtins__": |
|
682 | if word[:n] == text and word != "__builtins__": | |
683 | match_append(word) |
|
683 | match_append(word) | |
684 |
|
684 | |||
685 | snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z") |
|
685 | snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z") | |
686 | for lst in [self.namespace.keys(), |
|
686 | for lst in [self.namespace.keys(), | |
687 | self.global_namespace.keys()]: |
|
687 | self.global_namespace.keys()]: | |
688 | shortened = {"_".join([sub[0] for sub in word.split('_')]) : word |
|
688 | shortened = {"_".join([sub[0] for sub in word.split('_')]) : word | |
689 | for word in lst if snake_case_re.match(word)} |
|
689 | for word in lst if snake_case_re.match(word)} | |
690 | for word in shortened.keys(): |
|
690 | for word in shortened.keys(): | |
691 | if word[:n] == text and word != "__builtins__": |
|
691 | if word[:n] == text and word != "__builtins__": | |
692 | match_append(shortened[word]) |
|
692 | match_append(shortened[word]) | |
693 | return matches |
|
693 | return matches | |
694 |
|
694 | |||
695 | def attr_matches(self, text): |
|
695 | def attr_matches(self, text): | |
696 | """Compute matches when text contains a dot. |
|
696 | """Compute matches when text contains a dot. | |
697 |
|
697 | |||
698 | Assuming the text is of the form NAME.NAME....[NAME], and is |
|
698 | Assuming the text is of the form NAME.NAME....[NAME], and is | |
699 | evaluatable in self.namespace or self.global_namespace, it will be |
|
699 | evaluatable in self.namespace or self.global_namespace, it will be | |
700 | evaluated and its attributes (as revealed by dir()) are used as |
|
700 | evaluated and its attributes (as revealed by dir()) are used as | |
701 | possible completions. (For class instances, class members are |
|
701 | possible completions. (For class instances, class members are | |
702 | also considered.) |
|
702 | also considered.) | |
703 |
|
703 | |||
704 | WARNING: this can still invoke arbitrary C code, if an object |
|
704 | WARNING: this can still invoke arbitrary C code, if an object | |
705 | with a __getattr__ hook is evaluated. |
|
705 | with a __getattr__ hook is evaluated. | |
706 |
|
706 | |||
707 | """ |
|
707 | """ | |
708 |
|
708 | |||
709 | # Another option, seems to work great. Catches things like ''.<tab> |
|
709 | # Another option, seems to work great. Catches things like ''.<tab> | |
710 | m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) |
|
710 | m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) | |
711 |
|
711 | |||
712 | if m: |
|
712 | if m: | |
713 | expr, attr = m.group(1, 3) |
|
713 | expr, attr = m.group(1, 3) | |
714 | elif self.greedy: |
|
714 | elif self.greedy: | |
715 | m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) |
|
715 | m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) | |
716 | if not m2: |
|
716 | if not m2: | |
717 | return [] |
|
717 | return [] | |
718 | expr, attr = m2.group(1,2) |
|
718 | expr, attr = m2.group(1,2) | |
719 | else: |
|
719 | else: | |
720 | return [] |
|
720 | return [] | |
721 |
|
721 | |||
722 | try: |
|
722 | try: | |
723 | obj = eval(expr, self.namespace) |
|
723 | obj = eval(expr, self.namespace) | |
724 | except: |
|
724 | except: | |
725 | try: |
|
725 | try: | |
726 | obj = eval(expr, self.global_namespace) |
|
726 | obj = eval(expr, self.global_namespace) | |
727 | except: |
|
727 | except: | |
728 | return [] |
|
728 | return [] | |
729 |
|
729 | |||
730 | if self.limit_to__all__ and hasattr(obj, '__all__'): |
|
730 | if self.limit_to__all__ and hasattr(obj, '__all__'): | |
731 | words = get__all__entries(obj) |
|
731 | words = get__all__entries(obj) | |
732 | else: |
|
732 | else: | |
733 | words = dir2(obj) |
|
733 | words = dir2(obj) | |
734 |
|
734 | |||
735 | try: |
|
735 | try: | |
736 | words = generics.complete_object(obj, words) |
|
736 | words = generics.complete_object(obj, words) | |
737 | except TryNext: |
|
737 | except TryNext: | |
738 | pass |
|
738 | pass | |
739 | except AssertionError: |
|
739 | except AssertionError: | |
740 | raise |
|
740 | raise | |
741 | except Exception: |
|
741 | except Exception: | |
742 | # Silence errors from completion function |
|
742 | # Silence errors from completion function | |
743 | #raise # dbg |
|
743 | #raise # dbg | |
744 | pass |
|
744 | pass | |
745 | # Build match list to return |
|
745 | # Build match list to return | |
746 | n = len(attr) |
|
746 | n = len(attr) | |
747 | return [u"%s.%s" % (expr, w) for w in words if w[:n] == attr ] |
|
747 | return [u"%s.%s" % (expr, w) for w in words if w[:n] == attr ] | |
748 |
|
748 | |||
749 |
|
749 | |||
750 | def get__all__entries(obj): |
|
750 | def get__all__entries(obj): | |
751 | """returns the strings in the __all__ attribute""" |
|
751 | """returns the strings in the __all__ attribute""" | |
752 | try: |
|
752 | try: | |
753 | words = getattr(obj, '__all__') |
|
753 | words = getattr(obj, '__all__') | |
754 | except: |
|
754 | except: | |
755 | return [] |
|
755 | return [] | |
756 |
|
756 | |||
757 | return [w for w in words if isinstance(w, str)] |
|
757 | return [w for w in words if isinstance(w, str)] | |
758 |
|
758 | |||
759 |
|
759 | |||
760 | def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str, |
|
760 | def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str, | |
761 | extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]: |
|
761 | extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]: | |
762 | """Used by dict_key_matches, matching the prefix to a list of keys |
|
762 | """Used by dict_key_matches, matching the prefix to a list of keys | |
763 |
|
763 | |||
764 | Parameters |
|
764 | Parameters | |
765 | ---------- |
|
765 | ---------- | |
766 |
keys |
|
766 | keys | |
767 | list of keys in dictionary currently being completed. |
|
767 | list of keys in dictionary currently being completed. | |
768 |
prefix |
|
768 | prefix | |
769 | Part of the text already typed by the user. E.g. `mydict[b'fo` |
|
769 | Part of the text already typed by the user. E.g. `mydict[b'fo` | |
770 |
delims |
|
770 | delims | |
771 | String of delimiters to consider when finding the current key. |
|
771 | String of delimiters to consider when finding the current key. | |
772 | extra_prefix: optional |
|
772 | extra_prefix : optional | |
773 | Part of the text already typed in multi-key index cases. E.g. for |
|
773 | Part of the text already typed in multi-key index cases. E.g. for | |
774 | `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`. |
|
774 | `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`. | |
775 |
|
775 | |||
776 | Returns |
|
776 | Returns | |
777 | ------- |
|
777 | ------- | |
778 | A tuple of three elements: ``quote``, ``token_start``, ``matched``, with |
|
778 | A tuple of three elements: ``quote``, ``token_start``, ``matched``, with | |
779 | ``quote`` being the quote that need to be used to close current string. |
|
779 | ``quote`` being the quote that need to be used to close current string. | |
780 | ``token_start`` the position where the replacement should start occurring, |
|
780 | ``token_start`` the position where the replacement should start occurring, | |
781 | ``matches`` a list of replacement/completion |
|
781 | ``matches`` a list of replacement/completion | |
782 |
|
782 | |||
783 | """ |
|
783 | """ | |
784 | prefix_tuple = extra_prefix if extra_prefix else () |
|
784 | prefix_tuple = extra_prefix if extra_prefix else () | |
785 | Nprefix = len(prefix_tuple) |
|
785 | Nprefix = len(prefix_tuple) | |
786 | def filter_prefix_tuple(key): |
|
786 | def filter_prefix_tuple(key): | |
787 | # Reject too short keys |
|
787 | # Reject too short keys | |
788 | if len(key) <= Nprefix: |
|
788 | if len(key) <= Nprefix: | |
789 | return False |
|
789 | return False | |
790 | # Reject keys with non str/bytes in it |
|
790 | # Reject keys with non str/bytes in it | |
791 | for k in key: |
|
791 | for k in key: | |
792 | if not isinstance(k, (str, bytes)): |
|
792 | if not isinstance(k, (str, bytes)): | |
793 | return False |
|
793 | return False | |
794 | # Reject keys that do not match the prefix |
|
794 | # Reject keys that do not match the prefix | |
795 | for k, pt in zip(key, prefix_tuple): |
|
795 | for k, pt in zip(key, prefix_tuple): | |
796 | if k != pt: |
|
796 | if k != pt: | |
797 | return False |
|
797 | return False | |
798 | # All checks passed! |
|
798 | # All checks passed! | |
799 | return True |
|
799 | return True | |
800 |
|
800 | |||
801 | filtered_keys:List[Union[str,bytes]] = [] |
|
801 | filtered_keys:List[Union[str,bytes]] = [] | |
802 | def _add_to_filtered_keys(key): |
|
802 | def _add_to_filtered_keys(key): | |
803 | if isinstance(key, (str, bytes)): |
|
803 | if isinstance(key, (str, bytes)): | |
804 | filtered_keys.append(key) |
|
804 | filtered_keys.append(key) | |
805 |
|
805 | |||
806 | for k in keys: |
|
806 | for k in keys: | |
807 | if isinstance(k, tuple): |
|
807 | if isinstance(k, tuple): | |
808 | if filter_prefix_tuple(k): |
|
808 | if filter_prefix_tuple(k): | |
809 | _add_to_filtered_keys(k[Nprefix]) |
|
809 | _add_to_filtered_keys(k[Nprefix]) | |
810 | else: |
|
810 | else: | |
811 | _add_to_filtered_keys(k) |
|
811 | _add_to_filtered_keys(k) | |
812 |
|
812 | |||
813 | if not prefix: |
|
813 | if not prefix: | |
814 | return '', 0, [repr(k) for k in filtered_keys] |
|
814 | return '', 0, [repr(k) for k in filtered_keys] | |
815 | quote_match = re.search('["\']', prefix) |
|
815 | quote_match = re.search('["\']', prefix) | |
816 | assert quote_match is not None # silence mypy |
|
816 | assert quote_match is not None # silence mypy | |
817 | quote = quote_match.group() |
|
817 | quote = quote_match.group() | |
818 | try: |
|
818 | try: | |
819 | prefix_str = eval(prefix + quote, {}) |
|
819 | prefix_str = eval(prefix + quote, {}) | |
820 | except Exception: |
|
820 | except Exception: | |
821 | return '', 0, [] |
|
821 | return '', 0, [] | |
822 |
|
822 | |||
823 | pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$' |
|
823 | pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$' | |
824 | token_match = re.search(pattern, prefix, re.UNICODE) |
|
824 | token_match = re.search(pattern, prefix, re.UNICODE) | |
825 | assert token_match is not None # silence mypy |
|
825 | assert token_match is not None # silence mypy | |
826 | token_start = token_match.start() |
|
826 | token_start = token_match.start() | |
827 | token_prefix = token_match.group() |
|
827 | token_prefix = token_match.group() | |
828 |
|
828 | |||
829 | matched:List[str] = [] |
|
829 | matched:List[str] = [] | |
830 | for key in filtered_keys: |
|
830 | for key in filtered_keys: | |
831 | try: |
|
831 | try: | |
832 | if not key.startswith(prefix_str): |
|
832 | if not key.startswith(prefix_str): | |
833 | continue |
|
833 | continue | |
834 | except (AttributeError, TypeError, UnicodeError): |
|
834 | except (AttributeError, TypeError, UnicodeError): | |
835 | # Python 3+ TypeError on b'a'.startswith('a') or vice-versa |
|
835 | # Python 3+ TypeError on b'a'.startswith('a') or vice-versa | |
836 | continue |
|
836 | continue | |
837 |
|
837 | |||
838 | # reformat remainder of key to begin with prefix |
|
838 | # reformat remainder of key to begin with prefix | |
839 | rem = key[len(prefix_str):] |
|
839 | rem = key[len(prefix_str):] | |
840 | # force repr wrapped in ' |
|
840 | # force repr wrapped in ' | |
841 | rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"') |
|
841 | rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"') | |
842 | rem_repr = rem_repr[1 + rem_repr.index("'"):-2] |
|
842 | rem_repr = rem_repr[1 + rem_repr.index("'"):-2] | |
843 | if quote == '"': |
|
843 | if quote == '"': | |
844 | # The entered prefix is quoted with ", |
|
844 | # The entered prefix is quoted with ", | |
845 | # but the match is quoted with '. |
|
845 | # but the match is quoted with '. | |
846 | # A contained " hence needs escaping for comparison: |
|
846 | # A contained " hence needs escaping for comparison: | |
847 | rem_repr = rem_repr.replace('"', '\\"') |
|
847 | rem_repr = rem_repr.replace('"', '\\"') | |
848 |
|
848 | |||
849 | # then reinsert prefix from start of token |
|
849 | # then reinsert prefix from start of token | |
850 | matched.append('%s%s' % (token_prefix, rem_repr)) |
|
850 | matched.append('%s%s' % (token_prefix, rem_repr)) | |
851 | return quote, token_start, matched |
|
851 | return quote, token_start, matched | |
852 |
|
852 | |||
853 |
|
853 | |||
854 | def cursor_to_position(text:str, line:int, column:int)->int: |
|
854 | def cursor_to_position(text:str, line:int, column:int)->int: | |
855 | """ |
|
855 | """ | |
856 | Convert the (line,column) position of the cursor in text to an offset in a |
|
856 | Convert the (line,column) position of the cursor in text to an offset in a | |
857 | string. |
|
857 | string. | |
858 |
|
858 | |||
859 | Parameters |
|
859 | Parameters | |
860 | ---------- |
|
860 | ---------- | |
861 | text : str |
|
861 | text : str | |
862 | The text in which to calculate the cursor offset |
|
862 | The text in which to calculate the cursor offset | |
863 | line : int |
|
863 | line : int | |
864 | Line of the cursor; 0-indexed |
|
864 | Line of the cursor; 0-indexed | |
865 | column : int |
|
865 | column : int | |
866 | Column of the cursor 0-indexed |
|
866 | Column of the cursor 0-indexed | |
867 |
|
867 | |||
868 | Returns |
|
868 | Returns | |
869 | ------- |
|
869 | ------- | |
870 | Position of the cursor in ``text``, 0-indexed. |
|
870 | Position of the cursor in ``text``, 0-indexed. | |
871 |
|
871 | |||
872 | See Also |
|
872 | See Also | |
873 | -------- |
|
873 | -------- | |
874 | position_to_cursor : reciprocal of this function |
|
874 | position_to_cursor : reciprocal of this function | |
875 |
|
875 | |||
876 | """ |
|
876 | """ | |
877 | lines = text.split('\n') |
|
877 | lines = text.split('\n') | |
878 | assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines))) |
|
878 | assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines))) | |
879 |
|
879 | |||
880 | return sum(len(l) + 1 for l in lines[:line]) + column |
|
880 | return sum(len(l) + 1 for l in lines[:line]) + column | |
881 |
|
881 | |||
882 | def position_to_cursor(text:str, offset:int)->Tuple[int, int]: |
|
882 | def position_to_cursor(text:str, offset:int)->Tuple[int, int]: | |
883 | """ |
|
883 | """ | |
884 | Convert the position of the cursor in text (0 indexed) to a line |
|
884 | Convert the position of the cursor in text (0 indexed) to a line | |
885 | number(0-indexed) and a column number (0-indexed) pair |
|
885 | number(0-indexed) and a column number (0-indexed) pair | |
886 |
|
886 | |||
887 | Position should be a valid position in ``text``. |
|
887 | Position should be a valid position in ``text``. | |
888 |
|
888 | |||
889 | Parameters |
|
889 | Parameters | |
890 | ---------- |
|
890 | ---------- | |
891 | text : str |
|
891 | text : str | |
892 | The text in which to calculate the cursor offset |
|
892 | The text in which to calculate the cursor offset | |
893 | offset : int |
|
893 | offset : int | |
894 | Position of the cursor in ``text``, 0-indexed. |
|
894 | Position of the cursor in ``text``, 0-indexed. | |
895 |
|
895 | |||
896 | Returns |
|
896 | Returns | |
897 | ------- |
|
897 | ------- | |
898 | (line, column) : (int, int) |
|
898 | (line, column) : (int, int) | |
899 | Line of the cursor; 0-indexed, column of the cursor 0-indexed |
|
899 | Line of the cursor; 0-indexed, column of the cursor 0-indexed | |
900 |
|
900 | |||
901 | See Also |
|
901 | See Also | |
902 | -------- |
|
902 | -------- | |
903 | cursor_to_position : reciprocal of this function |
|
903 | cursor_to_position : reciprocal of this function | |
904 |
|
904 | |||
905 | """ |
|
905 | """ | |
906 |
|
906 | |||
907 | assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text)) |
|
907 | assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text)) | |
908 |
|
908 | |||
909 | before = text[:offset] |
|
909 | before = text[:offset] | |
910 | blines = before.split('\n') # ! splitnes trim trailing \n |
|
910 | blines = before.split('\n') # ! splitnes trim trailing \n | |
911 | line = before.count('\n') |
|
911 | line = before.count('\n') | |
912 | col = len(blines[-1]) |
|
912 | col = len(blines[-1]) | |
913 | return line, col |
|
913 | return line, col | |
914 |
|
914 | |||
915 |
|
915 | |||
916 | def _safe_isinstance(obj, module, class_name): |
|
916 | def _safe_isinstance(obj, module, class_name): | |
917 | """Checks if obj is an instance of module.class_name if loaded |
|
917 | """Checks if obj is an instance of module.class_name if loaded | |
918 | """ |
|
918 | """ | |
919 | return (module in sys.modules and |
|
919 | return (module in sys.modules and | |
920 | isinstance(obj, getattr(import_module(module), class_name))) |
|
920 | isinstance(obj, getattr(import_module(module), class_name))) | |
921 |
|
921 | |||
922 | def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]: |
|
922 | def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]: | |
923 | """Match Unicode characters back to Unicode name |
|
923 | """Match Unicode characters back to Unicode name | |
924 |
|
924 | |||
925 | This does ``☃`` -> ``\\snowman`` |
|
925 | This does ``☃`` -> ``\\snowman`` | |
926 |
|
926 | |||
927 | Note that snowman is not a valid python3 combining character but will be expanded. |
|
927 | Note that snowman is not a valid python3 combining character but will be expanded. | |
928 | Though it will not recombine back to the snowman character by the completion machinery. |
|
928 | Though it will not recombine back to the snowman character by the completion machinery. | |
929 |
|
929 | |||
930 | This will not either back-complete standard sequences like \\n, \\b ... |
|
930 | This will not either back-complete standard sequences like \\n, \\b ... | |
931 |
|
931 | |||
932 | Returns |
|
932 | Returns | |
933 | ======= |
|
933 | ======= | |
934 |
|
934 | |||
935 | Return a tuple with two elements: |
|
935 | Return a tuple with two elements: | |
936 |
|
936 | |||
937 | - The Unicode character that was matched (preceded with a backslash), or |
|
937 | - The Unicode character that was matched (preceded with a backslash), or | |
938 | empty string, |
|
938 | empty string, | |
939 | - a sequence (of 1), name for the match Unicode character, preceded by |
|
939 | - a sequence (of 1), name for the match Unicode character, preceded by | |
940 | backslash, or empty if no match. |
|
940 | backslash, or empty if no match. | |
941 |
|
941 | |||
942 | """ |
|
942 | """ | |
943 | if len(text)<2: |
|
943 | if len(text)<2: | |
944 | return '', () |
|
944 | return '', () | |
945 | maybe_slash = text[-2] |
|
945 | maybe_slash = text[-2] | |
946 | if maybe_slash != '\\': |
|
946 | if maybe_slash != '\\': | |
947 | return '', () |
|
947 | return '', () | |
948 |
|
948 | |||
949 | char = text[-1] |
|
949 | char = text[-1] | |
950 | # no expand on quote for completion in strings. |
|
950 | # no expand on quote for completion in strings. | |
951 | # nor backcomplete standard ascii keys |
|
951 | # nor backcomplete standard ascii keys | |
952 | if char in string.ascii_letters or char in ('"',"'"): |
|
952 | if char in string.ascii_letters or char in ('"',"'"): | |
953 | return '', () |
|
953 | return '', () | |
954 | try : |
|
954 | try : | |
955 | unic = unicodedata.name(char) |
|
955 | unic = unicodedata.name(char) | |
956 | return '\\'+char,('\\'+unic,) |
|
956 | return '\\'+char,('\\'+unic,) | |
957 | except KeyError: |
|
957 | except KeyError: | |
958 | pass |
|
958 | pass | |
959 | return '', () |
|
959 | return '', () | |
960 |
|
960 | |||
961 | def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] : |
|
961 | def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] : | |
962 | """Match latex characters back to unicode name |
|
962 | """Match latex characters back to unicode name | |
963 |
|
963 | |||
964 | This does ``\\ℵ`` -> ``\\aleph`` |
|
964 | This does ``\\ℵ`` -> ``\\aleph`` | |
965 |
|
965 | |||
966 | """ |
|
966 | """ | |
967 | if len(text)<2: |
|
967 | if len(text)<2: | |
968 | return '', () |
|
968 | return '', () | |
969 | maybe_slash = text[-2] |
|
969 | maybe_slash = text[-2] | |
970 | if maybe_slash != '\\': |
|
970 | if maybe_slash != '\\': | |
971 | return '', () |
|
971 | return '', () | |
972 |
|
972 | |||
973 |
|
973 | |||
974 | char = text[-1] |
|
974 | char = text[-1] | |
975 | # no expand on quote for completion in strings. |
|
975 | # no expand on quote for completion in strings. | |
976 | # nor backcomplete standard ascii keys |
|
976 | # nor backcomplete standard ascii keys | |
977 | if char in string.ascii_letters or char in ('"',"'"): |
|
977 | if char in string.ascii_letters or char in ('"',"'"): | |
978 | return '', () |
|
978 | return '', () | |
979 | try : |
|
979 | try : | |
980 | latex = reverse_latex_symbol[char] |
|
980 | latex = reverse_latex_symbol[char] | |
981 | # '\\' replace the \ as well |
|
981 | # '\\' replace the \ as well | |
982 | return '\\'+char,[latex] |
|
982 | return '\\'+char,[latex] | |
983 | except KeyError: |
|
983 | except KeyError: | |
984 | pass |
|
984 | pass | |
985 | return '', () |
|
985 | return '', () | |
986 |
|
986 | |||
987 |
|
987 | |||
988 | def _formatparamchildren(parameter) -> str: |
|
988 | def _formatparamchildren(parameter) -> str: | |
989 | """ |
|
989 | """ | |
990 | Get parameter name and value from Jedi Private API |
|
990 | Get parameter name and value from Jedi Private API | |
991 |
|
991 | |||
992 | Jedi does not expose a simple way to get `param=value` from its API. |
|
992 | Jedi does not expose a simple way to get `param=value` from its API. | |
993 |
|
993 | |||
994 | Parameters |
|
994 | Parameters | |
995 | ---------- |
|
995 | ---------- | |
996 |
parameter |
|
996 | parameter | |
997 | Jedi's function `Param` |
|
997 | Jedi's function `Param` | |
998 |
|
998 | |||
999 | Returns |
|
999 | Returns | |
1000 | ------- |
|
1000 | ------- | |
1001 | A string like 'a', 'b=1', '*args', '**kwargs' |
|
1001 | A string like 'a', 'b=1', '*args', '**kwargs' | |
1002 |
|
1002 | |||
1003 | """ |
|
1003 | """ | |
1004 | description = parameter.description |
|
1004 | description = parameter.description | |
1005 | if not description.startswith('param '): |
|
1005 | if not description.startswith('param '): | |
1006 | raise ValueError('Jedi function parameter description have change format.' |
|
1006 | raise ValueError('Jedi function parameter description have change format.' | |
1007 | 'Expected "param ...", found %r".' % description) |
|
1007 | 'Expected "param ...", found %r".' % description) | |
1008 | return description[6:] |
|
1008 | return description[6:] | |
1009 |
|
1009 | |||
1010 | def _make_signature(completion)-> str: |
|
1010 | def _make_signature(completion)-> str: | |
1011 | """ |
|
1011 | """ | |
1012 | Make the signature from a jedi completion |
|
1012 | Make the signature from a jedi completion | |
1013 |
|
1013 | |||
1014 | Parameters |
|
1014 | Parameters | |
1015 | ---------- |
|
1015 | ---------- | |
1016 | completion: jedi.Completion |
|
1016 | completion : jedi.Completion | |
1017 | object does not complete a function type |
|
1017 | object does not complete a function type | |
1018 |
|
1018 | |||
1019 | Returns |
|
1019 | Returns | |
1020 | ------- |
|
1020 | ------- | |
1021 | a string consisting of the function signature, with the parenthesis but |
|
1021 | a string consisting of the function signature, with the parenthesis but | |
1022 | without the function name. example: |
|
1022 | without the function name. example: | |
1023 | `(a, *args, b=1, **kwargs)` |
|
1023 | `(a, *args, b=1, **kwargs)` | |
1024 |
|
1024 | |||
1025 | """ |
|
1025 | """ | |
1026 |
|
1026 | |||
1027 | # it looks like this might work on jedi 0.17 |
|
1027 | # it looks like this might work on jedi 0.17 | |
1028 | if hasattr(completion, 'get_signatures'): |
|
1028 | if hasattr(completion, 'get_signatures'): | |
1029 | signatures = completion.get_signatures() |
|
1029 | signatures = completion.get_signatures() | |
1030 | if not signatures: |
|
1030 | if not signatures: | |
1031 | return '(?)' |
|
1031 | return '(?)' | |
1032 |
|
1032 | |||
1033 | c0 = completion.get_signatures()[0] |
|
1033 | c0 = completion.get_signatures()[0] | |
1034 | return '('+c0.to_string().split('(', maxsplit=1)[1] |
|
1034 | return '('+c0.to_string().split('(', maxsplit=1)[1] | |
1035 |
|
1035 | |||
1036 | return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures() |
|
1036 | return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures() | |
1037 | for p in signature.defined_names()) if f]) |
|
1037 | for p in signature.defined_names()) if f]) | |
1038 |
|
1038 | |||
1039 |
|
1039 | |||
1040 | class _CompleteResult(NamedTuple): |
|
1040 | class _CompleteResult(NamedTuple): | |
1041 | matched_text : str |
|
1041 | matched_text : str | |
1042 | matches: Sequence[str] |
|
1042 | matches: Sequence[str] | |
1043 | matches_origin: Sequence[str] |
|
1043 | matches_origin: Sequence[str] | |
1044 | jedi_matches: Any |
|
1044 | jedi_matches: Any | |
1045 |
|
1045 | |||
1046 |
|
1046 | |||
1047 | class IPCompleter(Completer): |
|
1047 | class IPCompleter(Completer): | |
1048 | """Extension of the completer class with IPython-specific features""" |
|
1048 | """Extension of the completer class with IPython-specific features""" | |
1049 |
|
1049 | |||
1050 | __dict_key_regexps: Optional[Dict[bool,Pattern]] = None |
|
1050 | __dict_key_regexps: Optional[Dict[bool,Pattern]] = None | |
1051 |
|
1051 | |||
1052 | @observe('greedy') |
|
1052 | @observe('greedy') | |
1053 | def _greedy_changed(self, change): |
|
1053 | def _greedy_changed(self, change): | |
1054 | """update the splitter and readline delims when greedy is changed""" |
|
1054 | """update the splitter and readline delims when greedy is changed""" | |
1055 | if change['new']: |
|
1055 | if change['new']: | |
1056 | self.splitter.delims = GREEDY_DELIMS |
|
1056 | self.splitter.delims = GREEDY_DELIMS | |
1057 | else: |
|
1057 | else: | |
1058 | self.splitter.delims = DELIMS |
|
1058 | self.splitter.delims = DELIMS | |
1059 |
|
1059 | |||
1060 | dict_keys_only = Bool(False, |
|
1060 | dict_keys_only = Bool(False, | |
1061 | help="""Whether to show dict key matches only""") |
|
1061 | help="""Whether to show dict key matches only""") | |
1062 |
|
1062 | |||
1063 | merge_completions = Bool(True, |
|
1063 | merge_completions = Bool(True, | |
1064 | help="""Whether to merge completion results into a single list |
|
1064 | help="""Whether to merge completion results into a single list | |
1065 |
|
1065 | |||
1066 | If False, only the completion results from the first non-empty |
|
1066 | If False, only the completion results from the first non-empty | |
1067 | completer will be returned. |
|
1067 | completer will be returned. | |
1068 | """ |
|
1068 | """ | |
1069 | ).tag(config=True) |
|
1069 | ).tag(config=True) | |
1070 | omit__names = Enum((0,1,2), default_value=2, |
|
1070 | omit__names = Enum((0,1,2), default_value=2, | |
1071 | help="""Instruct the completer to omit private method names |
|
1071 | help="""Instruct the completer to omit private method names | |
1072 |
|
1072 | |||
1073 | Specifically, when completing on ``object.<tab>``. |
|
1073 | Specifically, when completing on ``object.<tab>``. | |
1074 |
|
1074 | |||
1075 | When 2 [default]: all names that start with '_' will be excluded. |
|
1075 | When 2 [default]: all names that start with '_' will be excluded. | |
1076 |
|
1076 | |||
1077 | When 1: all 'magic' names (``__foo__``) will be excluded. |
|
1077 | When 1: all 'magic' names (``__foo__``) will be excluded. | |
1078 |
|
1078 | |||
1079 | When 0: nothing will be excluded. |
|
1079 | When 0: nothing will be excluded. | |
1080 | """ |
|
1080 | """ | |
1081 | ).tag(config=True) |
|
1081 | ).tag(config=True) | |
1082 | limit_to__all__ = Bool(False, |
|
1082 | limit_to__all__ = Bool(False, | |
1083 | help=""" |
|
1083 | help=""" | |
1084 | DEPRECATED as of version 5.0. |
|
1084 | DEPRECATED as of version 5.0. | |
1085 |
|
1085 | |||
1086 | Instruct the completer to use __all__ for the completion |
|
1086 | Instruct the completer to use __all__ for the completion | |
1087 |
|
1087 | |||
1088 | Specifically, when completing on ``object.<tab>``. |
|
1088 | Specifically, when completing on ``object.<tab>``. | |
1089 |
|
1089 | |||
1090 | When True: only those names in obj.__all__ will be included. |
|
1090 | When True: only those names in obj.__all__ will be included. | |
1091 |
|
1091 | |||
1092 | When False [default]: the __all__ attribute is ignored |
|
1092 | When False [default]: the __all__ attribute is ignored | |
1093 | """, |
|
1093 | """, | |
1094 | ).tag(config=True) |
|
1094 | ).tag(config=True) | |
1095 |
|
1095 | |||
1096 | profile_completions = Bool( |
|
1096 | profile_completions = Bool( | |
1097 | default_value=False, |
|
1097 | default_value=False, | |
1098 | help="If True, emit profiling data for completion subsystem using cProfile." |
|
1098 | help="If True, emit profiling data for completion subsystem using cProfile." | |
1099 | ).tag(config=True) |
|
1099 | ).tag(config=True) | |
1100 |
|
1100 | |||
1101 | profiler_output_dir = Unicode( |
|
1101 | profiler_output_dir = Unicode( | |
1102 | default_value=".completion_profiles", |
|
1102 | default_value=".completion_profiles", | |
1103 | help="Template for path at which to output profile data for completions." |
|
1103 | help="Template for path at which to output profile data for completions." | |
1104 | ).tag(config=True) |
|
1104 | ).tag(config=True) | |
1105 |
|
1105 | |||
1106 | @observe('limit_to__all__') |
|
1106 | @observe('limit_to__all__') | |
1107 | def _limit_to_all_changed(self, change): |
|
1107 | def _limit_to_all_changed(self, change): | |
1108 | warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration ' |
|
1108 | warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration ' | |
1109 | 'value has been deprecated since IPython 5.0, will be made to have ' |
|
1109 | 'value has been deprecated since IPython 5.0, will be made to have ' | |
1110 | 'no effects and then removed in future version of IPython.', |
|
1110 | 'no effects and then removed in future version of IPython.', | |
1111 | UserWarning) |
|
1111 | UserWarning) | |
1112 |
|
1112 | |||
1113 | def __init__(self, shell=None, namespace=None, global_namespace=None, |
|
1113 | def __init__(self, shell=None, namespace=None, global_namespace=None, | |
1114 | use_readline=_deprecation_readline_sentinel, config=None, **kwargs): |
|
1114 | use_readline=_deprecation_readline_sentinel, config=None, **kwargs): | |
1115 | """IPCompleter() -> completer |
|
1115 | """IPCompleter() -> completer | |
1116 |
|
1116 | |||
1117 | Return a completer object. |
|
1117 | Return a completer object. | |
1118 |
|
1118 | |||
1119 | Parameters |
|
1119 | Parameters | |
1120 | ---------- |
|
1120 | ---------- | |
1121 | shell |
|
1121 | shell | |
1122 | a pointer to the ipython shell itself. This is needed |
|
1122 | a pointer to the ipython shell itself. This is needed | |
1123 | because this completer knows about magic functions, and those can |
|
1123 | because this completer knows about magic functions, and those can | |
1124 | only be accessed via the ipython instance. |
|
1124 | only be accessed via the ipython instance. | |
1125 | namespace : dict, optional |
|
1125 | namespace : dict, optional | |
1126 | an optional dict where completions are performed. |
|
1126 | an optional dict where completions are performed. | |
1127 | global_namespace : dict, optional |
|
1127 | global_namespace : dict, optional | |
1128 | secondary optional dict for completions, to |
|
1128 | secondary optional dict for completions, to | |
1129 | handle cases (such as IPython embedded inside functions) where |
|
1129 | handle cases (such as IPython embedded inside functions) where | |
1130 | both Python scopes are visible. |
|
1130 | both Python scopes are visible. | |
1131 | use_readline : bool, optional |
|
1131 | use_readline : bool, optional | |
1132 | DEPRECATED, ignored since IPython 6.0, will have no effects |
|
1132 | DEPRECATED, ignored since IPython 6.0, will have no effects | |
1133 | """ |
|
1133 | """ | |
1134 |
|
1134 | |||
1135 | self.magic_escape = ESC_MAGIC |
|
1135 | self.magic_escape = ESC_MAGIC | |
1136 | self.splitter = CompletionSplitter() |
|
1136 | self.splitter = CompletionSplitter() | |
1137 |
|
1137 | |||
1138 | if use_readline is not _deprecation_readline_sentinel: |
|
1138 | if use_readline is not _deprecation_readline_sentinel: | |
1139 | warnings.warn('The `use_readline` parameter is deprecated and ignored since IPython 6.0.', |
|
1139 | warnings.warn('The `use_readline` parameter is deprecated and ignored since IPython 6.0.', | |
1140 | DeprecationWarning, stacklevel=2) |
|
1140 | DeprecationWarning, stacklevel=2) | |
1141 |
|
1141 | |||
1142 | # _greedy_changed() depends on splitter and readline being defined: |
|
1142 | # _greedy_changed() depends on splitter and readline being defined: | |
1143 | Completer.__init__(self, namespace=namespace, global_namespace=global_namespace, |
|
1143 | Completer.__init__(self, namespace=namespace, global_namespace=global_namespace, | |
1144 | config=config, **kwargs) |
|
1144 | config=config, **kwargs) | |
1145 |
|
1145 | |||
1146 | # List where completion matches will be stored |
|
1146 | # List where completion matches will be stored | |
1147 | self.matches = [] |
|
1147 | self.matches = [] | |
1148 | self.shell = shell |
|
1148 | self.shell = shell | |
1149 | # Regexp to split filenames with spaces in them |
|
1149 | # Regexp to split filenames with spaces in them | |
1150 | self.space_name_re = re.compile(r'([^\\] )') |
|
1150 | self.space_name_re = re.compile(r'([^\\] )') | |
1151 | # Hold a local ref. to glob.glob for speed |
|
1151 | # Hold a local ref. to glob.glob for speed | |
1152 | self.glob = glob.glob |
|
1152 | self.glob = glob.glob | |
1153 |
|
1153 | |||
1154 | # Determine if we are running on 'dumb' terminals, like (X)Emacs |
|
1154 | # Determine if we are running on 'dumb' terminals, like (X)Emacs | |
1155 | # buffers, to avoid completion problems. |
|
1155 | # buffers, to avoid completion problems. | |
1156 | term = os.environ.get('TERM','xterm') |
|
1156 | term = os.environ.get('TERM','xterm') | |
1157 | self.dumb_terminal = term in ['dumb','emacs'] |
|
1157 | self.dumb_terminal = term in ['dumb','emacs'] | |
1158 |
|
1158 | |||
1159 | # Special handling of backslashes needed in win32 platforms |
|
1159 | # Special handling of backslashes needed in win32 platforms | |
1160 | if sys.platform == "win32": |
|
1160 | if sys.platform == "win32": | |
1161 | self.clean_glob = self._clean_glob_win32 |
|
1161 | self.clean_glob = self._clean_glob_win32 | |
1162 | else: |
|
1162 | else: | |
1163 | self.clean_glob = self._clean_glob |
|
1163 | self.clean_glob = self._clean_glob | |
1164 |
|
1164 | |||
1165 | #regexp to parse docstring for function signature |
|
1165 | #regexp to parse docstring for function signature | |
1166 | self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') |
|
1166 | self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') | |
1167 | self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') |
|
1167 | self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') | |
1168 | #use this if positional argument name is also needed |
|
1168 | #use this if positional argument name is also needed | |
1169 | #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)') |
|
1169 | #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)') | |
1170 |
|
1170 | |||
1171 | self.magic_arg_matchers = [ |
|
1171 | self.magic_arg_matchers = [ | |
1172 | self.magic_config_matches, |
|
1172 | self.magic_config_matches, | |
1173 | self.magic_color_matches, |
|
1173 | self.magic_color_matches, | |
1174 | ] |
|
1174 | ] | |
1175 |
|
1175 | |||
1176 | # This is set externally by InteractiveShell |
|
1176 | # This is set externally by InteractiveShell | |
1177 | self.custom_completers = None |
|
1177 | self.custom_completers = None | |
1178 |
|
1178 | |||
1179 | # This is a list of names of unicode characters that can be completed |
|
1179 | # This is a list of names of unicode characters that can be completed | |
1180 | # into their corresponding unicode value. The list is large, so we |
|
1180 | # into their corresponding unicode value. The list is large, so we | |
1181 | # laziliy initialize it on first use. Consuming code should access this |
|
1181 | # laziliy initialize it on first use. Consuming code should access this | |
1182 | # attribute through the `@unicode_names` property. |
|
1182 | # attribute through the `@unicode_names` property. | |
1183 | self._unicode_names = None |
|
1183 | self._unicode_names = None | |
1184 |
|
1184 | |||
1185 | @property |
|
1185 | @property | |
1186 | def matchers(self) -> List[Any]: |
|
1186 | def matchers(self) -> List[Any]: | |
1187 | """All active matcher routines for completion""" |
|
1187 | """All active matcher routines for completion""" | |
1188 | if self.dict_keys_only: |
|
1188 | if self.dict_keys_only: | |
1189 | return [self.dict_key_matches] |
|
1189 | return [self.dict_key_matches] | |
1190 |
|
1190 | |||
1191 | if self.use_jedi: |
|
1191 | if self.use_jedi: | |
1192 | return [ |
|
1192 | return [ | |
1193 | *self.custom_matchers, |
|
1193 | *self.custom_matchers, | |
1194 | self.file_matches, |
|
1194 | self.file_matches, | |
1195 | self.magic_matches, |
|
1195 | self.magic_matches, | |
1196 | self.dict_key_matches, |
|
1196 | self.dict_key_matches, | |
1197 | ] |
|
1197 | ] | |
1198 | else: |
|
1198 | else: | |
1199 | return [ |
|
1199 | return [ | |
1200 | *self.custom_matchers, |
|
1200 | *self.custom_matchers, | |
1201 | self.python_matches, |
|
1201 | self.python_matches, | |
1202 | self.file_matches, |
|
1202 | self.file_matches, | |
1203 | self.magic_matches, |
|
1203 | self.magic_matches, | |
1204 | self.python_func_kw_matches, |
|
1204 | self.python_func_kw_matches, | |
1205 | self.dict_key_matches, |
|
1205 | self.dict_key_matches, | |
1206 | ] |
|
1206 | ] | |
1207 |
|
1207 | |||
1208 | def all_completions(self, text:str) -> List[str]: |
|
1208 | def all_completions(self, text:str) -> List[str]: | |
1209 | """ |
|
1209 | """ | |
1210 | Wrapper around the completion methods for the benefit of emacs. |
|
1210 | Wrapper around the completion methods for the benefit of emacs. | |
1211 | """ |
|
1211 | """ | |
1212 | prefix = text.rpartition('.')[0] |
|
1212 | prefix = text.rpartition('.')[0] | |
1213 | with provisionalcompleter(): |
|
1213 | with provisionalcompleter(): | |
1214 | return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text |
|
1214 | return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text | |
1215 | for c in self.completions(text, len(text))] |
|
1215 | for c in self.completions(text, len(text))] | |
1216 |
|
1216 | |||
1217 | return self.complete(text)[1] |
|
1217 | return self.complete(text)[1] | |
1218 |
|
1218 | |||
1219 | def _clean_glob(self, text:str): |
|
1219 | def _clean_glob(self, text:str): | |
1220 | return self.glob("%s*" % text) |
|
1220 | return self.glob("%s*" % text) | |
1221 |
|
1221 | |||
1222 | def _clean_glob_win32(self, text:str): |
|
1222 | def _clean_glob_win32(self, text:str): | |
1223 | return [f.replace("\\","/") |
|
1223 | return [f.replace("\\","/") | |
1224 | for f in self.glob("%s*" % text)] |
|
1224 | for f in self.glob("%s*" % text)] | |
1225 |
|
1225 | |||
1226 | def file_matches(self, text:str)->List[str]: |
|
1226 | def file_matches(self, text:str)->List[str]: | |
1227 | """Match filenames, expanding ~USER type strings. |
|
1227 | """Match filenames, expanding ~USER type strings. | |
1228 |
|
1228 | |||
1229 | Most of the seemingly convoluted logic in this completer is an |
|
1229 | Most of the seemingly convoluted logic in this completer is an | |
1230 | attempt to handle filenames with spaces in them. And yet it's not |
|
1230 | attempt to handle filenames with spaces in them. And yet it's not | |
1231 | quite perfect, because Python's readline doesn't expose all of the |
|
1231 | quite perfect, because Python's readline doesn't expose all of the | |
1232 | GNU readline details needed for this to be done correctly. |
|
1232 | GNU readline details needed for this to be done correctly. | |
1233 |
|
1233 | |||
1234 | For a filename with a space in it, the printed completions will be |
|
1234 | For a filename with a space in it, the printed completions will be | |
1235 | only the parts after what's already been typed (instead of the |
|
1235 | only the parts after what's already been typed (instead of the | |
1236 | full completions, as is normally done). I don't think with the |
|
1236 | full completions, as is normally done). I don't think with the | |
1237 | current (as of Python 2.3) Python readline it's possible to do |
|
1237 | current (as of Python 2.3) Python readline it's possible to do | |
1238 | better.""" |
|
1238 | better.""" | |
1239 |
|
1239 | |||
1240 | # chars that require escaping with backslash - i.e. chars |
|
1240 | # chars that require escaping with backslash - i.e. chars | |
1241 | # that readline treats incorrectly as delimiters, but we |
|
1241 | # that readline treats incorrectly as delimiters, but we | |
1242 | # don't want to treat as delimiters in filename matching |
|
1242 | # don't want to treat as delimiters in filename matching | |
1243 | # when escaped with backslash |
|
1243 | # when escaped with backslash | |
1244 | if text.startswith('!'): |
|
1244 | if text.startswith('!'): | |
1245 | text = text[1:] |
|
1245 | text = text[1:] | |
1246 | text_prefix = u'!' |
|
1246 | text_prefix = u'!' | |
1247 | else: |
|
1247 | else: | |
1248 | text_prefix = u'' |
|
1248 | text_prefix = u'' | |
1249 |
|
1249 | |||
1250 | text_until_cursor = self.text_until_cursor |
|
1250 | text_until_cursor = self.text_until_cursor | |
1251 | # track strings with open quotes |
|
1251 | # track strings with open quotes | |
1252 | open_quotes = has_open_quotes(text_until_cursor) |
|
1252 | open_quotes = has_open_quotes(text_until_cursor) | |
1253 |
|
1253 | |||
1254 | if '(' in text_until_cursor or '[' in text_until_cursor: |
|
1254 | if '(' in text_until_cursor or '[' in text_until_cursor: | |
1255 | lsplit = text |
|
1255 | lsplit = text | |
1256 | else: |
|
1256 | else: | |
1257 | try: |
|
1257 | try: | |
1258 | # arg_split ~ shlex.split, but with unicode bugs fixed by us |
|
1258 | # arg_split ~ shlex.split, but with unicode bugs fixed by us | |
1259 | lsplit = arg_split(text_until_cursor)[-1] |
|
1259 | lsplit = arg_split(text_until_cursor)[-1] | |
1260 | except ValueError: |
|
1260 | except ValueError: | |
1261 | # typically an unmatched ", or backslash without escaped char. |
|
1261 | # typically an unmatched ", or backslash without escaped char. | |
1262 | if open_quotes: |
|
1262 | if open_quotes: | |
1263 | lsplit = text_until_cursor.split(open_quotes)[-1] |
|
1263 | lsplit = text_until_cursor.split(open_quotes)[-1] | |
1264 | else: |
|
1264 | else: | |
1265 | return [] |
|
1265 | return [] | |
1266 | except IndexError: |
|
1266 | except IndexError: | |
1267 | # tab pressed on empty line |
|
1267 | # tab pressed on empty line | |
1268 | lsplit = "" |
|
1268 | lsplit = "" | |
1269 |
|
1269 | |||
1270 | if not open_quotes and lsplit != protect_filename(lsplit): |
|
1270 | if not open_quotes and lsplit != protect_filename(lsplit): | |
1271 | # if protectables are found, do matching on the whole escaped name |
|
1271 | # if protectables are found, do matching on the whole escaped name | |
1272 | has_protectables = True |
|
1272 | has_protectables = True | |
1273 | text0,text = text,lsplit |
|
1273 | text0,text = text,lsplit | |
1274 | else: |
|
1274 | else: | |
1275 | has_protectables = False |
|
1275 | has_protectables = False | |
1276 | text = os.path.expanduser(text) |
|
1276 | text = os.path.expanduser(text) | |
1277 |
|
1277 | |||
1278 | if text == "": |
|
1278 | if text == "": | |
1279 | return [text_prefix + protect_filename(f) for f in self.glob("*")] |
|
1279 | return [text_prefix + protect_filename(f) for f in self.glob("*")] | |
1280 |
|
1280 | |||
1281 | # Compute the matches from the filesystem |
|
1281 | # Compute the matches from the filesystem | |
1282 | if sys.platform == 'win32': |
|
1282 | if sys.platform == 'win32': | |
1283 | m0 = self.clean_glob(text) |
|
1283 | m0 = self.clean_glob(text) | |
1284 | else: |
|
1284 | else: | |
1285 | m0 = self.clean_glob(text.replace('\\', '')) |
|
1285 | m0 = self.clean_glob(text.replace('\\', '')) | |
1286 |
|
1286 | |||
1287 | if has_protectables: |
|
1287 | if has_protectables: | |
1288 | # If we had protectables, we need to revert our changes to the |
|
1288 | # If we had protectables, we need to revert our changes to the | |
1289 | # beginning of filename so that we don't double-write the part |
|
1289 | # beginning of filename so that we don't double-write the part | |
1290 | # of the filename we have so far |
|
1290 | # of the filename we have so far | |
1291 | len_lsplit = len(lsplit) |
|
1291 | len_lsplit = len(lsplit) | |
1292 | matches = [text_prefix + text0 + |
|
1292 | matches = [text_prefix + text0 + | |
1293 | protect_filename(f[len_lsplit:]) for f in m0] |
|
1293 | protect_filename(f[len_lsplit:]) for f in m0] | |
1294 | else: |
|
1294 | else: | |
1295 | if open_quotes: |
|
1295 | if open_quotes: | |
1296 | # if we have a string with an open quote, we don't need to |
|
1296 | # if we have a string with an open quote, we don't need to | |
1297 | # protect the names beyond the quote (and we _shouldn't_, as |
|
1297 | # protect the names beyond the quote (and we _shouldn't_, as | |
1298 | # it would cause bugs when the filesystem call is made). |
|
1298 | # it would cause bugs when the filesystem call is made). | |
1299 | matches = m0 if sys.platform == "win32" else\ |
|
1299 | matches = m0 if sys.platform == "win32" else\ | |
1300 | [protect_filename(f, open_quotes) for f in m0] |
|
1300 | [protect_filename(f, open_quotes) for f in m0] | |
1301 | else: |
|
1301 | else: | |
1302 | matches = [text_prefix + |
|
1302 | matches = [text_prefix + | |
1303 | protect_filename(f) for f in m0] |
|
1303 | protect_filename(f) for f in m0] | |
1304 |
|
1304 | |||
1305 | # Mark directories in input list by appending '/' to their names. |
|
1305 | # Mark directories in input list by appending '/' to their names. | |
1306 | return [x+'/' if os.path.isdir(x) else x for x in matches] |
|
1306 | return [x+'/' if os.path.isdir(x) else x for x in matches] | |
1307 |
|
1307 | |||
1308 | def magic_matches(self, text:str): |
|
1308 | def magic_matches(self, text:str): | |
1309 | """Match magics""" |
|
1309 | """Match magics""" | |
1310 | # Get all shell magics now rather than statically, so magics loaded at |
|
1310 | # Get all shell magics now rather than statically, so magics loaded at | |
1311 | # runtime show up too. |
|
1311 | # runtime show up too. | |
1312 | lsm = self.shell.magics_manager.lsmagic() |
|
1312 | lsm = self.shell.magics_manager.lsmagic() | |
1313 | line_magics = lsm['line'] |
|
1313 | line_magics = lsm['line'] | |
1314 | cell_magics = lsm['cell'] |
|
1314 | cell_magics = lsm['cell'] | |
1315 | pre = self.magic_escape |
|
1315 | pre = self.magic_escape | |
1316 | pre2 = pre+pre |
|
1316 | pre2 = pre+pre | |
1317 |
|
1317 | |||
1318 | explicit_magic = text.startswith(pre) |
|
1318 | explicit_magic = text.startswith(pre) | |
1319 |
|
1319 | |||
1320 | # Completion logic: |
|
1320 | # Completion logic: | |
1321 | # - user gives %%: only do cell magics |
|
1321 | # - user gives %%: only do cell magics | |
1322 | # - user gives %: do both line and cell magics |
|
1322 | # - user gives %: do both line and cell magics | |
1323 | # - no prefix: do both |
|
1323 | # - no prefix: do both | |
1324 | # In other words, line magics are skipped if the user gives %% explicitly |
|
1324 | # In other words, line magics are skipped if the user gives %% explicitly | |
1325 | # |
|
1325 | # | |
1326 | # We also exclude magics that match any currently visible names: |
|
1326 | # We also exclude magics that match any currently visible names: | |
1327 | # https://github.com/ipython/ipython/issues/4877, unless the user has |
|
1327 | # https://github.com/ipython/ipython/issues/4877, unless the user has | |
1328 | # typed a %: |
|
1328 | # typed a %: | |
1329 | # https://github.com/ipython/ipython/issues/10754 |
|
1329 | # https://github.com/ipython/ipython/issues/10754 | |
1330 | bare_text = text.lstrip(pre) |
|
1330 | bare_text = text.lstrip(pre) | |
1331 | global_matches = self.global_matches(bare_text) |
|
1331 | global_matches = self.global_matches(bare_text) | |
1332 | if not explicit_magic: |
|
1332 | if not explicit_magic: | |
1333 | def matches(magic): |
|
1333 | def matches(magic): | |
1334 | """ |
|
1334 | """ | |
1335 | Filter magics, in particular remove magics that match |
|
1335 | Filter magics, in particular remove magics that match | |
1336 | a name present in global namespace. |
|
1336 | a name present in global namespace. | |
1337 | """ |
|
1337 | """ | |
1338 | return ( magic.startswith(bare_text) and |
|
1338 | return ( magic.startswith(bare_text) and | |
1339 | magic not in global_matches ) |
|
1339 | magic not in global_matches ) | |
1340 | else: |
|
1340 | else: | |
1341 | def matches(magic): |
|
1341 | def matches(magic): | |
1342 | return magic.startswith(bare_text) |
|
1342 | return magic.startswith(bare_text) | |
1343 |
|
1343 | |||
1344 | comp = [ pre2+m for m in cell_magics if matches(m)] |
|
1344 | comp = [ pre2+m for m in cell_magics if matches(m)] | |
1345 | if not text.startswith(pre2): |
|
1345 | if not text.startswith(pre2): | |
1346 | comp += [ pre+m for m in line_magics if matches(m)] |
|
1346 | comp += [ pre+m for m in line_magics if matches(m)] | |
1347 |
|
1347 | |||
1348 | return comp |
|
1348 | return comp | |
1349 |
|
1349 | |||
1350 | def magic_config_matches(self, text:str) -> List[str]: |
|
1350 | def magic_config_matches(self, text:str) -> List[str]: | |
1351 | """ Match class names and attributes for %config magic """ |
|
1351 | """ Match class names and attributes for %config magic """ | |
1352 | texts = text.strip().split() |
|
1352 | texts = text.strip().split() | |
1353 |
|
1353 | |||
1354 | if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): |
|
1354 | if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): | |
1355 | # get all configuration classes |
|
1355 | # get all configuration classes | |
1356 | classes = sorted(set([ c for c in self.shell.configurables |
|
1356 | classes = sorted(set([ c for c in self.shell.configurables | |
1357 | if c.__class__.class_traits(config=True) |
|
1357 | if c.__class__.class_traits(config=True) | |
1358 | ]), key=lambda x: x.__class__.__name__) |
|
1358 | ]), key=lambda x: x.__class__.__name__) | |
1359 | classnames = [ c.__class__.__name__ for c in classes ] |
|
1359 | classnames = [ c.__class__.__name__ for c in classes ] | |
1360 |
|
1360 | |||
1361 | # return all classnames if config or %config is given |
|
1361 | # return all classnames if config or %config is given | |
1362 | if len(texts) == 1: |
|
1362 | if len(texts) == 1: | |
1363 | return classnames |
|
1363 | return classnames | |
1364 |
|
1364 | |||
1365 | # match classname |
|
1365 | # match classname | |
1366 | classname_texts = texts[1].split('.') |
|
1366 | classname_texts = texts[1].split('.') | |
1367 | classname = classname_texts[0] |
|
1367 | classname = classname_texts[0] | |
1368 | classname_matches = [ c for c in classnames |
|
1368 | classname_matches = [ c for c in classnames | |
1369 | if c.startswith(classname) ] |
|
1369 | if c.startswith(classname) ] | |
1370 |
|
1370 | |||
1371 | # return matched classes or the matched class with attributes |
|
1371 | # return matched classes or the matched class with attributes | |
1372 | if texts[1].find('.') < 0: |
|
1372 | if texts[1].find('.') < 0: | |
1373 | return classname_matches |
|
1373 | return classname_matches | |
1374 | elif len(classname_matches) == 1 and \ |
|
1374 | elif len(classname_matches) == 1 and \ | |
1375 | classname_matches[0] == classname: |
|
1375 | classname_matches[0] == classname: | |
1376 | cls = classes[classnames.index(classname)].__class__ |
|
1376 | cls = classes[classnames.index(classname)].__class__ | |
1377 | help = cls.class_get_help() |
|
1377 | help = cls.class_get_help() | |
1378 | # strip leading '--' from cl-args: |
|
1378 | # strip leading '--' from cl-args: | |
1379 | help = re.sub(re.compile(r'^--', re.MULTILINE), '', help) |
|
1379 | help = re.sub(re.compile(r'^--', re.MULTILINE), '', help) | |
1380 | return [ attr.split('=')[0] |
|
1380 | return [ attr.split('=')[0] | |
1381 | for attr in help.strip().splitlines() |
|
1381 | for attr in help.strip().splitlines() | |
1382 | if attr.startswith(texts[1]) ] |
|
1382 | if attr.startswith(texts[1]) ] | |
1383 | return [] |
|
1383 | return [] | |
1384 |
|
1384 | |||
1385 | def magic_color_matches(self, text:str) -> List[str] : |
|
1385 | def magic_color_matches(self, text:str) -> List[str] : | |
1386 | """ Match color schemes for %colors magic""" |
|
1386 | """ Match color schemes for %colors magic""" | |
1387 | texts = text.split() |
|
1387 | texts = text.split() | |
1388 | if text.endswith(' '): |
|
1388 | if text.endswith(' '): | |
1389 | # .split() strips off the trailing whitespace. Add '' back |
|
1389 | # .split() strips off the trailing whitespace. Add '' back | |
1390 | # so that: '%colors ' -> ['%colors', ''] |
|
1390 | # so that: '%colors ' -> ['%colors', ''] | |
1391 | texts.append('') |
|
1391 | texts.append('') | |
1392 |
|
1392 | |||
1393 | if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'): |
|
1393 | if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'): | |
1394 | prefix = texts[1] |
|
1394 | prefix = texts[1] | |
1395 | return [ color for color in InspectColors.keys() |
|
1395 | return [ color for color in InspectColors.keys() | |
1396 | if color.startswith(prefix) ] |
|
1396 | if color.startswith(prefix) ] | |
1397 | return [] |
|
1397 | return [] | |
1398 |
|
1398 | |||
1399 | def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]: |
|
1399 | def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]: | |
1400 | """ |
|
1400 | """ | |
1401 | Return a list of :any:`jedi.api.Completions` object from a ``text`` and |
|
1401 | Return a list of :any:`jedi.api.Completions` object from a ``text`` and | |
1402 | cursor position. |
|
1402 | cursor position. | |
1403 |
|
1403 | |||
1404 | Parameters |
|
1404 | Parameters | |
1405 | ---------- |
|
1405 | ---------- | |
1406 | cursor_column : int |
|
1406 | cursor_column : int | |
1407 | column position of the cursor in ``text``, 0-indexed. |
|
1407 | column position of the cursor in ``text``, 0-indexed. | |
1408 | cursor_line : int |
|
1408 | cursor_line : int | |
1409 | line position of the cursor in ``text``, 0-indexed |
|
1409 | line position of the cursor in ``text``, 0-indexed | |
1410 | text : str |
|
1410 | text : str | |
1411 | text to complete |
|
1411 | text to complete | |
1412 |
|
1412 | |||
1413 | Notes |
|
1413 | Notes | |
1414 | ----- |
|
1414 | ----- | |
1415 | If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` |
|
1415 | If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` | |
1416 | object containing a string with the Jedi debug information attached. |
|
1416 | object containing a string with the Jedi debug information attached. | |
1417 | """ |
|
1417 | """ | |
1418 | namespaces = [self.namespace] |
|
1418 | namespaces = [self.namespace] | |
1419 | if self.global_namespace is not None: |
|
1419 | if self.global_namespace is not None: | |
1420 | namespaces.append(self.global_namespace) |
|
1420 | namespaces.append(self.global_namespace) | |
1421 |
|
1421 | |||
1422 | completion_filter = lambda x:x |
|
1422 | completion_filter = lambda x:x | |
1423 | offset = cursor_to_position(text, cursor_line, cursor_column) |
|
1423 | offset = cursor_to_position(text, cursor_line, cursor_column) | |
1424 | # filter output if we are completing for object members |
|
1424 | # filter output if we are completing for object members | |
1425 | if offset: |
|
1425 | if offset: | |
1426 | pre = text[offset-1] |
|
1426 | pre = text[offset-1] | |
1427 | if pre == '.': |
|
1427 | if pre == '.': | |
1428 | if self.omit__names == 2: |
|
1428 | if self.omit__names == 2: | |
1429 | completion_filter = lambda c:not c.name.startswith('_') |
|
1429 | completion_filter = lambda c:not c.name.startswith('_') | |
1430 | elif self.omit__names == 1: |
|
1430 | elif self.omit__names == 1: | |
1431 | completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__')) |
|
1431 | completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__')) | |
1432 | elif self.omit__names == 0: |
|
1432 | elif self.omit__names == 0: | |
1433 | completion_filter = lambda x:x |
|
1433 | completion_filter = lambda x:x | |
1434 | else: |
|
1434 | else: | |
1435 | raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names)) |
|
1435 | raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names)) | |
1436 |
|
1436 | |||
1437 | interpreter = jedi.Interpreter(text[:offset], namespaces) |
|
1437 | interpreter = jedi.Interpreter(text[:offset], namespaces) | |
1438 | try_jedi = True |
|
1438 | try_jedi = True | |
1439 |
|
1439 | |||
1440 | try: |
|
1440 | try: | |
1441 | # find the first token in the current tree -- if it is a ' or " then we are in a string |
|
1441 | # find the first token in the current tree -- if it is a ' or " then we are in a string | |
1442 | completing_string = False |
|
1442 | completing_string = False | |
1443 | try: |
|
1443 | try: | |
1444 | first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value')) |
|
1444 | first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value')) | |
1445 | except StopIteration: |
|
1445 | except StopIteration: | |
1446 | pass |
|
1446 | pass | |
1447 | else: |
|
1447 | else: | |
1448 | # note the value may be ', ", or it may also be ''' or """, or |
|
1448 | # note the value may be ', ", or it may also be ''' or """, or | |
1449 | # in some cases, """what/you/typed..., but all of these are |
|
1449 | # in some cases, """what/you/typed..., but all of these are | |
1450 | # strings. |
|
1450 | # strings. | |
1451 | completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'} |
|
1451 | completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'} | |
1452 |
|
1452 | |||
1453 | # if we are in a string jedi is likely not the right candidate for |
|
1453 | # if we are in a string jedi is likely not the right candidate for | |
1454 | # now. Skip it. |
|
1454 | # now. Skip it. | |
1455 | try_jedi = not completing_string |
|
1455 | try_jedi = not completing_string | |
1456 | except Exception as e: |
|
1456 | except Exception as e: | |
1457 | # many of things can go wrong, we are using private API just don't crash. |
|
1457 | # many of things can go wrong, we are using private API just don't crash. | |
1458 | if self.debug: |
|
1458 | if self.debug: | |
1459 | print("Error detecting if completing a non-finished string :", e, '|') |
|
1459 | print("Error detecting if completing a non-finished string :", e, '|') | |
1460 |
|
1460 | |||
1461 | if not try_jedi: |
|
1461 | if not try_jedi: | |
1462 | return [] |
|
1462 | return [] | |
1463 | try: |
|
1463 | try: | |
1464 | return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1)) |
|
1464 | return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1)) | |
1465 | except Exception as e: |
|
1465 | except Exception as e: | |
1466 | if self.debug: |
|
1466 | if self.debug: | |
1467 | return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))] |
|
1467 | return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))] | |
1468 | else: |
|
1468 | else: | |
1469 | return [] |
|
1469 | return [] | |
1470 |
|
1470 | |||
1471 | def python_matches(self, text:str)->List[str]: |
|
1471 | def python_matches(self, text:str)->List[str]: | |
1472 | """Match attributes or global python names""" |
|
1472 | """Match attributes or global python names""" | |
1473 | if "." in text: |
|
1473 | if "." in text: | |
1474 | try: |
|
1474 | try: | |
1475 | matches = self.attr_matches(text) |
|
1475 | matches = self.attr_matches(text) | |
1476 | if text.endswith('.') and self.omit__names: |
|
1476 | if text.endswith('.') and self.omit__names: | |
1477 | if self.omit__names == 1: |
|
1477 | if self.omit__names == 1: | |
1478 | # true if txt is _not_ a __ name, false otherwise: |
|
1478 | # true if txt is _not_ a __ name, false otherwise: | |
1479 | no__name = (lambda txt: |
|
1479 | no__name = (lambda txt: | |
1480 | re.match(r'.*\.__.*?__',txt) is None) |
|
1480 | re.match(r'.*\.__.*?__',txt) is None) | |
1481 | else: |
|
1481 | else: | |
1482 | # true if txt is _not_ a _ name, false otherwise: |
|
1482 | # true if txt is _not_ a _ name, false otherwise: | |
1483 | no__name = (lambda txt: |
|
1483 | no__name = (lambda txt: | |
1484 | re.match(r'\._.*?',txt[txt.rindex('.'):]) is None) |
|
1484 | re.match(r'\._.*?',txt[txt.rindex('.'):]) is None) | |
1485 | matches = filter(no__name, matches) |
|
1485 | matches = filter(no__name, matches) | |
1486 | except NameError: |
|
1486 | except NameError: | |
1487 | # catches <undefined attributes>.<tab> |
|
1487 | # catches <undefined attributes>.<tab> | |
1488 | matches = [] |
|
1488 | matches = [] | |
1489 | else: |
|
1489 | else: | |
1490 | matches = self.global_matches(text) |
|
1490 | matches = self.global_matches(text) | |
1491 | return matches |
|
1491 | return matches | |
1492 |
|
1492 | |||
1493 | def _default_arguments_from_docstring(self, doc): |
|
1493 | def _default_arguments_from_docstring(self, doc): | |
1494 | """Parse the first line of docstring for call signature. |
|
1494 | """Parse the first line of docstring for call signature. | |
1495 |
|
1495 | |||
1496 | Docstring should be of the form 'min(iterable[, key=func])\n'. |
|
1496 | Docstring should be of the form 'min(iterable[, key=func])\n'. | |
1497 | It can also parse cython docstring of the form |
|
1497 | It can also parse cython docstring of the form | |
1498 | 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'. |
|
1498 | 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'. | |
1499 | """ |
|
1499 | """ | |
1500 | if doc is None: |
|
1500 | if doc is None: | |
1501 | return [] |
|
1501 | return [] | |
1502 |
|
1502 | |||
1503 | #care only the firstline |
|
1503 | #care only the firstline | |
1504 | line = doc.lstrip().splitlines()[0] |
|
1504 | line = doc.lstrip().splitlines()[0] | |
1505 |
|
1505 | |||
1506 | #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') |
|
1506 | #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') | |
1507 | #'min(iterable[, key=func])\n' -> 'iterable[, key=func]' |
|
1507 | #'min(iterable[, key=func])\n' -> 'iterable[, key=func]' | |
1508 | sig = self.docstring_sig_re.search(line) |
|
1508 | sig = self.docstring_sig_re.search(line) | |
1509 | if sig is None: |
|
1509 | if sig is None: | |
1510 | return [] |
|
1510 | return [] | |
1511 | # iterable[, key=func]' -> ['iterable[' ,' key=func]'] |
|
1511 | # iterable[, key=func]' -> ['iterable[' ,' key=func]'] | |
1512 | sig = sig.groups()[0].split(',') |
|
1512 | sig = sig.groups()[0].split(',') | |
1513 | ret = [] |
|
1513 | ret = [] | |
1514 | for s in sig: |
|
1514 | for s in sig: | |
1515 | #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') |
|
1515 | #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') | |
1516 | ret += self.docstring_kwd_re.findall(s) |
|
1516 | ret += self.docstring_kwd_re.findall(s) | |
1517 | return ret |
|
1517 | return ret | |
1518 |
|
1518 | |||
1519 | def _default_arguments(self, obj): |
|
1519 | def _default_arguments(self, obj): | |
1520 | """Return the list of default arguments of obj if it is callable, |
|
1520 | """Return the list of default arguments of obj if it is callable, | |
1521 | or empty list otherwise.""" |
|
1521 | or empty list otherwise.""" | |
1522 | call_obj = obj |
|
1522 | call_obj = obj | |
1523 | ret = [] |
|
1523 | ret = [] | |
1524 | if inspect.isbuiltin(obj): |
|
1524 | if inspect.isbuiltin(obj): | |
1525 | pass |
|
1525 | pass | |
1526 | elif not (inspect.isfunction(obj) or inspect.ismethod(obj)): |
|
1526 | elif not (inspect.isfunction(obj) or inspect.ismethod(obj)): | |
1527 | if inspect.isclass(obj): |
|
1527 | if inspect.isclass(obj): | |
1528 | #for cython embedsignature=True the constructor docstring |
|
1528 | #for cython embedsignature=True the constructor docstring | |
1529 | #belongs to the object itself not __init__ |
|
1529 | #belongs to the object itself not __init__ | |
1530 | ret += self._default_arguments_from_docstring( |
|
1530 | ret += self._default_arguments_from_docstring( | |
1531 | getattr(obj, '__doc__', '')) |
|
1531 | getattr(obj, '__doc__', '')) | |
1532 | # for classes, check for __init__,__new__ |
|
1532 | # for classes, check for __init__,__new__ | |
1533 | call_obj = (getattr(obj, '__init__', None) or |
|
1533 | call_obj = (getattr(obj, '__init__', None) or | |
1534 | getattr(obj, '__new__', None)) |
|
1534 | getattr(obj, '__new__', None)) | |
1535 | # for all others, check if they are __call__able |
|
1535 | # for all others, check if they are __call__able | |
1536 | elif hasattr(obj, '__call__'): |
|
1536 | elif hasattr(obj, '__call__'): | |
1537 | call_obj = obj.__call__ |
|
1537 | call_obj = obj.__call__ | |
1538 | ret += self._default_arguments_from_docstring( |
|
1538 | ret += self._default_arguments_from_docstring( | |
1539 | getattr(call_obj, '__doc__', '')) |
|
1539 | getattr(call_obj, '__doc__', '')) | |
1540 |
|
1540 | |||
1541 | _keeps = (inspect.Parameter.KEYWORD_ONLY, |
|
1541 | _keeps = (inspect.Parameter.KEYWORD_ONLY, | |
1542 | inspect.Parameter.POSITIONAL_OR_KEYWORD) |
|
1542 | inspect.Parameter.POSITIONAL_OR_KEYWORD) | |
1543 |
|
1543 | |||
1544 | try: |
|
1544 | try: | |
1545 | sig = inspect.signature(call_obj) |
|
1545 | sig = inspect.signature(call_obj) | |
1546 | ret.extend(k for k, v in sig.parameters.items() if |
|
1546 | ret.extend(k for k, v in sig.parameters.items() if | |
1547 | v.kind in _keeps) |
|
1547 | v.kind in _keeps) | |
1548 | except ValueError: |
|
1548 | except ValueError: | |
1549 | pass |
|
1549 | pass | |
1550 |
|
1550 | |||
1551 | return list(set(ret)) |
|
1551 | return list(set(ret)) | |
1552 |
|
1552 | |||
1553 | def python_func_kw_matches(self, text): |
|
1553 | def python_func_kw_matches(self, text): | |
1554 | """Match named parameters (kwargs) of the last open function""" |
|
1554 | """Match named parameters (kwargs) of the last open function""" | |
1555 |
|
1555 | |||
1556 | if "." in text: # a parameter cannot be dotted |
|
1556 | if "." in text: # a parameter cannot be dotted | |
1557 | return [] |
|
1557 | return [] | |
1558 | try: regexp = self.__funcParamsRegex |
|
1558 | try: regexp = self.__funcParamsRegex | |
1559 | except AttributeError: |
|
1559 | except AttributeError: | |
1560 | regexp = self.__funcParamsRegex = re.compile(r''' |
|
1560 | regexp = self.__funcParamsRegex = re.compile(r''' | |
1561 | '.*?(?<!\\)' | # single quoted strings or |
|
1561 | '.*?(?<!\\)' | # single quoted strings or | |
1562 | ".*?(?<!\\)" | # double quoted strings or |
|
1562 | ".*?(?<!\\)" | # double quoted strings or | |
1563 | \w+ | # identifier |
|
1563 | \w+ | # identifier | |
1564 | \S # other characters |
|
1564 | \S # other characters | |
1565 | ''', re.VERBOSE | re.DOTALL) |
|
1565 | ''', re.VERBOSE | re.DOTALL) | |
1566 | # 1. find the nearest identifier that comes before an unclosed |
|
1566 | # 1. find the nearest identifier that comes before an unclosed | |
1567 | # parenthesis before the cursor |
|
1567 | # parenthesis before the cursor | |
1568 | # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo" |
|
1568 | # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo" | |
1569 | tokens = regexp.findall(self.text_until_cursor) |
|
1569 | tokens = regexp.findall(self.text_until_cursor) | |
1570 | iterTokens = reversed(tokens); openPar = 0 |
|
1570 | iterTokens = reversed(tokens); openPar = 0 | |
1571 |
|
1571 | |||
1572 | for token in iterTokens: |
|
1572 | for token in iterTokens: | |
1573 | if token == ')': |
|
1573 | if token == ')': | |
1574 | openPar -= 1 |
|
1574 | openPar -= 1 | |
1575 | elif token == '(': |
|
1575 | elif token == '(': | |
1576 | openPar += 1 |
|
1576 | openPar += 1 | |
1577 | if openPar > 0: |
|
1577 | if openPar > 0: | |
1578 | # found the last unclosed parenthesis |
|
1578 | # found the last unclosed parenthesis | |
1579 | break |
|
1579 | break | |
1580 | else: |
|
1580 | else: | |
1581 | return [] |
|
1581 | return [] | |
1582 | # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" ) |
|
1582 | # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" ) | |
1583 | ids = [] |
|
1583 | ids = [] | |
1584 | isId = re.compile(r'\w+$').match |
|
1584 | isId = re.compile(r'\w+$').match | |
1585 |
|
1585 | |||
1586 | while True: |
|
1586 | while True: | |
1587 | try: |
|
1587 | try: | |
1588 | ids.append(next(iterTokens)) |
|
1588 | ids.append(next(iterTokens)) | |
1589 | if not isId(ids[-1]): |
|
1589 | if not isId(ids[-1]): | |
1590 | ids.pop(); break |
|
1590 | ids.pop(); break | |
1591 | if not next(iterTokens) == '.': |
|
1591 | if not next(iterTokens) == '.': | |
1592 | break |
|
1592 | break | |
1593 | except StopIteration: |
|
1593 | except StopIteration: | |
1594 | break |
|
1594 | break | |
1595 |
|
1595 | |||
1596 | # Find all named arguments already assigned to, as to avoid suggesting |
|
1596 | # Find all named arguments already assigned to, as to avoid suggesting | |
1597 | # them again |
|
1597 | # them again | |
1598 | usedNamedArgs = set() |
|
1598 | usedNamedArgs = set() | |
1599 | par_level = -1 |
|
1599 | par_level = -1 | |
1600 | for token, next_token in zip(tokens, tokens[1:]): |
|
1600 | for token, next_token in zip(tokens, tokens[1:]): | |
1601 | if token == '(': |
|
1601 | if token == '(': | |
1602 | par_level += 1 |
|
1602 | par_level += 1 | |
1603 | elif token == ')': |
|
1603 | elif token == ')': | |
1604 | par_level -= 1 |
|
1604 | par_level -= 1 | |
1605 |
|
1605 | |||
1606 | if par_level != 0: |
|
1606 | if par_level != 0: | |
1607 | continue |
|
1607 | continue | |
1608 |
|
1608 | |||
1609 | if next_token != '=': |
|
1609 | if next_token != '=': | |
1610 | continue |
|
1610 | continue | |
1611 |
|
1611 | |||
1612 | usedNamedArgs.add(token) |
|
1612 | usedNamedArgs.add(token) | |
1613 |
|
1613 | |||
1614 | argMatches = [] |
|
1614 | argMatches = [] | |
1615 | try: |
|
1615 | try: | |
1616 | callableObj = '.'.join(ids[::-1]) |
|
1616 | callableObj = '.'.join(ids[::-1]) | |
1617 | namedArgs = self._default_arguments(eval(callableObj, |
|
1617 | namedArgs = self._default_arguments(eval(callableObj, | |
1618 | self.namespace)) |
|
1618 | self.namespace)) | |
1619 |
|
1619 | |||
1620 | # Remove used named arguments from the list, no need to show twice |
|
1620 | # Remove used named arguments from the list, no need to show twice | |
1621 | for namedArg in set(namedArgs) - usedNamedArgs: |
|
1621 | for namedArg in set(namedArgs) - usedNamedArgs: | |
1622 | if namedArg.startswith(text): |
|
1622 | if namedArg.startswith(text): | |
1623 | argMatches.append("%s=" %namedArg) |
|
1623 | argMatches.append("%s=" %namedArg) | |
1624 | except: |
|
1624 | except: | |
1625 | pass |
|
1625 | pass | |
1626 |
|
1626 | |||
1627 | return argMatches |
|
1627 | return argMatches | |
1628 |
|
1628 | |||
1629 | @staticmethod |
|
1629 | @staticmethod | |
1630 | def _get_keys(obj: Any) -> List[Any]: |
|
1630 | def _get_keys(obj: Any) -> List[Any]: | |
1631 | # Objects can define their own completions by defining an |
|
1631 | # Objects can define their own completions by defining an | |
1632 | # _ipy_key_completions_() method. |
|
1632 | # _ipy_key_completions_() method. | |
1633 | method = get_real_method(obj, '_ipython_key_completions_') |
|
1633 | method = get_real_method(obj, '_ipython_key_completions_') | |
1634 | if method is not None: |
|
1634 | if method is not None: | |
1635 | return method() |
|
1635 | return method() | |
1636 |
|
1636 | |||
1637 | # Special case some common in-memory dict-like types |
|
1637 | # Special case some common in-memory dict-like types | |
1638 | if isinstance(obj, dict) or\ |
|
1638 | if isinstance(obj, dict) or\ | |
1639 | _safe_isinstance(obj, 'pandas', 'DataFrame'): |
|
1639 | _safe_isinstance(obj, 'pandas', 'DataFrame'): | |
1640 | try: |
|
1640 | try: | |
1641 | return list(obj.keys()) |
|
1641 | return list(obj.keys()) | |
1642 | except Exception: |
|
1642 | except Exception: | |
1643 | return [] |
|
1643 | return [] | |
1644 | elif _safe_isinstance(obj, 'numpy', 'ndarray') or\ |
|
1644 | elif _safe_isinstance(obj, 'numpy', 'ndarray') or\ | |
1645 | _safe_isinstance(obj, 'numpy', 'void'): |
|
1645 | _safe_isinstance(obj, 'numpy', 'void'): | |
1646 | return obj.dtype.names or [] |
|
1646 | return obj.dtype.names or [] | |
1647 | return [] |
|
1647 | return [] | |
1648 |
|
1648 | |||
1649 | def dict_key_matches(self, text:str) -> List[str]: |
|
1649 | def dict_key_matches(self, text:str) -> List[str]: | |
1650 | "Match string keys in a dictionary, after e.g. 'foo[' " |
|
1650 | "Match string keys in a dictionary, after e.g. 'foo[' " | |
1651 |
|
1651 | |||
1652 |
|
1652 | |||
1653 | if self.__dict_key_regexps is not None: |
|
1653 | if self.__dict_key_regexps is not None: | |
1654 | regexps = self.__dict_key_regexps |
|
1654 | regexps = self.__dict_key_regexps | |
1655 | else: |
|
1655 | else: | |
1656 | dict_key_re_fmt = r'''(?x) |
|
1656 | dict_key_re_fmt = r'''(?x) | |
1657 | ( # match dict-referring expression wrt greedy setting |
|
1657 | ( # match dict-referring expression wrt greedy setting | |
1658 | %s |
|
1658 | %s | |
1659 | ) |
|
1659 | ) | |
1660 | \[ # open bracket |
|
1660 | \[ # open bracket | |
1661 | \s* # and optional whitespace |
|
1661 | \s* # and optional whitespace | |
1662 | # Capture any number of str-like objects (e.g. "a", "b", 'c') |
|
1662 | # Capture any number of str-like objects (e.g. "a", "b", 'c') | |
1663 | ((?:[uUbB]? # string prefix (r not handled) |
|
1663 | ((?:[uUbB]? # string prefix (r not handled) | |
1664 | (?: |
|
1664 | (?: | |
1665 | '(?:[^']|(?<!\\)\\')*' |
|
1665 | '(?:[^']|(?<!\\)\\')*' | |
1666 | | |
|
1666 | | | |
1667 | "(?:[^"]|(?<!\\)\\")*" |
|
1667 | "(?:[^"]|(?<!\\)\\")*" | |
1668 | ) |
|
1668 | ) | |
1669 | \s*,\s* |
|
1669 | \s*,\s* | |
1670 | )*) |
|
1670 | )*) | |
1671 | ([uUbB]? # string prefix (r not handled) |
|
1671 | ([uUbB]? # string prefix (r not handled) | |
1672 | (?: # unclosed string |
|
1672 | (?: # unclosed string | |
1673 | '(?:[^']|(?<!\\)\\')* |
|
1673 | '(?:[^']|(?<!\\)\\')* | |
1674 | | |
|
1674 | | | |
1675 | "(?:[^"]|(?<!\\)\\")* |
|
1675 | "(?:[^"]|(?<!\\)\\")* | |
1676 | ) |
|
1676 | ) | |
1677 | )? |
|
1677 | )? | |
1678 | $ |
|
1678 | $ | |
1679 | ''' |
|
1679 | ''' | |
1680 | regexps = self.__dict_key_regexps = { |
|
1680 | regexps = self.__dict_key_regexps = { | |
1681 | False: re.compile(dict_key_re_fmt % r''' |
|
1681 | False: re.compile(dict_key_re_fmt % r''' | |
1682 | # identifiers separated by . |
|
1682 | # identifiers separated by . | |
1683 | (?!\d)\w+ |
|
1683 | (?!\d)\w+ | |
1684 | (?:\.(?!\d)\w+)* |
|
1684 | (?:\.(?!\d)\w+)* | |
1685 | '''), |
|
1685 | '''), | |
1686 | True: re.compile(dict_key_re_fmt % ''' |
|
1686 | True: re.compile(dict_key_re_fmt % ''' | |
1687 | .+ |
|
1687 | .+ | |
1688 | ''') |
|
1688 | ''') | |
1689 | } |
|
1689 | } | |
1690 |
|
1690 | |||
1691 | match = regexps[self.greedy].search(self.text_until_cursor) |
|
1691 | match = regexps[self.greedy].search(self.text_until_cursor) | |
1692 |
|
1692 | |||
1693 | if match is None: |
|
1693 | if match is None: | |
1694 | return [] |
|
1694 | return [] | |
1695 |
|
1695 | |||
1696 | expr, prefix0, prefix = match.groups() |
|
1696 | expr, prefix0, prefix = match.groups() | |
1697 | try: |
|
1697 | try: | |
1698 | obj = eval(expr, self.namespace) |
|
1698 | obj = eval(expr, self.namespace) | |
1699 | except Exception: |
|
1699 | except Exception: | |
1700 | try: |
|
1700 | try: | |
1701 | obj = eval(expr, self.global_namespace) |
|
1701 | obj = eval(expr, self.global_namespace) | |
1702 | except Exception: |
|
1702 | except Exception: | |
1703 | return [] |
|
1703 | return [] | |
1704 |
|
1704 | |||
1705 | keys = self._get_keys(obj) |
|
1705 | keys = self._get_keys(obj) | |
1706 | if not keys: |
|
1706 | if not keys: | |
1707 | return keys |
|
1707 | return keys | |
1708 |
|
1708 | |||
1709 | extra_prefix = eval(prefix0) if prefix0 != '' else None |
|
1709 | extra_prefix = eval(prefix0) if prefix0 != '' else None | |
1710 |
|
1710 | |||
1711 | closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims, extra_prefix=extra_prefix) |
|
1711 | closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims, extra_prefix=extra_prefix) | |
1712 | if not matches: |
|
1712 | if not matches: | |
1713 | return matches |
|
1713 | return matches | |
1714 |
|
1714 | |||
1715 | # get the cursor position of |
|
1715 | # get the cursor position of | |
1716 | # - the text being completed |
|
1716 | # - the text being completed | |
1717 | # - the start of the key text |
|
1717 | # - the start of the key text | |
1718 | # - the start of the completion |
|
1718 | # - the start of the completion | |
1719 | text_start = len(self.text_until_cursor) - len(text) |
|
1719 | text_start = len(self.text_until_cursor) - len(text) | |
1720 | if prefix: |
|
1720 | if prefix: | |
1721 | key_start = match.start(3) |
|
1721 | key_start = match.start(3) | |
1722 | completion_start = key_start + token_offset |
|
1722 | completion_start = key_start + token_offset | |
1723 | else: |
|
1723 | else: | |
1724 | key_start = completion_start = match.end() |
|
1724 | key_start = completion_start = match.end() | |
1725 |
|
1725 | |||
1726 | # grab the leading prefix, to make sure all completions start with `text` |
|
1726 | # grab the leading prefix, to make sure all completions start with `text` | |
1727 | if text_start > key_start: |
|
1727 | if text_start > key_start: | |
1728 | leading = '' |
|
1728 | leading = '' | |
1729 | else: |
|
1729 | else: | |
1730 | leading = text[text_start:completion_start] |
|
1730 | leading = text[text_start:completion_start] | |
1731 |
|
1731 | |||
1732 | # the index of the `[` character |
|
1732 | # the index of the `[` character | |
1733 | bracket_idx = match.end(1) |
|
1733 | bracket_idx = match.end(1) | |
1734 |
|
1734 | |||
1735 | # append closing quote and bracket as appropriate |
|
1735 | # append closing quote and bracket as appropriate | |
1736 | # this is *not* appropriate if the opening quote or bracket is outside |
|
1736 | # this is *not* appropriate if the opening quote or bracket is outside | |
1737 | # the text given to this method |
|
1737 | # the text given to this method | |
1738 | suf = '' |
|
1738 | suf = '' | |
1739 | continuation = self.line_buffer[len(self.text_until_cursor):] |
|
1739 | continuation = self.line_buffer[len(self.text_until_cursor):] | |
1740 | if key_start > text_start and closing_quote: |
|
1740 | if key_start > text_start and closing_quote: | |
1741 | # quotes were opened inside text, maybe close them |
|
1741 | # quotes were opened inside text, maybe close them | |
1742 | if continuation.startswith(closing_quote): |
|
1742 | if continuation.startswith(closing_quote): | |
1743 | continuation = continuation[len(closing_quote):] |
|
1743 | continuation = continuation[len(closing_quote):] | |
1744 | else: |
|
1744 | else: | |
1745 | suf += closing_quote |
|
1745 | suf += closing_quote | |
1746 | if bracket_idx > text_start: |
|
1746 | if bracket_idx > text_start: | |
1747 | # brackets were opened inside text, maybe close them |
|
1747 | # brackets were opened inside text, maybe close them | |
1748 | if not continuation.startswith(']'): |
|
1748 | if not continuation.startswith(']'): | |
1749 | suf += ']' |
|
1749 | suf += ']' | |
1750 |
|
1750 | |||
1751 | return [leading + k + suf for k in matches] |
|
1751 | return [leading + k + suf for k in matches] | |
1752 |
|
1752 | |||
1753 | @staticmethod |
|
1753 | @staticmethod | |
1754 | def unicode_name_matches(text:str) -> Tuple[str, List[str]] : |
|
1754 | def unicode_name_matches(text:str) -> Tuple[str, List[str]] : | |
1755 | """Match Latex-like syntax for unicode characters base |
|
1755 | """Match Latex-like syntax for unicode characters base | |
1756 | on the name of the character. |
|
1756 | on the name of the character. | |
1757 |
|
1757 | |||
1758 | This does ``\\GREEK SMALL LETTER ETA`` -> ``η`` |
|
1758 | This does ``\\GREEK SMALL LETTER ETA`` -> ``η`` | |
1759 |
|
1759 | |||
1760 | Works only on valid python 3 identifier, or on combining characters that |
|
1760 | Works only on valid python 3 identifier, or on combining characters that | |
1761 | will combine to form a valid identifier. |
|
1761 | will combine to form a valid identifier. | |
1762 | """ |
|
1762 | """ | |
1763 | slashpos = text.rfind('\\') |
|
1763 | slashpos = text.rfind('\\') | |
1764 | if slashpos > -1: |
|
1764 | if slashpos > -1: | |
1765 | s = text[slashpos+1:] |
|
1765 | s = text[slashpos+1:] | |
1766 | try : |
|
1766 | try : | |
1767 | unic = unicodedata.lookup(s) |
|
1767 | unic = unicodedata.lookup(s) | |
1768 | # allow combining chars |
|
1768 | # allow combining chars | |
1769 | if ('a'+unic).isidentifier(): |
|
1769 | if ('a'+unic).isidentifier(): | |
1770 | return '\\'+s,[unic] |
|
1770 | return '\\'+s,[unic] | |
1771 | except KeyError: |
|
1771 | except KeyError: | |
1772 | pass |
|
1772 | pass | |
1773 | return '', [] |
|
1773 | return '', [] | |
1774 |
|
1774 | |||
1775 |
|
1775 | |||
1776 | def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]: |
|
1776 | def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]: | |
1777 | """Match Latex syntax for unicode characters. |
|
1777 | """Match Latex syntax for unicode characters. | |
1778 |
|
1778 | |||
1779 | This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` |
|
1779 | This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` | |
1780 | """ |
|
1780 | """ | |
1781 | slashpos = text.rfind('\\') |
|
1781 | slashpos = text.rfind('\\') | |
1782 | if slashpos > -1: |
|
1782 | if slashpos > -1: | |
1783 | s = text[slashpos:] |
|
1783 | s = text[slashpos:] | |
1784 | if s in latex_symbols: |
|
1784 | if s in latex_symbols: | |
1785 | # Try to complete a full latex symbol to unicode |
|
1785 | # Try to complete a full latex symbol to unicode | |
1786 | # \\alpha -> α |
|
1786 | # \\alpha -> α | |
1787 | return s, [latex_symbols[s]] |
|
1787 | return s, [latex_symbols[s]] | |
1788 | else: |
|
1788 | else: | |
1789 | # If a user has partially typed a latex symbol, give them |
|
1789 | # If a user has partially typed a latex symbol, give them | |
1790 | # a full list of options \al -> [\aleph, \alpha] |
|
1790 | # a full list of options \al -> [\aleph, \alpha] | |
1791 | matches = [k for k in latex_symbols if k.startswith(s)] |
|
1791 | matches = [k for k in latex_symbols if k.startswith(s)] | |
1792 | if matches: |
|
1792 | if matches: | |
1793 | return s, matches |
|
1793 | return s, matches | |
1794 | return '', () |
|
1794 | return '', () | |
1795 |
|
1795 | |||
1796 | def dispatch_custom_completer(self, text): |
|
1796 | def dispatch_custom_completer(self, text): | |
1797 | if not self.custom_completers: |
|
1797 | if not self.custom_completers: | |
1798 | return |
|
1798 | return | |
1799 |
|
1799 | |||
1800 | line = self.line_buffer |
|
1800 | line = self.line_buffer | |
1801 | if not line.strip(): |
|
1801 | if not line.strip(): | |
1802 | return None |
|
1802 | return None | |
1803 |
|
1803 | |||
1804 | # Create a little structure to pass all the relevant information about |
|
1804 | # Create a little structure to pass all the relevant information about | |
1805 | # the current completion to any custom completer. |
|
1805 | # the current completion to any custom completer. | |
1806 | event = SimpleNamespace() |
|
1806 | event = SimpleNamespace() | |
1807 | event.line = line |
|
1807 | event.line = line | |
1808 | event.symbol = text |
|
1808 | event.symbol = text | |
1809 | cmd = line.split(None,1)[0] |
|
1809 | cmd = line.split(None,1)[0] | |
1810 | event.command = cmd |
|
1810 | event.command = cmd | |
1811 | event.text_until_cursor = self.text_until_cursor |
|
1811 | event.text_until_cursor = self.text_until_cursor | |
1812 |
|
1812 | |||
1813 | # for foo etc, try also to find completer for %foo |
|
1813 | # for foo etc, try also to find completer for %foo | |
1814 | if not cmd.startswith(self.magic_escape): |
|
1814 | if not cmd.startswith(self.magic_escape): | |
1815 | try_magic = self.custom_completers.s_matches( |
|
1815 | try_magic = self.custom_completers.s_matches( | |
1816 | self.magic_escape + cmd) |
|
1816 | self.magic_escape + cmd) | |
1817 | else: |
|
1817 | else: | |
1818 | try_magic = [] |
|
1818 | try_magic = [] | |
1819 |
|
1819 | |||
1820 | for c in itertools.chain(self.custom_completers.s_matches(cmd), |
|
1820 | for c in itertools.chain(self.custom_completers.s_matches(cmd), | |
1821 | try_magic, |
|
1821 | try_magic, | |
1822 | self.custom_completers.flat_matches(self.text_until_cursor)): |
|
1822 | self.custom_completers.flat_matches(self.text_until_cursor)): | |
1823 | try: |
|
1823 | try: | |
1824 | res = c(event) |
|
1824 | res = c(event) | |
1825 | if res: |
|
1825 | if res: | |
1826 | # first, try case sensitive match |
|
1826 | # first, try case sensitive match | |
1827 | withcase = [r for r in res if r.startswith(text)] |
|
1827 | withcase = [r for r in res if r.startswith(text)] | |
1828 | if withcase: |
|
1828 | if withcase: | |
1829 | return withcase |
|
1829 | return withcase | |
1830 | # if none, then case insensitive ones are ok too |
|
1830 | # if none, then case insensitive ones are ok too | |
1831 | text_low = text.lower() |
|
1831 | text_low = text.lower() | |
1832 | return [r for r in res if r.lower().startswith(text_low)] |
|
1832 | return [r for r in res if r.lower().startswith(text_low)] | |
1833 | except TryNext: |
|
1833 | except TryNext: | |
1834 | pass |
|
1834 | pass | |
1835 | except KeyboardInterrupt: |
|
1835 | except KeyboardInterrupt: | |
1836 | """ |
|
1836 | """ | |
1837 | If custom completer take too long, |
|
1837 | If custom completer take too long, | |
1838 | let keyboard interrupt abort and return nothing. |
|
1838 | let keyboard interrupt abort and return nothing. | |
1839 | """ |
|
1839 | """ | |
1840 | break |
|
1840 | break | |
1841 |
|
1841 | |||
1842 | return None |
|
1842 | return None | |
1843 |
|
1843 | |||
1844 | def completions(self, text: str, offset: int)->Iterator[Completion]: |
|
1844 | def completions(self, text: str, offset: int)->Iterator[Completion]: | |
1845 | """ |
|
1845 | """ | |
1846 | Returns an iterator over the possible completions |
|
1846 | Returns an iterator over the possible completions | |
1847 |
|
1847 | |||
1848 | .. warning:: |
|
1848 | .. warning:: | |
1849 |
|
1849 | |||
1850 | Unstable |
|
1850 | Unstable | |
1851 |
|
1851 | |||
1852 | This function is unstable, API may change without warning. |
|
1852 | This function is unstable, API may change without warning. | |
1853 | It will also raise unless use in proper context manager. |
|
1853 | It will also raise unless use in proper context manager. | |
1854 |
|
1854 | |||
1855 | Parameters |
|
1855 | Parameters | |
1856 | ---------- |
|
1856 | ---------- | |
1857 | text:str |
|
1857 | text : str | |
1858 | Full text of the current input, multi line string. |
|
1858 | Full text of the current input, multi line string. | |
1859 | offset:int |
|
1859 | offset : int | |
1860 | Integer representing the position of the cursor in ``text``. Offset |
|
1860 | Integer representing the position of the cursor in ``text``. Offset | |
1861 | is 0-based indexed. |
|
1861 | is 0-based indexed. | |
1862 |
|
1862 | |||
1863 | Yields |
|
1863 | Yields | |
1864 | ------ |
|
1864 | ------ | |
1865 | Completion |
|
1865 | Completion | |
1866 |
|
1866 | |||
1867 | Notes |
|
1867 | Notes | |
1868 | ----- |
|
1868 | ----- | |
1869 | The cursor on a text can either be seen as being "in between" |
|
1869 | The cursor on a text can either be seen as being "in between" | |
1870 | characters or "On" a character depending on the interface visible to |
|
1870 | characters or "On" a character depending on the interface visible to | |
1871 | the user. For consistency the cursor being on "in between" characters X |
|
1871 | the user. For consistency the cursor being on "in between" characters X | |
1872 | and Y is equivalent to the cursor being "on" character Y, that is to say |
|
1872 | and Y is equivalent to the cursor being "on" character Y, that is to say | |
1873 | the character the cursor is on is considered as being after the cursor. |
|
1873 | the character the cursor is on is considered as being after the cursor. | |
1874 |
|
1874 | |||
1875 | Combining characters may span more that one position in the |
|
1875 | Combining characters may span more that one position in the | |
1876 | text. |
|
1876 | text. | |
1877 |
|
1877 | |||
1878 | .. note:: |
|
1878 | .. note:: | |
1879 |
|
1879 | |||
1880 | If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--`` |
|
1880 | If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--`` | |
1881 | fake Completion token to distinguish completion returned by Jedi |
|
1881 | fake Completion token to distinguish completion returned by Jedi | |
1882 | and usual IPython completion. |
|
1882 | and usual IPython completion. | |
1883 |
|
1883 | |||
1884 | .. note:: |
|
1884 | .. note:: | |
1885 |
|
1885 | |||
1886 | Completions are not completely deduplicated yet. If identical |
|
1886 | Completions are not completely deduplicated yet. If identical | |
1887 | completions are coming from different sources this function does not |
|
1887 | completions are coming from different sources this function does not | |
1888 | ensure that each completion object will only be present once. |
|
1888 | ensure that each completion object will only be present once. | |
1889 | """ |
|
1889 | """ | |
1890 | warnings.warn("_complete is a provisional API (as of IPython 6.0). " |
|
1890 | warnings.warn("_complete is a provisional API (as of IPython 6.0). " | |
1891 | "It may change without warnings. " |
|
1891 | "It may change without warnings. " | |
1892 | "Use in corresponding context manager.", |
|
1892 | "Use in corresponding context manager.", | |
1893 | category=ProvisionalCompleterWarning, stacklevel=2) |
|
1893 | category=ProvisionalCompleterWarning, stacklevel=2) | |
1894 |
|
1894 | |||
1895 | seen = set() |
|
1895 | seen = set() | |
1896 | profiler:Optional[cProfile.Profile] |
|
1896 | profiler:Optional[cProfile.Profile] | |
1897 | try: |
|
1897 | try: | |
1898 | if self.profile_completions: |
|
1898 | if self.profile_completions: | |
1899 | import cProfile |
|
1899 | import cProfile | |
1900 | profiler = cProfile.Profile() |
|
1900 | profiler = cProfile.Profile() | |
1901 | profiler.enable() |
|
1901 | profiler.enable() | |
1902 | else: |
|
1902 | else: | |
1903 | profiler = None |
|
1903 | profiler = None | |
1904 |
|
1904 | |||
1905 | for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): |
|
1905 | for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): | |
1906 | if c and (c in seen): |
|
1906 | if c and (c in seen): | |
1907 | continue |
|
1907 | continue | |
1908 | yield c |
|
1908 | yield c | |
1909 | seen.add(c) |
|
1909 | seen.add(c) | |
1910 | except KeyboardInterrupt: |
|
1910 | except KeyboardInterrupt: | |
1911 | """if completions take too long and users send keyboard interrupt, |
|
1911 | """if completions take too long and users send keyboard interrupt, | |
1912 | do not crash and return ASAP. """ |
|
1912 | do not crash and return ASAP. """ | |
1913 | pass |
|
1913 | pass | |
1914 | finally: |
|
1914 | finally: | |
1915 | if profiler is not None: |
|
1915 | if profiler is not None: | |
1916 | profiler.disable() |
|
1916 | profiler.disable() | |
1917 | ensure_dir_exists(self.profiler_output_dir) |
|
1917 | ensure_dir_exists(self.profiler_output_dir) | |
1918 | output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4())) |
|
1918 | output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4())) | |
1919 | print("Writing profiler output to", output_path) |
|
1919 | print("Writing profiler output to", output_path) | |
1920 | profiler.dump_stats(output_path) |
|
1920 | profiler.dump_stats(output_path) | |
1921 |
|
1921 | |||
1922 | def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]: |
|
1922 | def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]: | |
1923 | """ |
|
1923 | """ | |
1924 | Core completion module.Same signature as :any:`completions`, with the |
|
1924 | Core completion module.Same signature as :any:`completions`, with the | |
1925 | extra `timeout` parameter (in seconds). |
|
1925 | extra `timeout` parameter (in seconds). | |
1926 |
|
1926 | |||
1927 | Computing jedi's completion ``.type`` can be quite expensive (it is a |
|
1927 | Computing jedi's completion ``.type`` can be quite expensive (it is a | |
1928 | lazy property) and can require some warm-up, more warm up than just |
|
1928 | lazy property) and can require some warm-up, more warm up than just | |
1929 | computing the ``name`` of a completion. The warm-up can be : |
|
1929 | computing the ``name`` of a completion. The warm-up can be : | |
1930 |
|
1930 | |||
1931 | - Long warm-up the first time a module is encountered after |
|
1931 | - Long warm-up the first time a module is encountered after | |
1932 | install/update: actually build parse/inference tree. |
|
1932 | install/update: actually build parse/inference tree. | |
1933 |
|
1933 | |||
1934 | - first time the module is encountered in a session: load tree from |
|
1934 | - first time the module is encountered in a session: load tree from | |
1935 | disk. |
|
1935 | disk. | |
1936 |
|
1936 | |||
1937 | We don't want to block completions for tens of seconds so we give the |
|
1937 | We don't want to block completions for tens of seconds so we give the | |
1938 | completer a "budget" of ``_timeout`` seconds per invocation to compute |
|
1938 | completer a "budget" of ``_timeout`` seconds per invocation to compute | |
1939 | completions types, the completions that have not yet been computed will |
|
1939 | completions types, the completions that have not yet been computed will | |
1940 | be marked as "unknown" an will have a chance to be computed next round |
|
1940 | be marked as "unknown" an will have a chance to be computed next round | |
1941 | are things get cached. |
|
1941 | are things get cached. | |
1942 |
|
1942 | |||
1943 | Keep in mind that Jedi is not the only thing treating the completion so |
|
1943 | Keep in mind that Jedi is not the only thing treating the completion so | |
1944 | keep the timeout short-ish as if we take more than 0.3 second we still |
|
1944 | keep the timeout short-ish as if we take more than 0.3 second we still | |
1945 | have lots of processing to do. |
|
1945 | have lots of processing to do. | |
1946 |
|
1946 | |||
1947 | """ |
|
1947 | """ | |
1948 | deadline = time.monotonic() + _timeout |
|
1948 | deadline = time.monotonic() + _timeout | |
1949 |
|
1949 | |||
1950 |
|
1950 | |||
1951 | before = full_text[:offset] |
|
1951 | before = full_text[:offset] | |
1952 | cursor_line, cursor_column = position_to_cursor(full_text, offset) |
|
1952 | cursor_line, cursor_column = position_to_cursor(full_text, offset) | |
1953 |
|
1953 | |||
1954 | matched_text, matches, matches_origin, jedi_matches = self._complete( |
|
1954 | matched_text, matches, matches_origin, jedi_matches = self._complete( | |
1955 | full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column) |
|
1955 | full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column) | |
1956 |
|
1956 | |||
1957 | iter_jm = iter(jedi_matches) |
|
1957 | iter_jm = iter(jedi_matches) | |
1958 | if _timeout: |
|
1958 | if _timeout: | |
1959 | for jm in iter_jm: |
|
1959 | for jm in iter_jm: | |
1960 | try: |
|
1960 | try: | |
1961 | type_ = jm.type |
|
1961 | type_ = jm.type | |
1962 | except Exception: |
|
1962 | except Exception: | |
1963 | if self.debug: |
|
1963 | if self.debug: | |
1964 | print("Error in Jedi getting type of ", jm) |
|
1964 | print("Error in Jedi getting type of ", jm) | |
1965 | type_ = None |
|
1965 | type_ = None | |
1966 | delta = len(jm.name_with_symbols) - len(jm.complete) |
|
1966 | delta = len(jm.name_with_symbols) - len(jm.complete) | |
1967 | if type_ == 'function': |
|
1967 | if type_ == 'function': | |
1968 | signature = _make_signature(jm) |
|
1968 | signature = _make_signature(jm) | |
1969 | else: |
|
1969 | else: | |
1970 | signature = '' |
|
1970 | signature = '' | |
1971 | yield Completion(start=offset - delta, |
|
1971 | yield Completion(start=offset - delta, | |
1972 | end=offset, |
|
1972 | end=offset, | |
1973 | text=jm.name_with_symbols, |
|
1973 | text=jm.name_with_symbols, | |
1974 | type=type_, |
|
1974 | type=type_, | |
1975 | signature=signature, |
|
1975 | signature=signature, | |
1976 | _origin='jedi') |
|
1976 | _origin='jedi') | |
1977 |
|
1977 | |||
1978 | if time.monotonic() > deadline: |
|
1978 | if time.monotonic() > deadline: | |
1979 | break |
|
1979 | break | |
1980 |
|
1980 | |||
1981 | for jm in iter_jm: |
|
1981 | for jm in iter_jm: | |
1982 | delta = len(jm.name_with_symbols) - len(jm.complete) |
|
1982 | delta = len(jm.name_with_symbols) - len(jm.complete) | |
1983 | yield Completion(start=offset - delta, |
|
1983 | yield Completion(start=offset - delta, | |
1984 | end=offset, |
|
1984 | end=offset, | |
1985 | text=jm.name_with_symbols, |
|
1985 | text=jm.name_with_symbols, | |
1986 | type='<unknown>', # don't compute type for speed |
|
1986 | type='<unknown>', # don't compute type for speed | |
1987 | _origin='jedi', |
|
1987 | _origin='jedi', | |
1988 | signature='') |
|
1988 | signature='') | |
1989 |
|
1989 | |||
1990 |
|
1990 | |||
1991 | start_offset = before.rfind(matched_text) |
|
1991 | start_offset = before.rfind(matched_text) | |
1992 |
|
1992 | |||
1993 | # TODO: |
|
1993 | # TODO: | |
1994 | # Suppress this, right now just for debug. |
|
1994 | # Suppress this, right now just for debug. | |
1995 | if jedi_matches and matches and self.debug: |
|
1995 | if jedi_matches and matches and self.debug: | |
1996 | yield Completion(start=start_offset, end=offset, text='--jedi/ipython--', |
|
1996 | yield Completion(start=start_offset, end=offset, text='--jedi/ipython--', | |
1997 | _origin='debug', type='none', signature='') |
|
1997 | _origin='debug', type='none', signature='') | |
1998 |
|
1998 | |||
1999 | # I'm unsure if this is always true, so let's assert and see if it |
|
1999 | # I'm unsure if this is always true, so let's assert and see if it | |
2000 | # crash |
|
2000 | # crash | |
2001 | assert before.endswith(matched_text) |
|
2001 | assert before.endswith(matched_text) | |
2002 | for m, t in zip(matches, matches_origin): |
|
2002 | for m, t in zip(matches, matches_origin): | |
2003 | yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>') |
|
2003 | yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>') | |
2004 |
|
2004 | |||
2005 |
|
2005 | |||
2006 | def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: |
|
2006 | def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: | |
2007 | """Find completions for the given text and line context. |
|
2007 | """Find completions for the given text and line context. | |
2008 |
|
2008 | |||
2009 | Note that both the text and the line_buffer are optional, but at least |
|
2009 | Note that both the text and the line_buffer are optional, but at least | |
2010 | one of them must be given. |
|
2010 | one of them must be given. | |
2011 |
|
2011 | |||
2012 | Parameters |
|
2012 | Parameters | |
2013 | ---------- |
|
2013 | ---------- | |
2014 | text : string, optional |
|
2014 | text : string, optional | |
2015 | Text to perform the completion on. If not given, the line buffer |
|
2015 | Text to perform the completion on. If not given, the line buffer | |
2016 | is split using the instance's CompletionSplitter object. |
|
2016 | is split using the instance's CompletionSplitter object. | |
2017 | line_buffer : string, optional |
|
2017 | line_buffer : string, optional | |
2018 | If not given, the completer attempts to obtain the current line |
|
2018 | If not given, the completer attempts to obtain the current line | |
2019 | buffer via readline. This keyword allows clients which are |
|
2019 | buffer via readline. This keyword allows clients which are | |
2020 | requesting for text completions in non-readline contexts to inform |
|
2020 | requesting for text completions in non-readline contexts to inform | |
2021 | the completer of the entire text. |
|
2021 | the completer of the entire text. | |
2022 | cursor_pos : int, optional |
|
2022 | cursor_pos : int, optional | |
2023 | Index of the cursor in the full line buffer. Should be provided by |
|
2023 | Index of the cursor in the full line buffer. Should be provided by | |
2024 | remote frontends where kernel has no access to frontend state. |
|
2024 | remote frontends where kernel has no access to frontend state. | |
2025 |
|
2025 | |||
2026 | Returns |
|
2026 | Returns | |
2027 | ------- |
|
2027 | ------- | |
2028 | Tuple of two items: |
|
2028 | Tuple of two items: | |
2029 | text : str |
|
2029 | text : str | |
2030 | Text that was actually used in the completion. |
|
2030 | Text that was actually used in the completion. | |
2031 | matches : list |
|
2031 | matches : list | |
2032 | A list of completion matches. |
|
2032 | A list of completion matches. | |
2033 |
|
2033 | |||
2034 | Notes |
|
2034 | Notes | |
2035 | ----- |
|
2035 | ----- | |
2036 | This API is likely to be deprecated and replaced by |
|
2036 | This API is likely to be deprecated and replaced by | |
2037 | :any:`IPCompleter.completions` in the future. |
|
2037 | :any:`IPCompleter.completions` in the future. | |
2038 |
|
2038 | |||
2039 | """ |
|
2039 | """ | |
2040 | warnings.warn('`Completer.complete` is pending deprecation since ' |
|
2040 | warnings.warn('`Completer.complete` is pending deprecation since ' | |
2041 | 'IPython 6.0 and will be replaced by `Completer.completions`.', |
|
2041 | 'IPython 6.0 and will be replaced by `Completer.completions`.', | |
2042 | PendingDeprecationWarning) |
|
2042 | PendingDeprecationWarning) | |
2043 | # potential todo, FOLD the 3rd throw away argument of _complete |
|
2043 | # potential todo, FOLD the 3rd throw away argument of _complete | |
2044 | # into the first 2 one. |
|
2044 | # into the first 2 one. | |
2045 | return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2] |
|
2045 | return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2] | |
2046 |
|
2046 | |||
2047 | def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, |
|
2047 | def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, | |
2048 | full_text=None) -> _CompleteResult: |
|
2048 | full_text=None) -> _CompleteResult: | |
2049 | """ |
|
2049 | """ | |
2050 | Like complete but can also returns raw jedi completions as well as the |
|
2050 | Like complete but can also returns raw jedi completions as well as the | |
2051 | origin of the completion text. This could (and should) be made much |
|
2051 | origin of the completion text. This could (and should) be made much | |
2052 | cleaner but that will be simpler once we drop the old (and stateful) |
|
2052 | cleaner but that will be simpler once we drop the old (and stateful) | |
2053 | :any:`complete` API. |
|
2053 | :any:`complete` API. | |
2054 |
|
2054 | |||
2055 | With current provisional API, cursor_pos act both (depending on the |
|
2055 | With current provisional API, cursor_pos act both (depending on the | |
2056 | caller) as the offset in the ``text`` or ``line_buffer``, or as the |
|
2056 | caller) as the offset in the ``text`` or ``line_buffer``, or as the | |
2057 | ``column`` when passing multiline strings this could/should be renamed |
|
2057 | ``column`` when passing multiline strings this could/should be renamed | |
2058 | but would add extra noise. |
|
2058 | but would add extra noise. | |
2059 |
|
2059 | |||
2060 | Returns |
|
2060 | Returns | |
2061 | ------- |
|
2061 | ------- | |
2062 | A tuple of N elements which are (likely): |
|
2062 | A tuple of N elements which are (likely): | |
2063 | matched_text: ? the text that the complete matched |
|
2063 | matched_text: ? the text that the complete matched | |
2064 | matches: list of completions ? |
|
2064 | matches: list of completions ? | |
2065 | matches_origin: ? list same lenght as matches, and where each completion came from |
|
2065 | matches_origin: ? list same lenght as matches, and where each completion came from | |
2066 | jedi_matches: list of Jedi matches, have it's own structure. |
|
2066 | jedi_matches: list of Jedi matches, have it's own structure. | |
2067 | """ |
|
2067 | """ | |
2068 |
|
2068 | |||
2069 |
|
2069 | |||
2070 | # if the cursor position isn't given, the only sane assumption we can |
|
2070 | # if the cursor position isn't given, the only sane assumption we can | |
2071 | # make is that it's at the end of the line (the common case) |
|
2071 | # make is that it's at the end of the line (the common case) | |
2072 | if cursor_pos is None: |
|
2072 | if cursor_pos is None: | |
2073 | cursor_pos = len(line_buffer) if text is None else len(text) |
|
2073 | cursor_pos = len(line_buffer) if text is None else len(text) | |
2074 |
|
2074 | |||
2075 | if self.use_main_ns: |
|
2075 | if self.use_main_ns: | |
2076 | self.namespace = __main__.__dict__ |
|
2076 | self.namespace = __main__.__dict__ | |
2077 |
|
2077 | |||
2078 | # if text is either None or an empty string, rely on the line buffer |
|
2078 | # if text is either None or an empty string, rely on the line buffer | |
2079 | if (not line_buffer) and full_text: |
|
2079 | if (not line_buffer) and full_text: | |
2080 | line_buffer = full_text.split('\n')[cursor_line] |
|
2080 | line_buffer = full_text.split('\n')[cursor_line] | |
2081 | if not text: # issue #11508: check line_buffer before calling split_line |
|
2081 | if not text: # issue #11508: check line_buffer before calling split_line | |
2082 | text = self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else '' |
|
2082 | text = self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else '' | |
2083 |
|
2083 | |||
2084 | if self.backslash_combining_completions: |
|
2084 | if self.backslash_combining_completions: | |
2085 | # allow deactivation of these on windows. |
|
2085 | # allow deactivation of these on windows. | |
2086 | base_text = text if not line_buffer else line_buffer[:cursor_pos] |
|
2086 | base_text = text if not line_buffer else line_buffer[:cursor_pos] | |
2087 |
|
2087 | |||
2088 | for meth in (self.latex_matches, |
|
2088 | for meth in (self.latex_matches, | |
2089 | self.unicode_name_matches, |
|
2089 | self.unicode_name_matches, | |
2090 | back_latex_name_matches, |
|
2090 | back_latex_name_matches, | |
2091 | back_unicode_name_matches, |
|
2091 | back_unicode_name_matches, | |
2092 | self.fwd_unicode_match): |
|
2092 | self.fwd_unicode_match): | |
2093 | name_text, name_matches = meth(base_text) |
|
2093 | name_text, name_matches = meth(base_text) | |
2094 | if name_text: |
|
2094 | if name_text: | |
2095 | return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \ |
|
2095 | return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \ | |
2096 | [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ()) |
|
2096 | [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ()) | |
2097 |
|
2097 | |||
2098 |
|
2098 | |||
2099 | # If no line buffer is given, assume the input text is all there was |
|
2099 | # If no line buffer is given, assume the input text is all there was | |
2100 | if line_buffer is None: |
|
2100 | if line_buffer is None: | |
2101 | line_buffer = text |
|
2101 | line_buffer = text | |
2102 |
|
2102 | |||
2103 | self.line_buffer = line_buffer |
|
2103 | self.line_buffer = line_buffer | |
2104 | self.text_until_cursor = self.line_buffer[:cursor_pos] |
|
2104 | self.text_until_cursor = self.line_buffer[:cursor_pos] | |
2105 |
|
2105 | |||
2106 | # Do magic arg matches |
|
2106 | # Do magic arg matches | |
2107 | for matcher in self.magic_arg_matchers: |
|
2107 | for matcher in self.magic_arg_matchers: | |
2108 | matches = list(matcher(line_buffer))[:MATCHES_LIMIT] |
|
2108 | matches = list(matcher(line_buffer))[:MATCHES_LIMIT] | |
2109 | if matches: |
|
2109 | if matches: | |
2110 | origins = [matcher.__qualname__] * len(matches) |
|
2110 | origins = [matcher.__qualname__] * len(matches) | |
2111 | return _CompleteResult(text, matches, origins, ()) |
|
2111 | return _CompleteResult(text, matches, origins, ()) | |
2112 |
|
2112 | |||
2113 | # Start with a clean slate of completions |
|
2113 | # Start with a clean slate of completions | |
2114 | matches = [] |
|
2114 | matches = [] | |
2115 |
|
2115 | |||
2116 | # FIXME: we should extend our api to return a dict with completions for |
|
2116 | # FIXME: we should extend our api to return a dict with completions for | |
2117 | # different types of objects. The rlcomplete() method could then |
|
2117 | # different types of objects. The rlcomplete() method could then | |
2118 | # simply collapse the dict into a list for readline, but we'd have |
|
2118 | # simply collapse the dict into a list for readline, but we'd have | |
2119 | # richer completion semantics in other environments. |
|
2119 | # richer completion semantics in other environments. | |
2120 | completions:Iterable[Any] = [] |
|
2120 | completions:Iterable[Any] = [] | |
2121 | if self.use_jedi: |
|
2121 | if self.use_jedi: | |
2122 | if not full_text: |
|
2122 | if not full_text: | |
2123 | full_text = line_buffer |
|
2123 | full_text = line_buffer | |
2124 | completions = self._jedi_matches( |
|
2124 | completions = self._jedi_matches( | |
2125 | cursor_pos, cursor_line, full_text) |
|
2125 | cursor_pos, cursor_line, full_text) | |
2126 |
|
2126 | |||
2127 | if self.merge_completions: |
|
2127 | if self.merge_completions: | |
2128 | matches = [] |
|
2128 | matches = [] | |
2129 | for matcher in self.matchers: |
|
2129 | for matcher in self.matchers: | |
2130 | try: |
|
2130 | try: | |
2131 | matches.extend([(m, matcher.__qualname__) |
|
2131 | matches.extend([(m, matcher.__qualname__) | |
2132 | for m in matcher(text)]) |
|
2132 | for m in matcher(text)]) | |
2133 | except: |
|
2133 | except: | |
2134 | # Show the ugly traceback if the matcher causes an |
|
2134 | # Show the ugly traceback if the matcher causes an | |
2135 | # exception, but do NOT crash the kernel! |
|
2135 | # exception, but do NOT crash the kernel! | |
2136 | sys.excepthook(*sys.exc_info()) |
|
2136 | sys.excepthook(*sys.exc_info()) | |
2137 | else: |
|
2137 | else: | |
2138 | for matcher in self.matchers: |
|
2138 | for matcher in self.matchers: | |
2139 | matches = [(m, matcher.__qualname__) |
|
2139 | matches = [(m, matcher.__qualname__) | |
2140 | for m in matcher(text)] |
|
2140 | for m in matcher(text)] | |
2141 | if matches: |
|
2141 | if matches: | |
2142 | break |
|
2142 | break | |
2143 |
|
2143 | |||
2144 | seen = set() |
|
2144 | seen = set() | |
2145 | filtered_matches = set() |
|
2145 | filtered_matches = set() | |
2146 | for m in matches: |
|
2146 | for m in matches: | |
2147 | t, c = m |
|
2147 | t, c = m | |
2148 | if t not in seen: |
|
2148 | if t not in seen: | |
2149 | filtered_matches.add(m) |
|
2149 | filtered_matches.add(m) | |
2150 | seen.add(t) |
|
2150 | seen.add(t) | |
2151 |
|
2151 | |||
2152 | _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0])) |
|
2152 | _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0])) | |
2153 |
|
2153 | |||
2154 | custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []] |
|
2154 | custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []] | |
2155 |
|
2155 | |||
2156 | _filtered_matches = custom_res or _filtered_matches |
|
2156 | _filtered_matches = custom_res or _filtered_matches | |
2157 |
|
2157 | |||
2158 | _filtered_matches = _filtered_matches[:MATCHES_LIMIT] |
|
2158 | _filtered_matches = _filtered_matches[:MATCHES_LIMIT] | |
2159 | _matches = [m[0] for m in _filtered_matches] |
|
2159 | _matches = [m[0] for m in _filtered_matches] | |
2160 | origins = [m[1] for m in _filtered_matches] |
|
2160 | origins = [m[1] for m in _filtered_matches] | |
2161 |
|
2161 | |||
2162 | self.matches = _matches |
|
2162 | self.matches = _matches | |
2163 |
|
2163 | |||
2164 | return _CompleteResult(text, _matches, origins, completions) |
|
2164 | return _CompleteResult(text, _matches, origins, completions) | |
2165 |
|
2165 | |||
2166 | def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]: |
|
2166 | def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]: | |
2167 | """ |
|
2167 | """ | |
2168 | Forward match a string starting with a backslash with a list of |
|
2168 | Forward match a string starting with a backslash with a list of | |
2169 | potential Unicode completions. |
|
2169 | potential Unicode completions. | |
2170 |
|
2170 | |||
2171 | Will compute list list of Unicode character names on first call and cache it. |
|
2171 | Will compute list list of Unicode character names on first call and cache it. | |
2172 |
|
2172 | |||
2173 | Returns |
|
2173 | Returns | |
2174 | ------- |
|
2174 | ------- | |
2175 | At tuple with: |
|
2175 | At tuple with: | |
2176 | - matched text (empty if no matches) |
|
2176 | - matched text (empty if no matches) | |
2177 | - list of potential completions, empty tuple otherwise) |
|
2177 | - list of potential completions, empty tuple otherwise) | |
2178 | """ |
|
2178 | """ | |
2179 | # TODO: self.unicode_names is here a list we traverse each time with ~100k elements. |
|
2179 | # TODO: self.unicode_names is here a list we traverse each time with ~100k elements. | |
2180 | # We could do a faster match using a Trie. |
|
2180 | # We could do a faster match using a Trie. | |
2181 |
|
2181 | |||
2182 | # Using pygtrie the follwing seem to work: |
|
2182 | # Using pygtrie the follwing seem to work: | |
2183 |
|
2183 | |||
2184 | # s = PrefixSet() |
|
2184 | # s = PrefixSet() | |
2185 |
|
2185 | |||
2186 | # for c in range(0,0x10FFFF + 1): |
|
2186 | # for c in range(0,0x10FFFF + 1): | |
2187 | # try: |
|
2187 | # try: | |
2188 | # s.add(unicodedata.name(chr(c))) |
|
2188 | # s.add(unicodedata.name(chr(c))) | |
2189 | # except ValueError: |
|
2189 | # except ValueError: | |
2190 | # pass |
|
2190 | # pass | |
2191 | # [''.join(k) for k in s.iter(prefix)] |
|
2191 | # [''.join(k) for k in s.iter(prefix)] | |
2192 |
|
2192 | |||
2193 | # But need to be timed and adds an extra dependency. |
|
2193 | # But need to be timed and adds an extra dependency. | |
2194 |
|
2194 | |||
2195 | slashpos = text.rfind('\\') |
|
2195 | slashpos = text.rfind('\\') | |
2196 | # if text starts with slash |
|
2196 | # if text starts with slash | |
2197 | if slashpos > -1: |
|
2197 | if slashpos > -1: | |
2198 | # PERF: It's important that we don't access self._unicode_names |
|
2198 | # PERF: It's important that we don't access self._unicode_names | |
2199 | # until we're inside this if-block. _unicode_names is lazily |
|
2199 | # until we're inside this if-block. _unicode_names is lazily | |
2200 | # initialized, and it takes a user-noticeable amount of time to |
|
2200 | # initialized, and it takes a user-noticeable amount of time to | |
2201 | # initialize it, so we don't want to initialize it unless we're |
|
2201 | # initialize it, so we don't want to initialize it unless we're | |
2202 | # actually going to use it. |
|
2202 | # actually going to use it. | |
2203 | s = text[slashpos+1:] |
|
2203 | s = text[slashpos+1:] | |
2204 | candidates = [x for x in self.unicode_names if x.startswith(s)] |
|
2204 | candidates = [x for x in self.unicode_names if x.startswith(s)] | |
2205 | if candidates: |
|
2205 | if candidates: | |
2206 | return s, candidates |
|
2206 | return s, candidates | |
2207 | else: |
|
2207 | else: | |
2208 | return '', () |
|
2208 | return '', () | |
2209 |
|
2209 | |||
2210 | # if text does not start with slash |
|
2210 | # if text does not start with slash | |
2211 | else: |
|
2211 | else: | |
2212 | return '', () |
|
2212 | return '', () | |
2213 |
|
2213 | |||
2214 | @property |
|
2214 | @property | |
2215 | def unicode_names(self) -> List[str]: |
|
2215 | def unicode_names(self) -> List[str]: | |
2216 | """List of names of unicode code points that can be completed. |
|
2216 | """List of names of unicode code points that can be completed. | |
2217 |
|
2217 | |||
2218 | The list is lazily initialized on first access. |
|
2218 | The list is lazily initialized on first access. | |
2219 | """ |
|
2219 | """ | |
2220 | if self._unicode_names is None: |
|
2220 | if self._unicode_names is None: | |
2221 | names = [] |
|
2221 | names = [] | |
2222 | for c in range(0,0x10FFFF + 1): |
|
2222 | for c in range(0,0x10FFFF + 1): | |
2223 | try: |
|
2223 | try: | |
2224 | names.append(unicodedata.name(chr(c))) |
|
2224 | names.append(unicodedata.name(chr(c))) | |
2225 | except ValueError: |
|
2225 | except ValueError: | |
2226 | pass |
|
2226 | pass | |
2227 | self._unicode_names = _unicode_name_compute(_UNICODE_RANGES) |
|
2227 | self._unicode_names = _unicode_name_compute(_UNICODE_RANGES) | |
2228 |
|
2228 | |||
2229 | return self._unicode_names |
|
2229 | return self._unicode_names | |
2230 |
|
2230 | |||
2231 | def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]: |
|
2231 | def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]: | |
2232 | names = [] |
|
2232 | names = [] | |
2233 | for start,stop in ranges: |
|
2233 | for start,stop in ranges: | |
2234 | for c in range(start, stop) : |
|
2234 | for c in range(start, stop) : | |
2235 | try: |
|
2235 | try: | |
2236 | names.append(unicodedata.name(chr(c))) |
|
2236 | names.append(unicodedata.name(chr(c))) | |
2237 | except ValueError: |
|
2237 | except ValueError: | |
2238 | pass |
|
2238 | pass | |
2239 | return names |
|
2239 | return names |
@@ -1,354 +1,370 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """Implementations for various useful completers. |
|
2 | """Implementations for various useful completers. | |
3 |
|
3 | |||
4 | These are all loaded by default by IPython. |
|
4 | These are all loaded by default by IPython. | |
5 | """ |
|
5 | """ | |
6 | #----------------------------------------------------------------------------- |
|
6 | #----------------------------------------------------------------------------- | |
7 | # Copyright (C) 2010-2011 The IPython Development Team. |
|
7 | # Copyright (C) 2010-2011 The IPython Development Team. | |
8 | # |
|
8 | # | |
9 | # Distributed under the terms of the BSD License. |
|
9 | # Distributed under the terms of the BSD License. | |
10 | # |
|
10 | # | |
11 | # The full license is in the file COPYING.txt, distributed with this software. |
|
11 | # The full license is in the file COPYING.txt, distributed with this software. | |
12 | #----------------------------------------------------------------------------- |
|
12 | #----------------------------------------------------------------------------- | |
13 |
|
13 | |||
14 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
15 | # Imports |
|
15 | # Imports | |
16 | #----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
17 |
|
17 | |||
18 | # Stdlib imports |
|
18 | # Stdlib imports | |
19 | import glob |
|
19 | import glob | |
20 | import inspect |
|
20 | import inspect | |
21 | import os |
|
21 | import os | |
22 | import re |
|
22 | import re | |
23 | import sys |
|
23 | import sys | |
24 | from importlib import import_module |
|
24 | from importlib import import_module | |
25 | from importlib.machinery import all_suffixes |
|
25 | from importlib.machinery import all_suffixes | |
26 |
|
26 | |||
27 |
|
27 | |||
28 | # Third-party imports |
|
28 | # Third-party imports | |
29 | from time import time |
|
29 | from time import time | |
30 | from zipimport import zipimporter |
|
30 | from zipimport import zipimporter | |
31 |
|
31 | |||
32 | # Our own imports |
|
32 | # Our own imports | |
33 | from .completer import expand_user, compress_user |
|
33 | from .completer import expand_user, compress_user | |
34 | from .error import TryNext |
|
34 | from .error import TryNext | |
35 | from ..utils._process_common import arg_split |
|
35 | from ..utils._process_common import arg_split | |
36 |
|
36 | |||
37 | # FIXME: this should be pulled in with the right call via the component system |
|
37 | # FIXME: this should be pulled in with the right call via the component system | |
38 | from IPython import get_ipython |
|
38 | from IPython import get_ipython | |
39 |
|
39 | |||
40 | from typing import List |
|
40 | from typing import List | |
41 |
|
41 | |||
42 | #----------------------------------------------------------------------------- |
|
42 | #----------------------------------------------------------------------------- | |
43 | # Globals and constants |
|
43 | # Globals and constants | |
44 | #----------------------------------------------------------------------------- |
|
44 | #----------------------------------------------------------------------------- | |
45 | _suffixes = all_suffixes() |
|
45 | _suffixes = all_suffixes() | |
46 |
|
46 | |||
47 | # Time in seconds after which the rootmodules will be stored permanently in the |
|
47 | # Time in seconds after which the rootmodules will be stored permanently in the | |
48 | # ipython ip.db database (kept in the user's .ipython dir). |
|
48 | # ipython ip.db database (kept in the user's .ipython dir). | |
49 | TIMEOUT_STORAGE = 2 |
|
49 | TIMEOUT_STORAGE = 2 | |
50 |
|
50 | |||
51 | # Time in seconds after which we give up |
|
51 | # Time in seconds after which we give up | |
52 | TIMEOUT_GIVEUP = 20 |
|
52 | TIMEOUT_GIVEUP = 20 | |
53 |
|
53 | |||
54 | # Regular expression for the python import statement |
|
54 | # Regular expression for the python import statement | |
55 | import_re = re.compile(r'(?P<name>[^\W\d]\w*?)' |
|
55 | import_re = re.compile(r'(?P<name>[^\W\d]\w*?)' | |
56 | r'(?P<package>[/\\]__init__)?' |
|
56 | r'(?P<package>[/\\]__init__)?' | |
57 | r'(?P<suffix>%s)$' % |
|
57 | r'(?P<suffix>%s)$' % | |
58 | r'|'.join(re.escape(s) for s in _suffixes)) |
|
58 | r'|'.join(re.escape(s) for s in _suffixes)) | |
59 |
|
59 | |||
60 | # RE for the ipython %run command (python + ipython scripts) |
|
60 | # RE for the ipython %run command (python + ipython scripts) | |
61 | magic_run_re = re.compile(r'.*(\.ipy|\.ipynb|\.py[w]?)$') |
|
61 | magic_run_re = re.compile(r'.*(\.ipy|\.ipynb|\.py[w]?)$') | |
62 |
|
62 | |||
63 | #----------------------------------------------------------------------------- |
|
63 | #----------------------------------------------------------------------------- | |
64 | # Local utilities |
|
64 | # Local utilities | |
65 | #----------------------------------------------------------------------------- |
|
65 | #----------------------------------------------------------------------------- | |
66 |
|
66 | |||
67 | def module_list(path): |
|
67 | def module_list(path): | |
68 | """ |
|
68 | """ | |
69 | Return the list containing the names of the modules available in the given |
|
69 | Return the list containing the names of the modules available in the given | |
70 | folder. |
|
70 | folder. | |
71 | """ |
|
71 | """ | |
72 | # sys.path has the cwd as an empty string, but isdir/listdir need it as '.' |
|
72 | # sys.path has the cwd as an empty string, but isdir/listdir need it as '.' | |
73 | if path == '': |
|
73 | if path == '': | |
74 | path = '.' |
|
74 | path = '.' | |
75 |
|
75 | |||
76 | # A few local constants to be used in loops below |
|
76 | # A few local constants to be used in loops below | |
77 | pjoin = os.path.join |
|
77 | pjoin = os.path.join | |
78 |
|
78 | |||
79 | if os.path.isdir(path): |
|
79 | if os.path.isdir(path): | |
80 | # Build a list of all files in the directory and all files |
|
80 | # Build a list of all files in the directory and all files | |
81 | # in its subdirectories. For performance reasons, do not |
|
81 | # in its subdirectories. For performance reasons, do not | |
82 | # recurse more than one level into subdirectories. |
|
82 | # recurse more than one level into subdirectories. | |
83 | files = [] |
|
83 | files = [] | |
84 | for root, dirs, nondirs in os.walk(path, followlinks=True): |
|
84 | for root, dirs, nondirs in os.walk(path, followlinks=True): | |
85 | subdir = root[len(path)+1:] |
|
85 | subdir = root[len(path)+1:] | |
86 | if subdir: |
|
86 | if subdir: | |
87 | files.extend(pjoin(subdir, f) for f in nondirs) |
|
87 | files.extend(pjoin(subdir, f) for f in nondirs) | |
88 | dirs[:] = [] # Do not recurse into additional subdirectories. |
|
88 | dirs[:] = [] # Do not recurse into additional subdirectories. | |
89 | else: |
|
89 | else: | |
90 | files.extend(nondirs) |
|
90 | files.extend(nondirs) | |
91 |
|
91 | |||
92 | else: |
|
92 | else: | |
93 | try: |
|
93 | try: | |
94 | files = list(zipimporter(path)._files.keys()) |
|
94 | files = list(zipimporter(path)._files.keys()) | |
95 | except: |
|
95 | except: | |
96 | files = [] |
|
96 | files = [] | |
97 |
|
97 | |||
98 | # Build a list of modules which match the import_re regex. |
|
98 | # Build a list of modules which match the import_re regex. | |
99 | modules = [] |
|
99 | modules = [] | |
100 | for f in files: |
|
100 | for f in files: | |
101 | m = import_re.match(f) |
|
101 | m = import_re.match(f) | |
102 | if m: |
|
102 | if m: | |
103 | modules.append(m.group('name')) |
|
103 | modules.append(m.group('name')) | |
104 | return list(set(modules)) |
|
104 | return list(set(modules)) | |
105 |
|
105 | |||
106 |
|
106 | |||
107 | def get_root_modules(): |
|
107 | def get_root_modules(): | |
108 | """ |
|
108 | """ | |
109 | Returns a list containing the names of all the modules available in the |
|
109 | Returns a list containing the names of all the modules available in the | |
110 | folders of the pythonpath. |
|
110 | folders of the pythonpath. | |
111 |
|
111 | |||
112 | ip.db['rootmodules_cache'] maps sys.path entries to list of modules. |
|
112 | ip.db['rootmodules_cache'] maps sys.path entries to list of modules. | |
113 | """ |
|
113 | """ | |
114 | ip = get_ipython() |
|
114 | ip = get_ipython() | |
115 | if ip is None: |
|
115 | if ip is None: | |
116 | # No global shell instance to store cached list of modules. |
|
116 | # No global shell instance to store cached list of modules. | |
117 | # Don't try to scan for modules every time. |
|
117 | # Don't try to scan for modules every time. | |
118 | return list(sys.builtin_module_names) |
|
118 | return list(sys.builtin_module_names) | |
119 |
|
119 | |||
120 | rootmodules_cache = ip.db.get('rootmodules_cache', {}) |
|
120 | rootmodules_cache = ip.db.get('rootmodules_cache', {}) | |
121 | rootmodules = list(sys.builtin_module_names) |
|
121 | rootmodules = list(sys.builtin_module_names) | |
122 | start_time = time() |
|
122 | start_time = time() | |
123 | store = False |
|
123 | store = False | |
124 | for path in sys.path: |
|
124 | for path in sys.path: | |
125 | try: |
|
125 | try: | |
126 | modules = rootmodules_cache[path] |
|
126 | modules = rootmodules_cache[path] | |
127 | except KeyError: |
|
127 | except KeyError: | |
128 | modules = module_list(path) |
|
128 | modules = module_list(path) | |
129 | try: |
|
129 | try: | |
130 | modules.remove('__init__') |
|
130 | modules.remove('__init__') | |
131 | except ValueError: |
|
131 | except ValueError: | |
132 | pass |
|
132 | pass | |
133 | if path not in ('', '.'): # cwd modules should not be cached |
|
133 | if path not in ('', '.'): # cwd modules should not be cached | |
134 | rootmodules_cache[path] = modules |
|
134 | rootmodules_cache[path] = modules | |
135 | if time() - start_time > TIMEOUT_STORAGE and not store: |
|
135 | if time() - start_time > TIMEOUT_STORAGE and not store: | |
136 | store = True |
|
136 | store = True | |
137 | print("\nCaching the list of root modules, please wait!") |
|
137 | print("\nCaching the list of root modules, please wait!") | |
138 | print("(This will only be done once - type '%rehashx' to " |
|
138 | print("(This will only be done once - type '%rehashx' to " | |
139 | "reset cache!)\n") |
|
139 | "reset cache!)\n") | |
140 | sys.stdout.flush() |
|
140 | sys.stdout.flush() | |
141 | if time() - start_time > TIMEOUT_GIVEUP: |
|
141 | if time() - start_time > TIMEOUT_GIVEUP: | |
142 | print("This is taking too long, we give up.\n") |
|
142 | print("This is taking too long, we give up.\n") | |
143 | return [] |
|
143 | return [] | |
144 | rootmodules.extend(modules) |
|
144 | rootmodules.extend(modules) | |
145 | if store: |
|
145 | if store: | |
146 | ip.db['rootmodules_cache'] = rootmodules_cache |
|
146 | ip.db['rootmodules_cache'] = rootmodules_cache | |
147 | rootmodules = list(set(rootmodules)) |
|
147 | rootmodules = list(set(rootmodules)) | |
148 | return rootmodules |
|
148 | return rootmodules | |
149 |
|
149 | |||
150 |
|
150 | |||
151 | def is_importable(module, attr, only_modules): |
|
151 | def is_importable(module, attr, only_modules): | |
152 | if only_modules: |
|
152 | if only_modules: | |
153 | return inspect.ismodule(getattr(module, attr)) |
|
153 | return inspect.ismodule(getattr(module, attr)) | |
154 | else: |
|
154 | else: | |
155 | return not(attr[:2] == '__' and attr[-2:] == '__') |
|
155 | return not(attr[:2] == '__' and attr[-2:] == '__') | |
156 |
|
156 | |||
|
157 | def is_possible_submodule(module, attr): | |||
|
158 | try: | |||
|
159 | obj = getattr(module, attr) | |||
|
160 | except AttributeError: | |||
|
161 | # Is possilby an unimported submodule | |||
|
162 | return True | |||
|
163 | except TypeError: | |||
|
164 | # https://github.com/ipython/ipython/issues/9678 | |||
|
165 | return False | |||
|
166 | return inspect.ismodule(obj) | |||
|
167 | ||||
157 |
|
168 | |||
158 | def try_import(mod: str, only_modules=False) -> List[str]: |
|
169 | def try_import(mod: str, only_modules=False) -> List[str]: | |
159 | """ |
|
170 | """ | |
160 | Try to import given module and return list of potential completions. |
|
171 | Try to import given module and return list of potential completions. | |
161 | """ |
|
172 | """ | |
162 | mod = mod.rstrip('.') |
|
173 | mod = mod.rstrip('.') | |
163 | try: |
|
174 | try: | |
164 | m = import_module(mod) |
|
175 | m = import_module(mod) | |
165 | except: |
|
176 | except: | |
166 | return [] |
|
177 | return [] | |
167 |
|
178 | |||
168 | m_is_init = '__init__' in (getattr(m, '__file__', '') or '') |
|
179 | m_is_init = '__init__' in (getattr(m, '__file__', '') or '') | |
169 |
|
180 | |||
170 | completions = [] |
|
181 | completions = [] | |
171 | if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init: |
|
182 | if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init: | |
172 | completions.extend( [attr for attr in dir(m) if |
|
183 | completions.extend( [attr for attr in dir(m) if | |
173 | is_importable(m, attr, only_modules)]) |
|
184 | is_importable(m, attr, only_modules)]) | |
174 |
|
185 | |||
175 |
|
|
186 | m_all = getattr(m, "__all__", []) | |
|
187 | if only_modules: | |||
|
188 | completions.extend(attr for attr in m_all if is_possible_submodule(m, attr)) | |||
|
189 | else: | |||
|
190 | completions.extend(m_all) | |||
|
191 | ||||
176 | if m_is_init: |
|
192 | if m_is_init: | |
177 | completions.extend(module_list(os.path.dirname(m.__file__))) |
|
193 | completions.extend(module_list(os.path.dirname(m.__file__))) | |
178 | completions_set = {c for c in completions if isinstance(c, str)} |
|
194 | completions_set = {c for c in completions if isinstance(c, str)} | |
179 | completions_set.discard('__init__') |
|
195 | completions_set.discard('__init__') | |
180 | return list(completions_set) |
|
196 | return list(completions_set) | |
181 |
|
197 | |||
182 |
|
198 | |||
183 | #----------------------------------------------------------------------------- |
|
199 | #----------------------------------------------------------------------------- | |
184 | # Completion-related functions. |
|
200 | # Completion-related functions. | |
185 | #----------------------------------------------------------------------------- |
|
201 | #----------------------------------------------------------------------------- | |
186 |
|
202 | |||
187 | def quick_completer(cmd, completions): |
|
203 | def quick_completer(cmd, completions): | |
188 | r""" Easily create a trivial completer for a command. |
|
204 | r""" Easily create a trivial completer for a command. | |
189 |
|
205 | |||
190 | Takes either a list of completions, or all completions in string (that will |
|
206 | Takes either a list of completions, or all completions in string (that will | |
191 | be split on whitespace). |
|
207 | be split on whitespace). | |
192 |
|
208 | |||
193 | Example:: |
|
209 | Example:: | |
194 |
|
210 | |||
195 | [d:\ipython]|1> import ipy_completers |
|
211 | [d:\ipython]|1> import ipy_completers | |
196 | [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz']) |
|
212 | [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz']) | |
197 | [d:\ipython]|3> foo b<TAB> |
|
213 | [d:\ipython]|3> foo b<TAB> | |
198 | bar baz |
|
214 | bar baz | |
199 | [d:\ipython]|3> foo ba |
|
215 | [d:\ipython]|3> foo ba | |
200 | """ |
|
216 | """ | |
201 |
|
217 | |||
202 | if isinstance(completions, str): |
|
218 | if isinstance(completions, str): | |
203 | completions = completions.split() |
|
219 | completions = completions.split() | |
204 |
|
220 | |||
205 | def do_complete(self, event): |
|
221 | def do_complete(self, event): | |
206 | return completions |
|
222 | return completions | |
207 |
|
223 | |||
208 | get_ipython().set_hook('complete_command',do_complete, str_key = cmd) |
|
224 | get_ipython().set_hook('complete_command',do_complete, str_key = cmd) | |
209 |
|
225 | |||
210 | def module_completion(line): |
|
226 | def module_completion(line): | |
211 | """ |
|
227 | """ | |
212 | Returns a list containing the completion possibilities for an import line. |
|
228 | Returns a list containing the completion possibilities for an import line. | |
213 |
|
229 | |||
214 | The line looks like this : |
|
230 | The line looks like this : | |
215 | 'import xml.d' |
|
231 | 'import xml.d' | |
216 | 'from xml.dom import' |
|
232 | 'from xml.dom import' | |
217 | """ |
|
233 | """ | |
218 |
|
234 | |||
219 | words = line.split(' ') |
|
235 | words = line.split(' ') | |
220 | nwords = len(words) |
|
236 | nwords = len(words) | |
221 |
|
237 | |||
222 | # from whatever <tab> -> 'import ' |
|
238 | # from whatever <tab> -> 'import ' | |
223 | if nwords == 3 and words[0] == 'from': |
|
239 | if nwords == 3 and words[0] == 'from': | |
224 | return ['import '] |
|
240 | return ['import '] | |
225 |
|
241 | |||
226 | # 'from xy<tab>' or 'import xy<tab>' |
|
242 | # 'from xy<tab>' or 'import xy<tab>' | |
227 | if nwords < 3 and (words[0] in {'%aimport', 'import', 'from'}) : |
|
243 | if nwords < 3 and (words[0] in {'%aimport', 'import', 'from'}) : | |
228 | if nwords == 1: |
|
244 | if nwords == 1: | |
229 | return get_root_modules() |
|
245 | return get_root_modules() | |
230 | mod = words[1].split('.') |
|
246 | mod = words[1].split('.') | |
231 | if len(mod) < 2: |
|
247 | if len(mod) < 2: | |
232 | return get_root_modules() |
|
248 | return get_root_modules() | |
233 | completion_list = try_import('.'.join(mod[:-1]), True) |
|
249 | completion_list = try_import('.'.join(mod[:-1]), True) | |
234 | return ['.'.join(mod[:-1] + [el]) for el in completion_list] |
|
250 | return ['.'.join(mod[:-1] + [el]) for el in completion_list] | |
235 |
|
251 | |||
236 | # 'from xyz import abc<tab>' |
|
252 | # 'from xyz import abc<tab>' | |
237 | if nwords >= 3 and words[0] == 'from': |
|
253 | if nwords >= 3 and words[0] == 'from': | |
238 | mod = words[1] |
|
254 | mod = words[1] | |
239 | return try_import(mod) |
|
255 | return try_import(mod) | |
240 |
|
256 | |||
241 | #----------------------------------------------------------------------------- |
|
257 | #----------------------------------------------------------------------------- | |
242 | # Completers |
|
258 | # Completers | |
243 | #----------------------------------------------------------------------------- |
|
259 | #----------------------------------------------------------------------------- | |
244 | # These all have the func(self, event) signature to be used as custom |
|
260 | # These all have the func(self, event) signature to be used as custom | |
245 | # completers |
|
261 | # completers | |
246 |
|
262 | |||
247 | def module_completer(self,event): |
|
263 | def module_completer(self,event): | |
248 | """Give completions after user has typed 'import ...' or 'from ...'""" |
|
264 | """Give completions after user has typed 'import ...' or 'from ...'""" | |
249 |
|
265 | |||
250 | # This works in all versions of python. While 2.5 has |
|
266 | # This works in all versions of python. While 2.5 has | |
251 | # pkgutil.walk_packages(), that particular routine is fairly dangerous, |
|
267 | # pkgutil.walk_packages(), that particular routine is fairly dangerous, | |
252 | # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full |
|
268 | # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full | |
253 | # of possibly problematic side effects. |
|
269 | # of possibly problematic side effects. | |
254 | # This search the folders in the sys.path for available modules. |
|
270 | # This search the folders in the sys.path for available modules. | |
255 |
|
271 | |||
256 | return module_completion(event.line) |
|
272 | return module_completion(event.line) | |
257 |
|
273 | |||
258 | # FIXME: there's a lot of logic common to the run, cd and builtin file |
|
274 | # FIXME: there's a lot of logic common to the run, cd and builtin file | |
259 | # completers, that is currently reimplemented in each. |
|
275 | # completers, that is currently reimplemented in each. | |
260 |
|
276 | |||
261 | def magic_run_completer(self, event): |
|
277 | def magic_run_completer(self, event): | |
262 | """Complete files that end in .py or .ipy or .ipynb for the %run command. |
|
278 | """Complete files that end in .py or .ipy or .ipynb for the %run command. | |
263 | """ |
|
279 | """ | |
264 | comps = arg_split(event.line, strict=False) |
|
280 | comps = arg_split(event.line, strict=False) | |
265 | # relpath should be the current token that we need to complete. |
|
281 | # relpath should be the current token that we need to complete. | |
266 | if (len(comps) > 1) and (not event.line.endswith(' ')): |
|
282 | if (len(comps) > 1) and (not event.line.endswith(' ')): | |
267 | relpath = comps[-1].strip("'\"") |
|
283 | relpath = comps[-1].strip("'\"") | |
268 | else: |
|
284 | else: | |
269 | relpath = '' |
|
285 | relpath = '' | |
270 |
|
286 | |||
271 | #print("\nev=", event) # dbg |
|
287 | #print("\nev=", event) # dbg | |
272 | #print("rp=", relpath) # dbg |
|
288 | #print("rp=", relpath) # dbg | |
273 | #print('comps=', comps) # dbg |
|
289 | #print('comps=', comps) # dbg | |
274 |
|
290 | |||
275 | lglob = glob.glob |
|
291 | lglob = glob.glob | |
276 | isdir = os.path.isdir |
|
292 | isdir = os.path.isdir | |
277 | relpath, tilde_expand, tilde_val = expand_user(relpath) |
|
293 | relpath, tilde_expand, tilde_val = expand_user(relpath) | |
278 |
|
294 | |||
279 | # Find if the user has already typed the first filename, after which we |
|
295 | # Find if the user has already typed the first filename, after which we | |
280 | # should complete on all files, since after the first one other files may |
|
296 | # should complete on all files, since after the first one other files may | |
281 | # be arguments to the input script. |
|
297 | # be arguments to the input script. | |
282 |
|
298 | |||
283 | if any(magic_run_re.match(c) for c in comps): |
|
299 | if any(magic_run_re.match(c) for c in comps): | |
284 | matches = [f.replace('\\','/') + ('/' if isdir(f) else '') |
|
300 | matches = [f.replace('\\','/') + ('/' if isdir(f) else '') | |
285 | for f in lglob(relpath+'*')] |
|
301 | for f in lglob(relpath+'*')] | |
286 | else: |
|
302 | else: | |
287 | dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)] |
|
303 | dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)] | |
288 | pys = [f.replace('\\','/') |
|
304 | pys = [f.replace('\\','/') | |
289 | for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') + |
|
305 | for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') + | |
290 | lglob(relpath+'*.ipynb') + lglob(relpath + '*.pyw')] |
|
306 | lglob(relpath+'*.ipynb') + lglob(relpath + '*.pyw')] | |
291 |
|
307 | |||
292 | matches = dirs + pys |
|
308 | matches = dirs + pys | |
293 |
|
309 | |||
294 | #print('run comp:', dirs+pys) # dbg |
|
310 | #print('run comp:', dirs+pys) # dbg | |
295 | return [compress_user(p, tilde_expand, tilde_val) for p in matches] |
|
311 | return [compress_user(p, tilde_expand, tilde_val) for p in matches] | |
296 |
|
312 | |||
297 |
|
313 | |||
298 | def cd_completer(self, event): |
|
314 | def cd_completer(self, event): | |
299 | """Completer function for cd, which only returns directories.""" |
|
315 | """Completer function for cd, which only returns directories.""" | |
300 | ip = get_ipython() |
|
316 | ip = get_ipython() | |
301 | relpath = event.symbol |
|
317 | relpath = event.symbol | |
302 |
|
318 | |||
303 | #print(event) # dbg |
|
319 | #print(event) # dbg | |
304 | if event.line.endswith('-b') or ' -b ' in event.line: |
|
320 | if event.line.endswith('-b') or ' -b ' in event.line: | |
305 | # return only bookmark completions |
|
321 | # return only bookmark completions | |
306 | bkms = self.db.get('bookmarks', None) |
|
322 | bkms = self.db.get('bookmarks', None) | |
307 | if bkms: |
|
323 | if bkms: | |
308 | return bkms.keys() |
|
324 | return bkms.keys() | |
309 | else: |
|
325 | else: | |
310 | return [] |
|
326 | return [] | |
311 |
|
327 | |||
312 | if event.symbol == '-': |
|
328 | if event.symbol == '-': | |
313 | width_dh = str(len(str(len(ip.user_ns['_dh']) + 1))) |
|
329 | width_dh = str(len(str(len(ip.user_ns['_dh']) + 1))) | |
314 | # jump in directory history by number |
|
330 | # jump in directory history by number | |
315 | fmt = '-%0' + width_dh +'d [%s]' |
|
331 | fmt = '-%0' + width_dh +'d [%s]' | |
316 | ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])] |
|
332 | ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])] | |
317 | if len(ents) > 1: |
|
333 | if len(ents) > 1: | |
318 | return ents |
|
334 | return ents | |
319 | return [] |
|
335 | return [] | |
320 |
|
336 | |||
321 | if event.symbol.startswith('--'): |
|
337 | if event.symbol.startswith('--'): | |
322 | return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']] |
|
338 | return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']] | |
323 |
|
339 | |||
324 | # Expand ~ in path and normalize directory separators. |
|
340 | # Expand ~ in path and normalize directory separators. | |
325 | relpath, tilde_expand, tilde_val = expand_user(relpath) |
|
341 | relpath, tilde_expand, tilde_val = expand_user(relpath) | |
326 | relpath = relpath.replace('\\','/') |
|
342 | relpath = relpath.replace('\\','/') | |
327 |
|
343 | |||
328 | found = [] |
|
344 | found = [] | |
329 | for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*') |
|
345 | for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*') | |
330 | if os.path.isdir(f)]: |
|
346 | if os.path.isdir(f)]: | |
331 | if ' ' in d: |
|
347 | if ' ' in d: | |
332 | # we don't want to deal with any of that, complex code |
|
348 | # we don't want to deal with any of that, complex code | |
333 | # for this is elsewhere |
|
349 | # for this is elsewhere | |
334 | raise TryNext |
|
350 | raise TryNext | |
335 |
|
351 | |||
336 | found.append(d) |
|
352 | found.append(d) | |
337 |
|
353 | |||
338 | if not found: |
|
354 | if not found: | |
339 | if os.path.isdir(relpath): |
|
355 | if os.path.isdir(relpath): | |
340 | return [compress_user(relpath, tilde_expand, tilde_val)] |
|
356 | return [compress_user(relpath, tilde_expand, tilde_val)] | |
341 |
|
357 | |||
342 | # if no completions so far, try bookmarks |
|
358 | # if no completions so far, try bookmarks | |
343 | bks = self.db.get('bookmarks',{}) |
|
359 | bks = self.db.get('bookmarks',{}) | |
344 | bkmatches = [s for s in bks if s.startswith(event.symbol)] |
|
360 | bkmatches = [s for s in bks if s.startswith(event.symbol)] | |
345 | if bkmatches: |
|
361 | if bkmatches: | |
346 | return bkmatches |
|
362 | return bkmatches | |
347 |
|
363 | |||
348 | raise TryNext |
|
364 | raise TryNext | |
349 |
|
365 | |||
350 | return [compress_user(p, tilde_expand, tilde_val) for p in found] |
|
366 | return [compress_user(p, tilde_expand, tilde_val) for p in found] | |
351 |
|
367 | |||
352 | def reset_completer(self, event): |
|
368 | def reset_completer(self, event): | |
353 | "A completer for %reset magic" |
|
369 | "A completer for %reset magic" | |
354 | return '-f -s in out array dhist'.split() |
|
370 | return '-f -s in out array dhist'.split() |
@@ -1,229 +1,223 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """sys.excepthook for IPython itself, leaves a detailed report on disk. |
|
2 | """sys.excepthook for IPython itself, leaves a detailed report on disk. | |
3 |
|
3 | |||
4 | Authors: |
|
4 | Authors: | |
5 |
|
5 | |||
6 | * Fernando Perez |
|
6 | * Fernando Perez | |
7 | * Brian E. Granger |
|
7 | * Brian E. Granger | |
8 | """ |
|
8 | """ | |
9 |
|
9 | |||
10 | #----------------------------------------------------------------------------- |
|
10 | #----------------------------------------------------------------------------- | |
11 | # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu> |
|
11 | # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu> | |
12 | # Copyright (C) 2008-2011 The IPython Development Team |
|
12 | # Copyright (C) 2008-2011 The IPython Development Team | |
13 | # |
|
13 | # | |
14 | # Distributed under the terms of the BSD License. The full license is in |
|
14 | # Distributed under the terms of the BSD License. The full license is in | |
15 | # the file COPYING, distributed as part of this software. |
|
15 | # the file COPYING, distributed as part of this software. | |
16 | #----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
17 |
|
17 | |||
18 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
19 | # Imports |
|
19 | # Imports | |
20 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
21 |
|
21 | |||
22 | import os |
|
22 | import os | |
23 | import sys |
|
23 | import sys | |
24 | import traceback |
|
24 | import traceback | |
25 | from pprint import pformat |
|
25 | from pprint import pformat | |
26 | from pathlib import Path |
|
26 | from pathlib import Path | |
27 |
|
27 | |||
28 | from IPython.core import ultratb |
|
28 | from IPython.core import ultratb | |
29 | from IPython.core.release import author_email |
|
29 | from IPython.core.release import author_email | |
30 | from IPython.utils.sysinfo import sys_info |
|
30 | from IPython.utils.sysinfo import sys_info | |
31 | from IPython.utils.py3compat import input |
|
31 | from IPython.utils.py3compat import input | |
32 |
|
32 | |||
33 | from IPython.core.release import __version__ as version |
|
33 | from IPython.core.release import __version__ as version | |
34 |
|
34 | |||
35 | #----------------------------------------------------------------------------- |
|
35 | #----------------------------------------------------------------------------- | |
36 | # Code |
|
36 | # Code | |
37 | #----------------------------------------------------------------------------- |
|
37 | #----------------------------------------------------------------------------- | |
38 |
|
38 | |||
39 | # Template for the user message. |
|
39 | # Template for the user message. | |
40 | _default_message_template = """\ |
|
40 | _default_message_template = """\ | |
41 | Oops, {app_name} crashed. We do our best to make it stable, but... |
|
41 | Oops, {app_name} crashed. We do our best to make it stable, but... | |
42 |
|
42 | |||
43 | A crash report was automatically generated with the following information: |
|
43 | A crash report was automatically generated with the following information: | |
44 | - A verbatim copy of the crash traceback. |
|
44 | - A verbatim copy of the crash traceback. | |
45 | - A copy of your input history during this session. |
|
45 | - A copy of your input history during this session. | |
46 | - Data on your current {app_name} configuration. |
|
46 | - Data on your current {app_name} configuration. | |
47 |
|
47 | |||
48 | It was left in the file named: |
|
48 | It was left in the file named: | |
49 | \t'{crash_report_fname}' |
|
49 | \t'{crash_report_fname}' | |
50 | If you can email this file to the developers, the information in it will help |
|
50 | If you can email this file to the developers, the information in it will help | |
51 | them in understanding and correcting the problem. |
|
51 | them in understanding and correcting the problem. | |
52 |
|
52 | |||
53 | You can mail it to: {contact_name} at {contact_email} |
|
53 | You can mail it to: {contact_name} at {contact_email} | |
54 | with the subject '{app_name} Crash Report'. |
|
54 | with the subject '{app_name} Crash Report'. | |
55 |
|
55 | |||
56 | If you want to do it now, the following command will work (under Unix): |
|
56 | If you want to do it now, the following command will work (under Unix): | |
57 | mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname} |
|
57 | mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname} | |
58 |
|
58 | |||
59 | In your email, please also include information about: |
|
59 | In your email, please also include information about: | |
60 | - The operating system under which the crash happened: Linux, macOS, Windows, |
|
60 | - The operating system under which the crash happened: Linux, macOS, Windows, | |
61 | other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2, |
|
61 | other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2, | |
62 | Windows 10 Pro), and whether it is 32-bit or 64-bit; |
|
62 | Windows 10 Pro), and whether it is 32-bit or 64-bit; | |
63 | - How {app_name} was installed: using pip or conda, from GitHub, as part of |
|
63 | - How {app_name} was installed: using pip or conda, from GitHub, as part of | |
64 | a Docker container, or other, providing more detail if possible; |
|
64 | a Docker container, or other, providing more detail if possible; | |
65 | - How to reproduce the crash: what exact sequence of instructions can one |
|
65 | - How to reproduce the crash: what exact sequence of instructions can one | |
66 | input to get the same crash? Ideally, find a minimal yet complete sequence |
|
66 | input to get the same crash? Ideally, find a minimal yet complete sequence | |
67 | of instructions that yields the crash. |
|
67 | of instructions that yields the crash. | |
68 |
|
68 | |||
69 | To ensure accurate tracking of this issue, please file a report about it at: |
|
69 | To ensure accurate tracking of this issue, please file a report about it at: | |
70 | {bug_tracker} |
|
70 | {bug_tracker} | |
71 | """ |
|
71 | """ | |
72 |
|
72 | |||
73 | _lite_message_template = """ |
|
73 | _lite_message_template = """ | |
74 | If you suspect this is an IPython {version} bug, please report it at: |
|
74 | If you suspect this is an IPython {version} bug, please report it at: | |
75 | https://github.com/ipython/ipython/issues |
|
75 | https://github.com/ipython/ipython/issues | |
76 | or send an email to the mailing list at {email} |
|
76 | or send an email to the mailing list at {email} | |
77 |
|
77 | |||
78 | You can print a more detailed traceback right now with "%tb", or use "%debug" |
|
78 | You can print a more detailed traceback right now with "%tb", or use "%debug" | |
79 | to interactively debug it. |
|
79 | to interactively debug it. | |
80 |
|
80 | |||
81 | Extra-detailed tracebacks for bug-reporting purposes can be enabled via: |
|
81 | Extra-detailed tracebacks for bug-reporting purposes can be enabled via: | |
82 | {config}Application.verbose_crash=True |
|
82 | {config}Application.verbose_crash=True | |
83 | """ |
|
83 | """ | |
84 |
|
84 | |||
85 |
|
85 | |||
86 | class CrashHandler(object): |
|
86 | class CrashHandler(object): | |
87 | """Customizable crash handlers for IPython applications. |
|
87 | """Customizable crash handlers for IPython applications. | |
88 |
|
88 | |||
89 | Instances of this class provide a :meth:`__call__` method which can be |
|
89 | Instances of this class provide a :meth:`__call__` method which can be | |
90 | used as a ``sys.excepthook``. The :meth:`__call__` signature is:: |
|
90 | used as a ``sys.excepthook``. The :meth:`__call__` signature is:: | |
91 |
|
91 | |||
92 | def __call__(self, etype, evalue, etb) |
|
92 | def __call__(self, etype, evalue, etb) | |
93 | """ |
|
93 | """ | |
94 |
|
94 | |||
95 | message_template = _default_message_template |
|
95 | message_template = _default_message_template | |
96 | section_sep = '\n\n'+'*'*75+'\n\n' |
|
96 | section_sep = '\n\n'+'*'*75+'\n\n' | |
97 |
|
97 | |||
98 | def __init__(self, app, contact_name=None, contact_email=None, |
|
98 | def __init__(self, app, contact_name=None, contact_email=None, | |
99 | bug_tracker=None, show_crash_traceback=True, call_pdb=False): |
|
99 | bug_tracker=None, show_crash_traceback=True, call_pdb=False): | |
100 | """Create a new crash handler |
|
100 | """Create a new crash handler | |
101 |
|
101 | |||
102 | Parameters |
|
102 | Parameters | |
103 | ---------- |
|
103 | ---------- | |
104 |
app : |
|
104 | app : Application | |
105 | A running :class:`Application` instance, which will be queried at |
|
105 | A running :class:`Application` instance, which will be queried at | |
106 | crash time for internal information. |
|
106 | crash time for internal information. | |
107 |
|
||||
108 | contact_name : str |
|
107 | contact_name : str | |
109 | A string with the name of the person to contact. |
|
108 | A string with the name of the person to contact. | |
110 |
|
||||
111 | contact_email : str |
|
109 | contact_email : str | |
112 | A string with the email address of the contact. |
|
110 | A string with the email address of the contact. | |
113 |
|
||||
114 | bug_tracker : str |
|
111 | bug_tracker : str | |
115 | A string with the URL for your project's bug tracker. |
|
112 | A string with the URL for your project's bug tracker. | |
116 |
|
||||
117 | show_crash_traceback : bool |
|
113 | show_crash_traceback : bool | |
118 | If false, don't print the crash traceback on stderr, only generate |
|
114 | If false, don't print the crash traceback on stderr, only generate | |
119 | the on-disk report |
|
115 | the on-disk report | |
120 |
|
116 | Non-argument instance attributes | ||
121 | Non-argument instance attributes: |
|
|||
122 |
|
||||
123 | These instances contain some non-argument attributes which allow for |
|
117 | These instances contain some non-argument attributes which allow for | |
124 | further customization of the crash handler's behavior. Please see the |
|
118 | further customization of the crash handler's behavior. Please see the | |
125 | source for further details. |
|
119 | source for further details. | |
126 | """ |
|
120 | """ | |
127 | self.crash_report_fname = "Crash_report_%s.txt" % app.name |
|
121 | self.crash_report_fname = "Crash_report_%s.txt" % app.name | |
128 | self.app = app |
|
122 | self.app = app | |
129 | self.call_pdb = call_pdb |
|
123 | self.call_pdb = call_pdb | |
130 | #self.call_pdb = True # dbg |
|
124 | #self.call_pdb = True # dbg | |
131 | self.show_crash_traceback = show_crash_traceback |
|
125 | self.show_crash_traceback = show_crash_traceback | |
132 | self.info = dict(app_name = app.name, |
|
126 | self.info = dict(app_name = app.name, | |
133 | contact_name = contact_name, |
|
127 | contact_name = contact_name, | |
134 | contact_email = contact_email, |
|
128 | contact_email = contact_email, | |
135 | bug_tracker = bug_tracker, |
|
129 | bug_tracker = bug_tracker, | |
136 | crash_report_fname = self.crash_report_fname) |
|
130 | crash_report_fname = self.crash_report_fname) | |
137 |
|
131 | |||
138 |
|
132 | |||
139 | def __call__(self, etype, evalue, etb): |
|
133 | def __call__(self, etype, evalue, etb): | |
140 | """Handle an exception, call for compatible with sys.excepthook""" |
|
134 | """Handle an exception, call for compatible with sys.excepthook""" | |
141 |
|
135 | |||
142 | # do not allow the crash handler to be called twice without reinstalling it |
|
136 | # do not allow the crash handler to be called twice without reinstalling it | |
143 | # this prevents unlikely errors in the crash handling from entering an |
|
137 | # this prevents unlikely errors in the crash handling from entering an | |
144 | # infinite loop. |
|
138 | # infinite loop. | |
145 | sys.excepthook = sys.__excepthook__ |
|
139 | sys.excepthook = sys.__excepthook__ | |
146 |
|
140 | |||
147 | # Report tracebacks shouldn't use color in general (safer for users) |
|
141 | # Report tracebacks shouldn't use color in general (safer for users) | |
148 | color_scheme = 'NoColor' |
|
142 | color_scheme = 'NoColor' | |
149 |
|
143 | |||
150 | # Use this ONLY for developer debugging (keep commented out for release) |
|
144 | # Use this ONLY for developer debugging (keep commented out for release) | |
151 | #color_scheme = 'Linux' # dbg |
|
145 | #color_scheme = 'Linux' # dbg | |
152 | try: |
|
146 | try: | |
153 | rptdir = self.app.ipython_dir |
|
147 | rptdir = self.app.ipython_dir | |
154 | except: |
|
148 | except: | |
155 | rptdir = Path.cwd() |
|
149 | rptdir = Path.cwd() | |
156 | if rptdir is None or not Path.is_dir(rptdir): |
|
150 | if rptdir is None or not Path.is_dir(rptdir): | |
157 | rptdir = Path.cwd() |
|
151 | rptdir = Path.cwd() | |
158 | report_name = rptdir / self.crash_report_fname |
|
152 | report_name = rptdir / self.crash_report_fname | |
159 | # write the report filename into the instance dict so it can get |
|
153 | # write the report filename into the instance dict so it can get | |
160 | # properly expanded out in the user message template |
|
154 | # properly expanded out in the user message template | |
161 | self.crash_report_fname = report_name |
|
155 | self.crash_report_fname = report_name | |
162 | self.info['crash_report_fname'] = report_name |
|
156 | self.info['crash_report_fname'] = report_name | |
163 | TBhandler = ultratb.VerboseTB( |
|
157 | TBhandler = ultratb.VerboseTB( | |
164 | color_scheme=color_scheme, |
|
158 | color_scheme=color_scheme, | |
165 | long_header=1, |
|
159 | long_header=1, | |
166 | call_pdb=self.call_pdb, |
|
160 | call_pdb=self.call_pdb, | |
167 | ) |
|
161 | ) | |
168 | if self.call_pdb: |
|
162 | if self.call_pdb: | |
169 | TBhandler(etype,evalue,etb) |
|
163 | TBhandler(etype,evalue,etb) | |
170 | return |
|
164 | return | |
171 | else: |
|
165 | else: | |
172 | traceback = TBhandler.text(etype,evalue,etb,context=31) |
|
166 | traceback = TBhandler.text(etype,evalue,etb,context=31) | |
173 |
|
167 | |||
174 | # print traceback to screen |
|
168 | # print traceback to screen | |
175 | if self.show_crash_traceback: |
|
169 | if self.show_crash_traceback: | |
176 | print(traceback, file=sys.stderr) |
|
170 | print(traceback, file=sys.stderr) | |
177 |
|
171 | |||
178 | # and generate a complete report on disk |
|
172 | # and generate a complete report on disk | |
179 | try: |
|
173 | try: | |
180 | report = open(report_name,'w') |
|
174 | report = open(report_name,'w') | |
181 | except: |
|
175 | except: | |
182 | print('Could not create crash report on disk.', file=sys.stderr) |
|
176 | print('Could not create crash report on disk.', file=sys.stderr) | |
183 | return |
|
177 | return | |
184 |
|
178 | |||
185 | with report: |
|
179 | with report: | |
186 | # Inform user on stderr of what happened |
|
180 | # Inform user on stderr of what happened | |
187 | print('\n'+'*'*70+'\n', file=sys.stderr) |
|
181 | print('\n'+'*'*70+'\n', file=sys.stderr) | |
188 | print(self.message_template.format(**self.info), file=sys.stderr) |
|
182 | print(self.message_template.format(**self.info), file=sys.stderr) | |
189 |
|
183 | |||
190 | # Construct report on disk |
|
184 | # Construct report on disk | |
191 | report.write(self.make_report(traceback)) |
|
185 | report.write(self.make_report(traceback)) | |
192 |
|
186 | |||
193 | input("Hit <Enter> to quit (your terminal may close):") |
|
187 | input("Hit <Enter> to quit (your terminal may close):") | |
194 |
|
188 | |||
195 | def make_report(self,traceback): |
|
189 | def make_report(self,traceback): | |
196 | """Return a string containing a crash report.""" |
|
190 | """Return a string containing a crash report.""" | |
197 |
|
191 | |||
198 | sec_sep = self.section_sep |
|
192 | sec_sep = self.section_sep | |
199 |
|
193 | |||
200 | report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n'] |
|
194 | report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n'] | |
201 | rpt_add = report.append |
|
195 | rpt_add = report.append | |
202 | rpt_add(sys_info()) |
|
196 | rpt_add(sys_info()) | |
203 |
|
197 | |||
204 | try: |
|
198 | try: | |
205 | config = pformat(self.app.config) |
|
199 | config = pformat(self.app.config) | |
206 | rpt_add(sec_sep) |
|
200 | rpt_add(sec_sep) | |
207 | rpt_add('Application name: %s\n\n' % self.app_name) |
|
201 | rpt_add('Application name: %s\n\n' % self.app_name) | |
208 | rpt_add('Current user configuration structure:\n\n') |
|
202 | rpt_add('Current user configuration structure:\n\n') | |
209 | rpt_add(config) |
|
203 | rpt_add(config) | |
210 | except: |
|
204 | except: | |
211 | pass |
|
205 | pass | |
212 | rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) |
|
206 | rpt_add(sec_sep+'Crash traceback:\n\n' + traceback) | |
213 |
|
207 | |||
214 | return ''.join(report) |
|
208 | return ''.join(report) | |
215 |
|
209 | |||
216 |
|
210 | |||
217 | def crash_handler_lite(etype, evalue, tb): |
|
211 | def crash_handler_lite(etype, evalue, tb): | |
218 | """a light excepthook, adding a small message to the usual traceback""" |
|
212 | """a light excepthook, adding a small message to the usual traceback""" | |
219 | traceback.print_exception(etype, evalue, tb) |
|
213 | traceback.print_exception(etype, evalue, tb) | |
220 |
|
214 | |||
221 | from IPython.core.interactiveshell import InteractiveShell |
|
215 | from IPython.core.interactiveshell import InteractiveShell | |
222 | if InteractiveShell.initialized(): |
|
216 | if InteractiveShell.initialized(): | |
223 | # we are in a Shell environment, give %magic example |
|
217 | # we are in a Shell environment, give %magic example | |
224 | config = "%config " |
|
218 | config = "%config " | |
225 | else: |
|
219 | else: | |
226 | # we are not in a shell, show generic config |
|
220 | # we are not in a shell, show generic config | |
227 | config = "c." |
|
221 | config = "c." | |
228 | print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr) |
|
222 | print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr) | |
229 |
|
223 |
@@ -1,857 +1,972 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """ |
|
2 | """ | |
3 | Pdb debugger class. |
|
3 | Pdb debugger class. | |
4 |
|
4 | |||
5 | Modified from the standard pdb.Pdb class to avoid including readline, so that |
|
5 | Modified from the standard pdb.Pdb class to avoid including readline, so that | |
6 | the command line completion of other programs which include this isn't |
|
6 | the command line completion of other programs which include this isn't | |
7 | damaged. |
|
7 | damaged. | |
8 |
|
8 | |||
9 | In the future, this class will be expanded with improvements over the standard |
|
9 | In the future, this class will be expanded with improvements over the standard | |
10 | pdb. |
|
10 | pdb. | |
11 |
|
11 | |||
12 | The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor |
|
12 | The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor | |
13 | changes. Licensing should therefore be under the standard Python terms. For |
|
13 | changes. Licensing should therefore be under the standard Python terms. For | |
14 | details on the PSF (Python Software Foundation) standard license, see: |
|
14 | details on the PSF (Python Software Foundation) standard license, see: | |
15 |
|
15 | |||
16 | https://docs.python.org/2/license.html |
|
16 | https://docs.python.org/2/license.html | |
17 | """ |
|
17 | """ | |
18 |
|
18 | |||
19 | #***************************************************************************** |
|
19 | #***************************************************************************** | |
20 | # |
|
20 | # | |
21 | # This file is licensed under the PSF license. |
|
21 | # This file is licensed under the PSF license. | |
22 | # |
|
22 | # | |
23 | # Copyright (C) 2001 Python Software Foundation, www.python.org |
|
23 | # Copyright (C) 2001 Python Software Foundation, www.python.org | |
24 | # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu> |
|
24 | # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu> | |
25 | # |
|
25 | # | |
26 | # |
|
26 | # | |
27 | #***************************************************************************** |
|
27 | #***************************************************************************** | |
28 |
|
28 | |||
29 | import bdb |
|
29 | import bdb | |
30 | import functools |
|
30 | import functools | |
31 | import inspect |
|
31 | import inspect | |
32 | import linecache |
|
32 | import linecache | |
33 | import sys |
|
33 | import sys | |
34 | import warnings |
|
34 | import warnings | |
35 | import re |
|
35 | import re | |
|
36 | import os | |||
36 |
|
37 | |||
37 | from IPython import get_ipython |
|
38 | from IPython import get_ipython | |
38 | from IPython.utils import PyColorize |
|
39 | from IPython.utils import PyColorize | |
39 | from IPython.utils import coloransi, py3compat |
|
40 | from IPython.utils import coloransi, py3compat | |
40 | from IPython.core.excolors import exception_colors |
|
41 | from IPython.core.excolors import exception_colors | |
41 | from IPython.testing.skipdoctest import skip_doctest |
|
42 | from IPython.testing.skipdoctest import skip_doctest | |
42 |
|
43 | |||
43 |
|
44 | |||
44 | prompt = 'ipdb> ' |
|
45 | prompt = 'ipdb> ' | |
45 |
|
46 | |||
46 | # We have to check this directly from sys.argv, config struct not yet available |
|
47 | # We have to check this directly from sys.argv, config struct not yet available | |
47 | from pdb import Pdb as OldPdb |
|
48 | from pdb import Pdb as OldPdb | |
48 |
|
49 | |||
49 | # Allow the set_trace code to operate outside of an ipython instance, even if |
|
50 | # Allow the set_trace code to operate outside of an ipython instance, even if | |
50 | # it does so with some limitations. The rest of this support is implemented in |
|
51 | # it does so with some limitations. The rest of this support is implemented in | |
51 | # the Tracer constructor. |
|
52 | # the Tracer constructor. | |
52 |
|
53 | |||
53 |
|
54 | |||
54 | def make_arrow(pad): |
|
55 | def make_arrow(pad): | |
55 | """generate the leading arrow in front of traceback or debugger""" |
|
56 | """generate the leading arrow in front of traceback or debugger""" | |
56 | if pad >= 2: |
|
57 | if pad >= 2: | |
57 | return '-'*(pad-2) + '> ' |
|
58 | return '-'*(pad-2) + '> ' | |
58 | elif pad == 1: |
|
59 | elif pad == 1: | |
59 | return '>' |
|
60 | return '>' | |
60 | return '' |
|
61 | return '' | |
61 |
|
62 | |||
62 |
|
63 | |||
63 | def BdbQuit_excepthook(et, ev, tb, excepthook=None): |
|
64 | def BdbQuit_excepthook(et, ev, tb, excepthook=None): | |
64 | """Exception hook which handles `BdbQuit` exceptions. |
|
65 | """Exception hook which handles `BdbQuit` exceptions. | |
65 |
|
66 | |||
66 | All other exceptions are processed using the `excepthook` |
|
67 | All other exceptions are processed using the `excepthook` | |
67 | parameter. |
|
68 | parameter. | |
68 | """ |
|
69 | """ | |
69 | warnings.warn("`BdbQuit_excepthook` is deprecated since version 5.1", |
|
70 | warnings.warn("`BdbQuit_excepthook` is deprecated since version 5.1", | |
70 | DeprecationWarning, stacklevel=2) |
|
71 | DeprecationWarning, stacklevel=2) | |
71 | if et == bdb.BdbQuit: |
|
72 | if et == bdb.BdbQuit: | |
72 | print('Exiting Debugger.') |
|
73 | print('Exiting Debugger.') | |
73 | elif excepthook is not None: |
|
74 | elif excepthook is not None: | |
74 | excepthook(et, ev, tb) |
|
75 | excepthook(et, ev, tb) | |
75 | else: |
|
76 | else: | |
76 | # Backwards compatibility. Raise deprecation warning? |
|
77 | # Backwards compatibility. Raise deprecation warning? | |
77 | BdbQuit_excepthook.excepthook_ori(et, ev, tb) |
|
78 | BdbQuit_excepthook.excepthook_ori(et, ev, tb) | |
78 |
|
79 | |||
79 |
|
80 | |||
80 | def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None): |
|
81 | def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None): | |
81 | warnings.warn( |
|
82 | warnings.warn( | |
82 | "`BdbQuit_IPython_excepthook` is deprecated since version 5.1", |
|
83 | "`BdbQuit_IPython_excepthook` is deprecated since version 5.1", | |
83 | DeprecationWarning, stacklevel=2) |
|
84 | DeprecationWarning, stacklevel=2) | |
84 | print('Exiting Debugger.') |
|
85 | print('Exiting Debugger.') | |
85 |
|
86 | |||
86 |
|
87 | |||
87 | class Tracer(object): |
|
88 | class Tracer(object): | |
88 | """ |
|
89 | """ | |
89 | DEPRECATED |
|
90 | DEPRECATED | |
90 |
|
91 | |||
91 | Class for local debugging, similar to pdb.set_trace. |
|
92 | Class for local debugging, similar to pdb.set_trace. | |
92 |
|
93 | |||
93 | Instances of this class, when called, behave like pdb.set_trace, but |
|
94 | Instances of this class, when called, behave like pdb.set_trace, but | |
94 | providing IPython's enhanced capabilities. |
|
95 | providing IPython's enhanced capabilities. | |
95 |
|
96 | |||
96 | This is implemented as a class which must be initialized in your own code |
|
97 | This is implemented as a class which must be initialized in your own code | |
97 | and not as a standalone function because we need to detect at runtime |
|
98 | and not as a standalone function because we need to detect at runtime | |
98 | whether IPython is already active or not. That detection is done in the |
|
99 | whether IPython is already active or not. That detection is done in the | |
99 | constructor, ensuring that this code plays nicely with a running IPython, |
|
100 | constructor, ensuring that this code plays nicely with a running IPython, | |
100 | while functioning acceptably (though with limitations) if outside of it. |
|
101 | while functioning acceptably (though with limitations) if outside of it. | |
101 | """ |
|
102 | """ | |
102 |
|
103 | |||
103 | @skip_doctest |
|
104 | @skip_doctest | |
104 | def __init__(self, colors=None): |
|
105 | def __init__(self, colors=None): | |
105 | """ |
|
106 | """ | |
106 | DEPRECATED |
|
107 | DEPRECATED | |
107 |
|
108 | |||
108 | Create a local debugger instance. |
|
109 | Create a local debugger instance. | |
109 |
|
110 | |||
110 | Parameters |
|
111 | Parameters | |
111 | ---------- |
|
112 | ---------- | |
112 |
|
||||
113 | colors : str, optional |
|
113 | colors : str, optional | |
114 | The name of the color scheme to use, it must be one of IPython's |
|
114 | The name of the color scheme to use, it must be one of IPython's | |
115 | valid color schemes. If not given, the function will default to |
|
115 | valid color schemes. If not given, the function will default to | |
116 | the current IPython scheme when running inside IPython, and to |
|
116 | the current IPython scheme when running inside IPython, and to | |
117 | 'NoColor' otherwise. |
|
117 | 'NoColor' otherwise. | |
118 |
|
118 | |||
119 | Examples |
|
119 | Examples | |
120 | -------- |
|
120 | -------- | |
121 | :: |
|
121 | :: | |
122 |
|
122 | |||
123 | from IPython.core.debugger import Tracer; debug_here = Tracer() |
|
123 | from IPython.core.debugger import Tracer; debug_here = Tracer() | |
124 |
|
124 | |||
125 | Later in your code:: |
|
125 | Later in your code:: | |
126 |
|
126 | |||
127 | debug_here() # -> will open up the debugger at that point. |
|
127 | debug_here() # -> will open up the debugger at that point. | |
128 |
|
128 | |||
129 | Once the debugger activates, you can use all of its regular commands to |
|
129 | Once the debugger activates, you can use all of its regular commands to | |
130 | step through code, set breakpoints, etc. See the pdb documentation |
|
130 | step through code, set breakpoints, etc. See the pdb documentation | |
131 | from the Python standard library for usage details. |
|
131 | from the Python standard library for usage details. | |
132 | """ |
|
132 | """ | |
133 | warnings.warn("`Tracer` is deprecated since version 5.1, directly use " |
|
133 | warnings.warn("`Tracer` is deprecated since version 5.1, directly use " | |
134 | "`IPython.core.debugger.Pdb.set_trace()`", |
|
134 | "`IPython.core.debugger.Pdb.set_trace()`", | |
135 | DeprecationWarning, stacklevel=2) |
|
135 | DeprecationWarning, stacklevel=2) | |
136 |
|
136 | |||
137 | ip = get_ipython() |
|
137 | ip = get_ipython() | |
138 | if ip is None: |
|
138 | if ip is None: | |
139 | # Outside of ipython, we set our own exception hook manually |
|
139 | # Outside of ipython, we set our own exception hook manually | |
140 | sys.excepthook = functools.partial(BdbQuit_excepthook, |
|
140 | sys.excepthook = functools.partial(BdbQuit_excepthook, | |
141 | excepthook=sys.excepthook) |
|
141 | excepthook=sys.excepthook) | |
142 | def_colors = 'NoColor' |
|
142 | def_colors = 'NoColor' | |
143 | else: |
|
143 | else: | |
144 | # In ipython, we use its custom exception handler mechanism |
|
144 | # In ipython, we use its custom exception handler mechanism | |
145 | def_colors = ip.colors |
|
145 | def_colors = ip.colors | |
146 | ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook) |
|
146 | ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook) | |
147 |
|
147 | |||
148 | if colors is None: |
|
148 | if colors is None: | |
149 | colors = def_colors |
|
149 | colors = def_colors | |
150 |
|
150 | |||
151 | # The stdlib debugger internally uses a modified repr from the `repr` |
|
151 | # The stdlib debugger internally uses a modified repr from the `repr` | |
152 | # module, that limits the length of printed strings to a hardcoded |
|
152 | # module, that limits the length of printed strings to a hardcoded | |
153 | # limit of 30 characters. That much trimming is too aggressive, let's |
|
153 | # limit of 30 characters. That much trimming is too aggressive, let's | |
154 | # at least raise that limit to 80 chars, which should be enough for |
|
154 | # at least raise that limit to 80 chars, which should be enough for | |
155 | # most interactive uses. |
|
155 | # most interactive uses. | |
156 | try: |
|
156 | try: | |
157 | from reprlib import aRepr |
|
157 | from reprlib import aRepr | |
158 | aRepr.maxstring = 80 |
|
158 | aRepr.maxstring = 80 | |
159 | except: |
|
159 | except: | |
160 | # This is only a user-facing convenience, so any error we encounter |
|
160 | # This is only a user-facing convenience, so any error we encounter | |
161 | # here can be warned about but can be otherwise ignored. These |
|
161 | # here can be warned about but can be otherwise ignored. These | |
162 | # printouts will tell us about problems if this API changes |
|
162 | # printouts will tell us about problems if this API changes | |
163 | import traceback |
|
163 | import traceback | |
164 | traceback.print_exc() |
|
164 | traceback.print_exc() | |
165 |
|
165 | |||
166 | self.debugger = Pdb(colors) |
|
166 | self.debugger = Pdb(colors) | |
167 |
|
167 | |||
168 | def __call__(self): |
|
168 | def __call__(self): | |
169 | """Starts an interactive debugger at the point where called. |
|
169 | """Starts an interactive debugger at the point where called. | |
170 |
|
170 | |||
171 | This is similar to the pdb.set_trace() function from the std lib, but |
|
171 | This is similar to the pdb.set_trace() function from the std lib, but | |
172 | using IPython's enhanced debugger.""" |
|
172 | using IPython's enhanced debugger.""" | |
173 |
|
173 | |||
174 | self.debugger.set_trace(sys._getframe().f_back) |
|
174 | self.debugger.set_trace(sys._getframe().f_back) | |
175 |
|
175 | |||
176 |
|
176 | |||
177 | RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+') |
|
177 | RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+') | |
178 |
|
178 | |||
179 |
|
179 | |||
180 | def strip_indentation(multiline_string): |
|
180 | def strip_indentation(multiline_string): | |
181 | return RGX_EXTRA_INDENT.sub('', multiline_string) |
|
181 | return RGX_EXTRA_INDENT.sub('', multiline_string) | |
182 |
|
182 | |||
183 |
|
183 | |||
184 | def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): |
|
184 | def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): | |
185 | """Make new_fn have old_fn's doc string. This is particularly useful |
|
185 | """Make new_fn have old_fn's doc string. This is particularly useful | |
186 | for the ``do_...`` commands that hook into the help system. |
|
186 | for the ``do_...`` commands that hook into the help system. | |
187 | Adapted from from a comp.lang.python posting |
|
187 | Adapted from from a comp.lang.python posting | |
188 | by Duncan Booth.""" |
|
188 | by Duncan Booth.""" | |
189 | def wrapper(*args, **kw): |
|
189 | def wrapper(*args, **kw): | |
190 | return new_fn(*args, **kw) |
|
190 | return new_fn(*args, **kw) | |
191 | if old_fn.__doc__: |
|
191 | if old_fn.__doc__: | |
192 | wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text |
|
192 | wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text | |
193 | return wrapper |
|
193 | return wrapper | |
194 |
|
194 | |||
195 |
|
195 | |||
196 | class Pdb(OldPdb): |
|
196 | class Pdb(OldPdb): | |
197 | """Modified Pdb class, does not load readline. |
|
197 | """Modified Pdb class, does not load readline. | |
198 |
|
198 | |||
199 | for a standalone version that uses prompt_toolkit, see |
|
199 | for a standalone version that uses prompt_toolkit, see | |
200 | `IPython.terminal.debugger.TerminalPdb` and |
|
200 | `IPython.terminal.debugger.TerminalPdb` and | |
201 | `IPython.terminal.debugger.set_trace()` |
|
201 | `IPython.terminal.debugger.set_trace()` | |
|
202 | ||||
|
203 | ||||
|
204 | This debugger can hide and skip frames that are tagged according to some predicates. | |||
|
205 | See the `skip_predicates` commands. | |||
|
206 | ||||
202 | """ |
|
207 | """ | |
203 |
|
208 | |||
|
209 | default_predicates = {"tbhide": True, "readonly": False, "ipython_internal": True} | |||
|
210 | ||||
204 | def __init__(self, color_scheme=None, completekey=None, |
|
211 | def __init__(self, color_scheme=None, completekey=None, | |
205 | stdin=None, stdout=None, context=5, **kwargs): |
|
212 | stdin=None, stdout=None, context=5, **kwargs): | |
206 | """Create a new IPython debugger. |
|
213 | """Create a new IPython debugger. | |
207 |
|
214 | |||
208 | :param color_scheme: Deprecated, do not use. |
|
215 | Parameters | |
209 | :param completekey: Passed to pdb.Pdb. |
|
216 | ---------- | |
210 | :param stdin: Passed to pdb.Pdb. |
|
217 | color_scheme : default None | |
211 | :param stdout: Passed to pdb.Pdb. |
|
218 | Deprecated, do not use. | |
212 | :param context: Number of lines of source code context to show when |
|
219 | completekey : default None | |
|
220 | Passed to pdb.Pdb. | |||
|
221 | stdin : default None | |||
|
222 | Passed to pdb.Pdb. | |||
|
223 | stdout : default None | |||
|
224 | Passed to pdb.Pdb. | |||
|
225 | context : int | |||
|
226 | Number of lines of source code context to show when | |||
213 | displaying stacktrace information. |
|
227 | displaying stacktrace information. | |
214 | :param kwargs: Passed to pdb.Pdb. |
|
228 | **kwargs | |
215 | The possibilities are python version dependent, see the python |
|
229 | Passed to pdb.Pdb. | |
216 | docs for more info. |
|
230 | ||
|
231 | Notes | |||
|
232 | ----- | |||
|
233 | The possibilities are python version dependent, see the python | |||
|
234 | docs for more info. | |||
217 | """ |
|
235 | """ | |
218 |
|
236 | |||
219 | # Parent constructor: |
|
237 | # Parent constructor: | |
220 | try: |
|
238 | try: | |
221 | self.context = int(context) |
|
239 | self.context = int(context) | |
222 | if self.context <= 0: |
|
240 | if self.context <= 0: | |
223 | raise ValueError("Context must be a positive integer") |
|
241 | raise ValueError("Context must be a positive integer") | |
224 | except (TypeError, ValueError) as e: |
|
242 | except (TypeError, ValueError) as e: | |
225 | raise ValueError("Context must be a positive integer") from e |
|
243 | raise ValueError("Context must be a positive integer") from e | |
226 |
|
244 | |||
227 | # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`. |
|
245 | # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`. | |
228 | OldPdb.__init__(self, completekey, stdin, stdout, **kwargs) |
|
246 | OldPdb.__init__(self, completekey, stdin, stdout, **kwargs) | |
229 |
|
247 | |||
230 | # IPython changes... |
|
248 | # IPython changes... | |
231 | self.shell = get_ipython() |
|
249 | self.shell = get_ipython() | |
232 |
|
250 | |||
233 | if self.shell is None: |
|
251 | if self.shell is None: | |
234 | save_main = sys.modules['__main__'] |
|
252 | save_main = sys.modules['__main__'] | |
235 | # No IPython instance running, we must create one |
|
253 | # No IPython instance running, we must create one | |
236 | from IPython.terminal.interactiveshell import \ |
|
254 | from IPython.terminal.interactiveshell import \ | |
237 | TerminalInteractiveShell |
|
255 | TerminalInteractiveShell | |
238 | self.shell = TerminalInteractiveShell.instance() |
|
256 | self.shell = TerminalInteractiveShell.instance() | |
239 | # needed by any code which calls __import__("__main__") after |
|
257 | # needed by any code which calls __import__("__main__") after | |
240 | # the debugger was entered. See also #9941. |
|
258 | # the debugger was entered. See also #9941. | |
241 | sys.modules["__main__"] = save_main |
|
259 | sys.modules["__main__"] = save_main | |
242 |
|
260 | |||
243 | if color_scheme is not None: |
|
261 | if color_scheme is not None: | |
244 | warnings.warn( |
|
262 | warnings.warn( | |
245 | "The `color_scheme` argument is deprecated since version 5.1", |
|
263 | "The `color_scheme` argument is deprecated since version 5.1", | |
246 | DeprecationWarning, stacklevel=2) |
|
264 | DeprecationWarning, stacklevel=2) | |
247 | else: |
|
265 | else: | |
248 | color_scheme = self.shell.colors |
|
266 | color_scheme = self.shell.colors | |
249 |
|
267 | |||
250 | self.aliases = {} |
|
268 | self.aliases = {} | |
251 |
|
269 | |||
252 | # Create color table: we copy the default one from the traceback |
|
270 | # Create color table: we copy the default one from the traceback | |
253 | # module and add a few attributes needed for debugging |
|
271 | # module and add a few attributes needed for debugging | |
254 | self.color_scheme_table = exception_colors() |
|
272 | self.color_scheme_table = exception_colors() | |
255 |
|
273 | |||
256 | # shorthands |
|
274 | # shorthands | |
257 | C = coloransi.TermColors |
|
275 | C = coloransi.TermColors | |
258 | cst = self.color_scheme_table |
|
276 | cst = self.color_scheme_table | |
259 |
|
277 | |||
260 | cst['NoColor'].colors.prompt = C.NoColor |
|
278 | cst['NoColor'].colors.prompt = C.NoColor | |
261 | cst['NoColor'].colors.breakpoint_enabled = C.NoColor |
|
279 | cst['NoColor'].colors.breakpoint_enabled = C.NoColor | |
262 | cst['NoColor'].colors.breakpoint_disabled = C.NoColor |
|
280 | cst['NoColor'].colors.breakpoint_disabled = C.NoColor | |
263 |
|
281 | |||
264 | cst['Linux'].colors.prompt = C.Green |
|
282 | cst['Linux'].colors.prompt = C.Green | |
265 | cst['Linux'].colors.breakpoint_enabled = C.LightRed |
|
283 | cst['Linux'].colors.breakpoint_enabled = C.LightRed | |
266 | cst['Linux'].colors.breakpoint_disabled = C.Red |
|
284 | cst['Linux'].colors.breakpoint_disabled = C.Red | |
267 |
|
285 | |||
268 | cst['LightBG'].colors.prompt = C.Blue |
|
286 | cst['LightBG'].colors.prompt = C.Blue | |
269 | cst['LightBG'].colors.breakpoint_enabled = C.LightRed |
|
287 | cst['LightBG'].colors.breakpoint_enabled = C.LightRed | |
270 | cst['LightBG'].colors.breakpoint_disabled = C.Red |
|
288 | cst['LightBG'].colors.breakpoint_disabled = C.Red | |
271 |
|
289 | |||
272 | cst['Neutral'].colors.prompt = C.Blue |
|
290 | cst['Neutral'].colors.prompt = C.Blue | |
273 | cst['Neutral'].colors.breakpoint_enabled = C.LightRed |
|
291 | cst['Neutral'].colors.breakpoint_enabled = C.LightRed | |
274 | cst['Neutral'].colors.breakpoint_disabled = C.Red |
|
292 | cst['Neutral'].colors.breakpoint_disabled = C.Red | |
275 |
|
293 | |||
276 | # Add a python parser so we can syntax highlight source while |
|
294 | # Add a python parser so we can syntax highlight source while | |
277 | # debugging. |
|
295 | # debugging. | |
278 | self.parser = PyColorize.Parser(style=color_scheme) |
|
296 | self.parser = PyColorize.Parser(style=color_scheme) | |
279 | self.set_colors(color_scheme) |
|
297 | self.set_colors(color_scheme) | |
280 |
|
298 | |||
281 | # Set the prompt - the default prompt is '(Pdb)' |
|
299 | # Set the prompt - the default prompt is '(Pdb)' | |
282 | self.prompt = prompt |
|
300 | self.prompt = prompt | |
283 | self.skip_hidden = True |
|
301 | self.skip_hidden = True | |
|
302 | self.report_skipped = True | |||
|
303 | ||||
|
304 | # list of predicates we use to skip frames | |||
|
305 | self._predicates = self.default_predicates | |||
284 |
|
306 | |||
285 | def set_colors(self, scheme): |
|
307 | def set_colors(self, scheme): | |
286 | """Shorthand access to the color table scheme selector method.""" |
|
308 | """Shorthand access to the color table scheme selector method.""" | |
287 | self.color_scheme_table.set_active_scheme(scheme) |
|
309 | self.color_scheme_table.set_active_scheme(scheme) | |
288 | self.parser.style = scheme |
|
310 | self.parser.style = scheme | |
289 |
|
311 | |||
290 | def set_trace(self, frame=None): |
|
312 | def set_trace(self, frame=None): | |
291 | if frame is None: |
|
313 | if frame is None: | |
292 | frame = sys._getframe().f_back |
|
314 | frame = sys._getframe().f_back | |
293 | self.initial_frame = frame |
|
315 | self.initial_frame = frame | |
294 | return super().set_trace(frame) |
|
316 | return super().set_trace(frame) | |
295 |
|
317 | |||
|
318 | def _hidden_predicate(self, frame): | |||
|
319 | """ | |||
|
320 | Given a frame return whether it it should be hidden or not by IPython. | |||
|
321 | """ | |||
|
322 | ||||
|
323 | if self._predicates["readonly"]: | |||
|
324 | fname = frame.f_code.co_filename | |||
|
325 | # we need to check for file existence and interactively define | |||
|
326 | # function would otherwise appear as RO. | |||
|
327 | if os.path.isfile(fname) and not os.access(fname, os.W_OK): | |||
|
328 | return True | |||
|
329 | ||||
|
330 | if self._predicates["tbhide"]: | |||
|
331 | if frame in (self.curframe, getattr(self, "initial_frame", None)): | |||
|
332 | return False | |||
|
333 | else: | |||
|
334 | return self._get_frame_locals(frame).get("__tracebackhide__", False) | |||
|
335 | ||||
|
336 | return False | |||
|
337 | ||||
296 | def hidden_frames(self, stack): |
|
338 | def hidden_frames(self, stack): | |
297 | """ |
|
339 | """ | |
298 | Given an index in the stack return whether it should be skipped. |
|
340 | Given an index in the stack return whether it should be skipped. | |
299 |
|
341 | |||
300 | This is used in up/down and where to skip frames. |
|
342 | This is used in up/down and where to skip frames. | |
301 | """ |
|
343 | """ | |
302 | # The f_locals dictionary is updated from the actual frame |
|
344 | # The f_locals dictionary is updated from the actual frame | |
303 | # locals whenever the .f_locals accessor is called, so we |
|
345 | # locals whenever the .f_locals accessor is called, so we | |
304 | # avoid calling it here to preserve self.curframe_locals. |
|
346 | # avoid calling it here to preserve self.curframe_locals. | |
305 | # Futhermore, there is no good reason to hide the current frame. |
|
347 | # Futhermore, there is no good reason to hide the current frame. | |
306 | ip_hide = [ |
|
348 | ip_hide = [self._hidden_predicate(s[0]) for s in stack] | |
307 | False |
|
|||
308 | if s[0] in (self.curframe, getattr(self, "initial_frame", None)) |
|
|||
309 | else s[0].f_locals.get("__tracebackhide__", False) |
|
|||
310 | for s in stack |
|
|||
311 | ] |
|
|||
312 | ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"] |
|
349 | ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"] | |
313 | if ip_start: |
|
350 | if ip_start and self._predicates["ipython_internal"]: | |
314 | ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)] |
|
351 | ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)] | |
315 | return ip_hide |
|
352 | return ip_hide | |
316 |
|
353 | |||
317 | def interaction(self, frame, traceback): |
|
354 | def interaction(self, frame, traceback): | |
318 | try: |
|
355 | try: | |
319 | OldPdb.interaction(self, frame, traceback) |
|
356 | OldPdb.interaction(self, frame, traceback) | |
320 | except KeyboardInterrupt: |
|
357 | except KeyboardInterrupt: | |
321 | self.stdout.write("\n" + self.shell.get_exception_only()) |
|
358 | self.stdout.write("\n" + self.shell.get_exception_only()) | |
322 |
|
359 | |||
323 | def precmd(self, line): |
|
360 | def precmd(self, line): | |
324 | """Perform useful escapes on the command before it is executed.""" |
|
361 | """Perform useful escapes on the command before it is executed.""" | |
325 |
|
362 | |||
326 | if line.endswith("??"): |
|
363 | if line.endswith("??"): | |
327 | line = "pinfo2 " + line[:-2] |
|
364 | line = "pinfo2 " + line[:-2] | |
328 | elif line.endswith("?"): |
|
365 | elif line.endswith("?"): | |
329 | line = "pinfo " + line[:-1] |
|
366 | line = "pinfo " + line[:-1] | |
330 |
|
367 | |||
331 | line = super().precmd(line) |
|
368 | line = super().precmd(line) | |
332 |
|
369 | |||
333 | return line |
|
370 | return line | |
334 |
|
371 | |||
335 | def new_do_frame(self, arg): |
|
372 | def new_do_frame(self, arg): | |
336 | OldPdb.do_frame(self, arg) |
|
373 | OldPdb.do_frame(self, arg) | |
337 |
|
374 | |||
338 | def new_do_quit(self, arg): |
|
375 | def new_do_quit(self, arg): | |
339 |
|
376 | |||
340 | if hasattr(self, 'old_all_completions'): |
|
377 | if hasattr(self, 'old_all_completions'): | |
341 | self.shell.Completer.all_completions = self.old_all_completions |
|
378 | self.shell.Completer.all_completions = self.old_all_completions | |
342 |
|
379 | |||
343 | return OldPdb.do_quit(self, arg) |
|
380 | return OldPdb.do_quit(self, arg) | |
344 |
|
381 | |||
345 | do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit) |
|
382 | do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit) | |
346 |
|
383 | |||
347 | def new_do_restart(self, arg): |
|
384 | def new_do_restart(self, arg): | |
348 | """Restart command. In the context of ipython this is exactly the same |
|
385 | """Restart command. In the context of ipython this is exactly the same | |
349 | thing as 'quit'.""" |
|
386 | thing as 'quit'.""" | |
350 | self.msg("Restart doesn't make sense here. Using 'quit' instead.") |
|
387 | self.msg("Restart doesn't make sense here. Using 'quit' instead.") | |
351 | return self.do_quit(arg) |
|
388 | return self.do_quit(arg) | |
352 |
|
389 | |||
353 | def print_stack_trace(self, context=None): |
|
390 | def print_stack_trace(self, context=None): | |
354 | Colors = self.color_scheme_table.active_colors |
|
391 | Colors = self.color_scheme_table.active_colors | |
355 | ColorsNormal = Colors.Normal |
|
392 | ColorsNormal = Colors.Normal | |
356 | if context is None: |
|
393 | if context is None: | |
357 | context = self.context |
|
394 | context = self.context | |
358 | try: |
|
395 | try: | |
359 | context = int(context) |
|
396 | context = int(context) | |
360 | if context <= 0: |
|
397 | if context <= 0: | |
361 | raise ValueError("Context must be a positive integer") |
|
398 | raise ValueError("Context must be a positive integer") | |
362 | except (TypeError, ValueError) as e: |
|
399 | except (TypeError, ValueError) as e: | |
363 | raise ValueError("Context must be a positive integer") from e |
|
400 | raise ValueError("Context must be a positive integer") from e | |
364 | try: |
|
401 | try: | |
365 | skipped = 0 |
|
402 | skipped = 0 | |
366 | for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack): |
|
403 | for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack): | |
367 | if hidden and self.skip_hidden: |
|
404 | if hidden and self.skip_hidden: | |
368 | skipped += 1 |
|
405 | skipped += 1 | |
369 | continue |
|
406 | continue | |
370 | if skipped: |
|
407 | if skipped: | |
371 | print( |
|
408 | print( | |
372 | f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" |
|
409 | f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" | |
373 | ) |
|
410 | ) | |
374 | skipped = 0 |
|
411 | skipped = 0 | |
375 | self.print_stack_entry(frame_lineno, context=context) |
|
412 | self.print_stack_entry(frame_lineno, context=context) | |
376 | if skipped: |
|
413 | if skipped: | |
377 | print( |
|
414 | print( | |
378 | f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" |
|
415 | f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n" | |
379 | ) |
|
416 | ) | |
380 | except KeyboardInterrupt: |
|
417 | except KeyboardInterrupt: | |
381 | pass |
|
418 | pass | |
382 |
|
419 | |||
383 | def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ', |
|
420 | def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ', | |
384 | context=None): |
|
421 | context=None): | |
385 | if context is None: |
|
422 | if context is None: | |
386 | context = self.context |
|
423 | context = self.context | |
387 | try: |
|
424 | try: | |
388 | context = int(context) |
|
425 | context = int(context) | |
389 | if context <= 0: |
|
426 | if context <= 0: | |
390 | raise ValueError("Context must be a positive integer") |
|
427 | raise ValueError("Context must be a positive integer") | |
391 | except (TypeError, ValueError) as e: |
|
428 | except (TypeError, ValueError) as e: | |
392 | raise ValueError("Context must be a positive integer") from e |
|
429 | raise ValueError("Context must be a positive integer") from e | |
393 | print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout) |
|
430 | print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout) | |
394 |
|
431 | |||
395 | # vds: >> |
|
432 | # vds: >> | |
396 | frame, lineno = frame_lineno |
|
433 | frame, lineno = frame_lineno | |
397 | filename = frame.f_code.co_filename |
|
434 | filename = frame.f_code.co_filename | |
398 | self.shell.hooks.synchronize_with_editor(filename, lineno, 0) |
|
435 | self.shell.hooks.synchronize_with_editor(filename, lineno, 0) | |
399 | # vds: << |
|
436 | # vds: << | |
400 |
|
437 | |||
|
438 | def _get_frame_locals(self, frame): | |||
|
439 | """ " | |||
|
440 | Acessing f_local of current frame reset the namespace, so we want to avoid | |||
|
441 | that or the following can happend | |||
|
442 | ||||
|
443 | ipdb> foo | |||
|
444 | "old" | |||
|
445 | ipdb> foo = "new" | |||
|
446 | ipdb> foo | |||
|
447 | "new" | |||
|
448 | ipdb> where | |||
|
449 | ipdb> foo | |||
|
450 | "old" | |||
|
451 | ||||
|
452 | So if frame is self.current_frame we instead return self.curframe_locals | |||
|
453 | ||||
|
454 | """ | |||
|
455 | if frame is self.curframe: | |||
|
456 | return self.curframe_locals | |||
|
457 | else: | |||
|
458 | return frame.f_locals | |||
|
459 | ||||
401 | def format_stack_entry(self, frame_lineno, lprefix=': ', context=None): |
|
460 | def format_stack_entry(self, frame_lineno, lprefix=': ', context=None): | |
402 | if context is None: |
|
461 | if context is None: | |
403 | context = self.context |
|
462 | context = self.context | |
404 | try: |
|
463 | try: | |
405 | context = int(context) |
|
464 | context = int(context) | |
406 | if context <= 0: |
|
465 | if context <= 0: | |
407 | print("Context must be a positive integer", file=self.stdout) |
|
466 | print("Context must be a positive integer", file=self.stdout) | |
408 | except (TypeError, ValueError): |
|
467 | except (TypeError, ValueError): | |
409 | print("Context must be a positive integer", file=self.stdout) |
|
468 | print("Context must be a positive integer", file=self.stdout) | |
410 |
|
469 | |||
411 | import reprlib |
|
470 | import reprlib | |
412 |
|
471 | |||
413 | ret = [] |
|
472 | ret = [] | |
414 |
|
473 | |||
415 | Colors = self.color_scheme_table.active_colors |
|
474 | Colors = self.color_scheme_table.active_colors | |
416 | ColorsNormal = Colors.Normal |
|
475 | ColorsNormal = Colors.Normal | |
417 | tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal) |
|
476 | tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal) | |
418 | tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal) |
|
477 | tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal) | |
419 | tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal) |
|
478 | tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal) | |
420 | tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal) |
|
479 | tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal) | |
421 |
|
480 | |||
422 | frame, lineno = frame_lineno |
|
481 | frame, lineno = frame_lineno | |
423 |
|
482 | |||
424 | return_value = '' |
|
483 | return_value = '' | |
425 | if '__return__' in frame.f_locals: |
|
484 | loc_frame = self._get_frame_locals(frame) | |
426 | rv = frame.f_locals['__return__'] |
|
485 | if "__return__" in loc_frame: | |
427 | #return_value += '->' |
|
486 | rv = loc_frame["__return__"] | |
428 |
return_value += |
|
487 | # return_value += '->' | |
|
488 | return_value += reprlib.repr(rv) + "\n" | |||
429 | ret.append(return_value) |
|
489 | ret.append(return_value) | |
430 |
|
490 | |||
431 | #s = filename + '(' + `lineno` + ')' |
|
491 | #s = filename + '(' + `lineno` + ')' | |
432 | filename = self.canonic(frame.f_code.co_filename) |
|
492 | filename = self.canonic(frame.f_code.co_filename) | |
433 | link = tpl_link % py3compat.cast_unicode(filename) |
|
493 | link = tpl_link % py3compat.cast_unicode(filename) | |
434 |
|
494 | |||
435 | if frame.f_code.co_name: |
|
495 | if frame.f_code.co_name: | |
436 | func = frame.f_code.co_name |
|
496 | func = frame.f_code.co_name | |
437 | else: |
|
497 | else: | |
438 | func = "<lambda>" |
|
498 | func = "<lambda>" | |
439 |
|
499 | |||
440 |
call = |
|
500 | call = "" | |
441 |
if func != |
|
501 | if func != "?": | |
442 |
if |
|
502 | if "__args__" in loc_frame: | |
443 |
args = reprlib.repr(frame |
|
503 | args = reprlib.repr(loc_frame["__args__"]) | |
444 | else: |
|
504 | else: | |
445 | args = '()' |
|
505 | args = '()' | |
446 | call = tpl_call % (func, args) |
|
506 | call = tpl_call % (func, args) | |
447 |
|
507 | |||
448 | # The level info should be generated in the same format pdb uses, to |
|
508 | # The level info should be generated in the same format pdb uses, to | |
449 | # avoid breaking the pdbtrack functionality of python-mode in *emacs. |
|
509 | # avoid breaking the pdbtrack functionality of python-mode in *emacs. | |
450 | if frame is self.curframe: |
|
510 | if frame is self.curframe: | |
451 | ret.append('> ') |
|
511 | ret.append('> ') | |
452 | else: |
|
512 | else: | |
453 | ret.append(" ") |
|
513 | ret.append(" ") | |
454 | ret.append("%s(%s)%s\n" % (link, lineno, call)) |
|
514 | ret.append("%s(%s)%s\n" % (link, lineno, call)) | |
455 |
|
515 | |||
456 | start = lineno - 1 - context//2 |
|
516 | start = lineno - 1 - context//2 | |
457 | lines = linecache.getlines(filename) |
|
517 | lines = linecache.getlines(filename) | |
458 | start = min(start, len(lines) - context) |
|
518 | start = min(start, len(lines) - context) | |
459 | start = max(start, 0) |
|
519 | start = max(start, 0) | |
460 | lines = lines[start : start + context] |
|
520 | lines = lines[start : start + context] | |
461 |
|
521 | |||
462 | for i, line in enumerate(lines): |
|
522 | for i, line in enumerate(lines): | |
463 | show_arrow = start + 1 + i == lineno |
|
523 | show_arrow = start + 1 + i == lineno | |
464 | linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line |
|
524 | linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line | |
465 | ret.append( |
|
525 | ret.append( | |
466 | self.__format_line( |
|
526 | self.__format_line( | |
467 | linetpl, filename, start + 1 + i, line, arrow=show_arrow |
|
527 | linetpl, filename, start + 1 + i, line, arrow=show_arrow | |
468 | ) |
|
528 | ) | |
469 | ) |
|
529 | ) | |
470 | return "".join(ret) |
|
530 | return "".join(ret) | |
471 |
|
531 | |||
472 | def __format_line(self, tpl_line, filename, lineno, line, arrow=False): |
|
532 | def __format_line(self, tpl_line, filename, lineno, line, arrow=False): | |
473 | bp_mark = "" |
|
533 | bp_mark = "" | |
474 | bp_mark_color = "" |
|
534 | bp_mark_color = "" | |
475 |
|
535 | |||
476 | new_line, err = self.parser.format2(line, 'str') |
|
536 | new_line, err = self.parser.format2(line, 'str') | |
477 | if not err: |
|
537 | if not err: | |
478 | line = new_line |
|
538 | line = new_line | |
479 |
|
539 | |||
480 | bp = None |
|
540 | bp = None | |
481 | if lineno in self.get_file_breaks(filename): |
|
541 | if lineno in self.get_file_breaks(filename): | |
482 | bps = self.get_breaks(filename, lineno) |
|
542 | bps = self.get_breaks(filename, lineno) | |
483 | bp = bps[-1] |
|
543 | bp = bps[-1] | |
484 |
|
544 | |||
485 | if bp: |
|
545 | if bp: | |
486 | Colors = self.color_scheme_table.active_colors |
|
546 | Colors = self.color_scheme_table.active_colors | |
487 | bp_mark = str(bp.number) |
|
547 | bp_mark = str(bp.number) | |
488 | bp_mark_color = Colors.breakpoint_enabled |
|
548 | bp_mark_color = Colors.breakpoint_enabled | |
489 | if not bp.enabled: |
|
549 | if not bp.enabled: | |
490 | bp_mark_color = Colors.breakpoint_disabled |
|
550 | bp_mark_color = Colors.breakpoint_disabled | |
491 |
|
551 | |||
492 | numbers_width = 7 |
|
552 | numbers_width = 7 | |
493 | if arrow: |
|
553 | if arrow: | |
494 | # This is the line with the error |
|
554 | # This is the line with the error | |
495 | pad = numbers_width - len(str(lineno)) - len(bp_mark) |
|
555 | pad = numbers_width - len(str(lineno)) - len(bp_mark) | |
496 | num = '%s%s' % (make_arrow(pad), str(lineno)) |
|
556 | num = '%s%s' % (make_arrow(pad), str(lineno)) | |
497 | else: |
|
557 | else: | |
498 | num = '%*s' % (numbers_width - len(bp_mark), str(lineno)) |
|
558 | num = '%*s' % (numbers_width - len(bp_mark), str(lineno)) | |
499 |
|
559 | |||
500 | return tpl_line % (bp_mark_color + bp_mark, num, line) |
|
560 | return tpl_line % (bp_mark_color + bp_mark, num, line) | |
501 |
|
561 | |||
502 | def print_list_lines(self, filename, first, last): |
|
562 | def print_list_lines(self, filename, first, last): | |
503 | """The printing (as opposed to the parsing part of a 'list' |
|
563 | """The printing (as opposed to the parsing part of a 'list' | |
504 | command.""" |
|
564 | command.""" | |
505 | try: |
|
565 | try: | |
506 | Colors = self.color_scheme_table.active_colors |
|
566 | Colors = self.color_scheme_table.active_colors | |
507 | ColorsNormal = Colors.Normal |
|
567 | ColorsNormal = Colors.Normal | |
508 | tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal) |
|
568 | tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal) | |
509 | tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal) |
|
569 | tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal) | |
510 | src = [] |
|
570 | src = [] | |
511 | if filename == "<string>" and hasattr(self, "_exec_filename"): |
|
571 | if filename == "<string>" and hasattr(self, "_exec_filename"): | |
512 | filename = self._exec_filename |
|
572 | filename = self._exec_filename | |
513 |
|
573 | |||
514 | for lineno in range(first, last+1): |
|
574 | for lineno in range(first, last+1): | |
515 | line = linecache.getline(filename, lineno) |
|
575 | line = linecache.getline(filename, lineno) | |
516 | if not line: |
|
576 | if not line: | |
517 | break |
|
577 | break | |
518 |
|
578 | |||
519 | if lineno == self.curframe.f_lineno: |
|
579 | if lineno == self.curframe.f_lineno: | |
520 | line = self.__format_line( |
|
580 | line = self.__format_line( | |
521 | tpl_line_em, filename, lineno, line, arrow=True |
|
581 | tpl_line_em, filename, lineno, line, arrow=True | |
522 | ) |
|
582 | ) | |
523 | else: |
|
583 | else: | |
524 | line = self.__format_line( |
|
584 | line = self.__format_line( | |
525 | tpl_line, filename, lineno, line, arrow=False |
|
585 | tpl_line, filename, lineno, line, arrow=False | |
526 | ) |
|
586 | ) | |
527 |
|
587 | |||
528 | src.append(line) |
|
588 | src.append(line) | |
529 | self.lineno = lineno |
|
589 | self.lineno = lineno | |
530 |
|
590 | |||
531 | print(''.join(src), file=self.stdout) |
|
591 | print(''.join(src), file=self.stdout) | |
532 |
|
592 | |||
533 | except KeyboardInterrupt: |
|
593 | except KeyboardInterrupt: | |
534 | pass |
|
594 | pass | |
535 |
|
595 | |||
|
596 | def do_skip_predicates(self, args): | |||
|
597 | """ | |||
|
598 | Turn on/off individual predicates as to whether a frame should be hidden/skip. | |||
|
599 | ||||
|
600 | The global option to skip (or not) hidden frames is set with skip_hidden | |||
|
601 | ||||
|
602 | To change the value of a predicate | |||
|
603 | ||||
|
604 | skip_predicates key [true|false] | |||
|
605 | ||||
|
606 | Call without arguments to see the current values. | |||
|
607 | ||||
|
608 | To permanently change the value of an option add the corresponding | |||
|
609 | command to your ``~/.pdbrc`` file. If you are programmatically using the | |||
|
610 | Pdb instance you can also change the ``default_predicates`` class | |||
|
611 | attribute. | |||
|
612 | """ | |||
|
613 | if not args.strip(): | |||
|
614 | print("current predicates:") | |||
|
615 | for (p, v) in self._predicates.items(): | |||
|
616 | print(" ", p, ":", v) | |||
|
617 | return | |||
|
618 | type_value = args.strip().split(" ") | |||
|
619 | if len(type_value) != 2: | |||
|
620 | print( | |||
|
621 | f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}" | |||
|
622 | ) | |||
|
623 | return | |||
|
624 | ||||
|
625 | type_, value = type_value | |||
|
626 | if type_ not in self._predicates: | |||
|
627 | print(f"{type_!r} not in {set(self._predicates.keys())}") | |||
|
628 | return | |||
|
629 | if value.lower() not in ("true", "yes", "1", "no", "false", "0"): | |||
|
630 | print( | |||
|
631 | f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')" | |||
|
632 | ) | |||
|
633 | return | |||
|
634 | ||||
|
635 | self._predicates[type_] = value.lower() in ("true", "yes", "1") | |||
|
636 | if not any(self._predicates.values()): | |||
|
637 | print( | |||
|
638 | "Warning, all predicates set to False, skip_hidden may not have any effects." | |||
|
639 | ) | |||
|
640 | ||||
536 | def do_skip_hidden(self, arg): |
|
641 | def do_skip_hidden(self, arg): | |
537 | """ |
|
642 | """ | |
538 | Change whether or not we should skip frames with the |
|
643 | Change whether or not we should skip frames with the | |
539 | __tracebackhide__ attribute. |
|
644 | __tracebackhide__ attribute. | |
540 | """ |
|
645 | """ | |
541 | if arg.strip().lower() in ("true", "yes"): |
|
646 | if not arg.strip(): | |
|
647 | print( | |||
|
648 | f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change." | |||
|
649 | ) | |||
|
650 | elif arg.strip().lower() in ("true", "yes"): | |||
542 | self.skip_hidden = True |
|
651 | self.skip_hidden = True | |
543 | elif arg.strip().lower() in ("false", "no"): |
|
652 | elif arg.strip().lower() in ("false", "no"): | |
544 | self.skip_hidden = False |
|
653 | self.skip_hidden = False | |
|
654 | if not any(self._predicates.values()): | |||
|
655 | print( | |||
|
656 | "Warning, all predicates set to False, skip_hidden may not have any effects." | |||
|
657 | ) | |||
545 |
|
658 | |||
546 | def do_list(self, arg): |
|
659 | def do_list(self, arg): | |
547 | """Print lines of code from the current stack frame |
|
660 | """Print lines of code from the current stack frame | |
548 | """ |
|
661 | """ | |
549 | self.lastcmd = 'list' |
|
662 | self.lastcmd = 'list' | |
550 | last = None |
|
663 | last = None | |
551 | if arg: |
|
664 | if arg: | |
552 | try: |
|
665 | try: | |
553 | x = eval(arg, {}, {}) |
|
666 | x = eval(arg, {}, {}) | |
554 | if type(x) == type(()): |
|
667 | if type(x) == type(()): | |
555 | first, last = x |
|
668 | first, last = x | |
556 | first = int(first) |
|
669 | first = int(first) | |
557 | last = int(last) |
|
670 | last = int(last) | |
558 | if last < first: |
|
671 | if last < first: | |
559 | # Assume it's a count |
|
672 | # Assume it's a count | |
560 | last = first + last |
|
673 | last = first + last | |
561 | else: |
|
674 | else: | |
562 | first = max(1, int(x) - 5) |
|
675 | first = max(1, int(x) - 5) | |
563 | except: |
|
676 | except: | |
564 | print('*** Error in argument:', repr(arg), file=self.stdout) |
|
677 | print('*** Error in argument:', repr(arg), file=self.stdout) | |
565 | return |
|
678 | return | |
566 | elif self.lineno is None: |
|
679 | elif self.lineno is None: | |
567 | first = max(1, self.curframe.f_lineno - 5) |
|
680 | first = max(1, self.curframe.f_lineno - 5) | |
568 | else: |
|
681 | else: | |
569 | first = self.lineno + 1 |
|
682 | first = self.lineno + 1 | |
570 | if last is None: |
|
683 | if last is None: | |
571 | last = first + 10 |
|
684 | last = first + 10 | |
572 | self.print_list_lines(self.curframe.f_code.co_filename, first, last) |
|
685 | self.print_list_lines(self.curframe.f_code.co_filename, first, last) | |
573 |
|
686 | |||
574 | # vds: >> |
|
687 | # vds: >> | |
575 | lineno = first |
|
688 | lineno = first | |
576 | filename = self.curframe.f_code.co_filename |
|
689 | filename = self.curframe.f_code.co_filename | |
577 | self.shell.hooks.synchronize_with_editor(filename, lineno, 0) |
|
690 | self.shell.hooks.synchronize_with_editor(filename, lineno, 0) | |
578 | # vds: << |
|
691 | # vds: << | |
579 |
|
692 | |||
580 | do_l = do_list |
|
693 | do_l = do_list | |
581 |
|
694 | |||
582 | def getsourcelines(self, obj): |
|
695 | def getsourcelines(self, obj): | |
583 | lines, lineno = inspect.findsource(obj) |
|
696 | lines, lineno = inspect.findsource(obj) | |
584 |
if inspect.isframe(obj) and obj.f_globals is obj |
|
697 | if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj): | |
585 | # must be a module frame: do not try to cut a block out of it |
|
698 | # must be a module frame: do not try to cut a block out of it | |
586 | return lines, 1 |
|
699 | return lines, 1 | |
587 | elif inspect.ismodule(obj): |
|
700 | elif inspect.ismodule(obj): | |
588 | return lines, 1 |
|
701 | return lines, 1 | |
589 | return inspect.getblock(lines[lineno:]), lineno+1 |
|
702 | return inspect.getblock(lines[lineno:]), lineno+1 | |
590 |
|
703 | |||
591 | def do_longlist(self, arg): |
|
704 | def do_longlist(self, arg): | |
592 | """Print lines of code from the current stack frame. |
|
705 | """Print lines of code from the current stack frame. | |
593 |
|
706 | |||
594 | Shows more lines than 'list' does. |
|
707 | Shows more lines than 'list' does. | |
595 | """ |
|
708 | """ | |
596 | self.lastcmd = 'longlist' |
|
709 | self.lastcmd = 'longlist' | |
597 | try: |
|
710 | try: | |
598 | lines, lineno = self.getsourcelines(self.curframe) |
|
711 | lines, lineno = self.getsourcelines(self.curframe) | |
599 | except OSError as err: |
|
712 | except OSError as err: | |
600 | self.error(err) |
|
713 | self.error(err) | |
601 | return |
|
714 | return | |
602 | last = lineno + len(lines) |
|
715 | last = lineno + len(lines) | |
603 | self.print_list_lines(self.curframe.f_code.co_filename, lineno, last) |
|
716 | self.print_list_lines(self.curframe.f_code.co_filename, lineno, last) | |
604 | do_ll = do_longlist |
|
717 | do_ll = do_longlist | |
605 |
|
718 | |||
606 | def do_debug(self, arg): |
|
719 | def do_debug(self, arg): | |
607 | """debug code |
|
720 | """debug code | |
608 | Enter a recursive debugger that steps through the code |
|
721 | Enter a recursive debugger that steps through the code | |
609 | argument (which is an arbitrary expression or statement to be |
|
722 | argument (which is an arbitrary expression or statement to be | |
610 | executed in the current environment). |
|
723 | executed in the current environment). | |
611 | """ |
|
724 | """ | |
612 | trace_function = sys.gettrace() |
|
725 | trace_function = sys.gettrace() | |
613 | sys.settrace(None) |
|
726 | sys.settrace(None) | |
614 | globals = self.curframe.f_globals |
|
727 | globals = self.curframe.f_globals | |
615 | locals = self.curframe_locals |
|
728 | locals = self.curframe_locals | |
616 | p = self.__class__(completekey=self.completekey, |
|
729 | p = self.__class__(completekey=self.completekey, | |
617 | stdin=self.stdin, stdout=self.stdout) |
|
730 | stdin=self.stdin, stdout=self.stdout) | |
618 | p.use_rawinput = self.use_rawinput |
|
731 | p.use_rawinput = self.use_rawinput | |
619 | p.prompt = "(%s) " % self.prompt.strip() |
|
732 | p.prompt = "(%s) " % self.prompt.strip() | |
620 | self.message("ENTERING RECURSIVE DEBUGGER") |
|
733 | self.message("ENTERING RECURSIVE DEBUGGER") | |
621 | sys.call_tracing(p.run, (arg, globals, locals)) |
|
734 | sys.call_tracing(p.run, (arg, globals, locals)) | |
622 | self.message("LEAVING RECURSIVE DEBUGGER") |
|
735 | self.message("LEAVING RECURSIVE DEBUGGER") | |
623 | sys.settrace(trace_function) |
|
736 | sys.settrace(trace_function) | |
624 | self.lastcmd = p.lastcmd |
|
737 | self.lastcmd = p.lastcmd | |
625 |
|
738 | |||
626 | def do_pdef(self, arg): |
|
739 | def do_pdef(self, arg): | |
627 | """Print the call signature for any callable object. |
|
740 | """Print the call signature for any callable object. | |
628 |
|
741 | |||
629 | The debugger interface to %pdef""" |
|
742 | The debugger interface to %pdef""" | |
630 | namespaces = [ |
|
743 | namespaces = [ | |
631 | ("Locals", self.curframe_locals), |
|
744 | ("Locals", self.curframe_locals), | |
632 | ("Globals", self.curframe.f_globals), |
|
745 | ("Globals", self.curframe.f_globals), | |
633 | ] |
|
746 | ] | |
634 | self.shell.find_line_magic("pdef")(arg, namespaces=namespaces) |
|
747 | self.shell.find_line_magic("pdef")(arg, namespaces=namespaces) | |
635 |
|
748 | |||
636 | def do_pdoc(self, arg): |
|
749 | def do_pdoc(self, arg): | |
637 | """Print the docstring for an object. |
|
750 | """Print the docstring for an object. | |
638 |
|
751 | |||
639 | The debugger interface to %pdoc.""" |
|
752 | The debugger interface to %pdoc.""" | |
640 | namespaces = [ |
|
753 | namespaces = [ | |
641 | ("Locals", self.curframe_locals), |
|
754 | ("Locals", self.curframe_locals), | |
642 | ("Globals", self.curframe.f_globals), |
|
755 | ("Globals", self.curframe.f_globals), | |
643 | ] |
|
756 | ] | |
644 | self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces) |
|
757 | self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces) | |
645 |
|
758 | |||
646 | def do_pfile(self, arg): |
|
759 | def do_pfile(self, arg): | |
647 | """Print (or run through pager) the file where an object is defined. |
|
760 | """Print (or run through pager) the file where an object is defined. | |
648 |
|
761 | |||
649 | The debugger interface to %pfile. |
|
762 | The debugger interface to %pfile. | |
650 | """ |
|
763 | """ | |
651 | namespaces = [ |
|
764 | namespaces = [ | |
652 | ("Locals", self.curframe_locals), |
|
765 | ("Locals", self.curframe_locals), | |
653 | ("Globals", self.curframe.f_globals), |
|
766 | ("Globals", self.curframe.f_globals), | |
654 | ] |
|
767 | ] | |
655 | self.shell.find_line_magic("pfile")(arg, namespaces=namespaces) |
|
768 | self.shell.find_line_magic("pfile")(arg, namespaces=namespaces) | |
656 |
|
769 | |||
657 | def do_pinfo(self, arg): |
|
770 | def do_pinfo(self, arg): | |
658 | """Provide detailed information about an object. |
|
771 | """Provide detailed information about an object. | |
659 |
|
772 | |||
660 | The debugger interface to %pinfo, i.e., obj?.""" |
|
773 | The debugger interface to %pinfo, i.e., obj?.""" | |
661 | namespaces = [ |
|
774 | namespaces = [ | |
662 | ("Locals", self.curframe_locals), |
|
775 | ("Locals", self.curframe_locals), | |
663 | ("Globals", self.curframe.f_globals), |
|
776 | ("Globals", self.curframe.f_globals), | |
664 | ] |
|
777 | ] | |
665 | self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces) |
|
778 | self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces) | |
666 |
|
779 | |||
667 | def do_pinfo2(self, arg): |
|
780 | def do_pinfo2(self, arg): | |
668 | """Provide extra detailed information about an object. |
|
781 | """Provide extra detailed information about an object. | |
669 |
|
782 | |||
670 | The debugger interface to %pinfo2, i.e., obj??.""" |
|
783 | The debugger interface to %pinfo2, i.e., obj??.""" | |
671 | namespaces = [ |
|
784 | namespaces = [ | |
672 | ("Locals", self.curframe_locals), |
|
785 | ("Locals", self.curframe_locals), | |
673 | ("Globals", self.curframe.f_globals), |
|
786 | ("Globals", self.curframe.f_globals), | |
674 | ] |
|
787 | ] | |
675 | self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces) |
|
788 | self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces) | |
676 |
|
789 | |||
677 | def do_psource(self, arg): |
|
790 | def do_psource(self, arg): | |
678 | """Print (or run through pager) the source code for an object.""" |
|
791 | """Print (or run through pager) the source code for an object.""" | |
679 | namespaces = [ |
|
792 | namespaces = [ | |
680 | ("Locals", self.curframe_locals), |
|
793 | ("Locals", self.curframe_locals), | |
681 | ("Globals", self.curframe.f_globals), |
|
794 | ("Globals", self.curframe.f_globals), | |
682 | ] |
|
795 | ] | |
683 | self.shell.find_line_magic("psource")(arg, namespaces=namespaces) |
|
796 | self.shell.find_line_magic("psource")(arg, namespaces=namespaces) | |
684 |
|
797 | |||
685 | def do_where(self, arg): |
|
798 | def do_where(self, arg): | |
686 | """w(here) |
|
799 | """w(here) | |
687 | Print a stack trace, with the most recent frame at the bottom. |
|
800 | Print a stack trace, with the most recent frame at the bottom. | |
688 | An arrow indicates the "current frame", which determines the |
|
801 | An arrow indicates the "current frame", which determines the | |
689 | context of most commands. 'bt' is an alias for this command. |
|
802 | context of most commands. 'bt' is an alias for this command. | |
690 |
|
803 | |||
691 | Take a number as argument as an (optional) number of context line to |
|
804 | Take a number as argument as an (optional) number of context line to | |
692 | print""" |
|
805 | print""" | |
693 | if arg: |
|
806 | if arg: | |
694 | try: |
|
807 | try: | |
695 | context = int(arg) |
|
808 | context = int(arg) | |
696 | except ValueError as err: |
|
809 | except ValueError as err: | |
697 | self.error(err) |
|
810 | self.error(err) | |
698 | return |
|
811 | return | |
699 | self.print_stack_trace(context) |
|
812 | self.print_stack_trace(context) | |
700 | else: |
|
813 | else: | |
701 | self.print_stack_trace() |
|
814 | self.print_stack_trace() | |
702 |
|
815 | |||
703 | do_w = do_where |
|
816 | do_w = do_where | |
704 |
|
817 | |||
705 | def stop_here(self, frame): |
|
818 | def stop_here(self, frame): | |
706 | hidden = False |
|
819 | hidden = False | |
707 | if self.skip_hidden: |
|
820 | if self.skip_hidden: | |
708 | hidden = frame.f_locals.get("__tracebackhide__", False) |
|
821 | hidden = self._hidden_predicate(frame) | |
709 | if hidden: |
|
822 | if hidden: | |
710 | Colors = self.color_scheme_table.active_colors |
|
823 | if self.report_skipped: | |
711 | ColorsNormal = Colors.Normal |
|
824 | Colors = self.color_scheme_table.active_colors | |
712 | print(f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n") |
|
825 | ColorsNormal = Colors.Normal | |
713 |
|
826 | print( | ||
|
827 | f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n" | |||
|
828 | ) | |||
714 | return super().stop_here(frame) |
|
829 | return super().stop_here(frame) | |
715 |
|
830 | |||
716 | def do_up(self, arg): |
|
831 | def do_up(self, arg): | |
717 | """u(p) [count] |
|
832 | """u(p) [count] | |
718 | Move the current frame count (default one) levels up in the |
|
833 | Move the current frame count (default one) levels up in the | |
719 | stack trace (to an older frame). |
|
834 | stack trace (to an older frame). | |
720 |
|
835 | |||
721 | Will skip hidden frames. |
|
836 | Will skip hidden frames. | |
722 | """ |
|
837 | """ | |
723 | # modified version of upstream that skips |
|
838 | # modified version of upstream that skips | |
724 | # frames with __tracebackide__ |
|
839 | # frames with __tracebackhide__ | |
725 | if self.curindex == 0: |
|
840 | if self.curindex == 0: | |
726 | self.error("Oldest frame") |
|
841 | self.error("Oldest frame") | |
727 | return |
|
842 | return | |
728 | try: |
|
843 | try: | |
729 | count = int(arg or 1) |
|
844 | count = int(arg or 1) | |
730 | except ValueError: |
|
845 | except ValueError: | |
731 | self.error("Invalid frame count (%s)" % arg) |
|
846 | self.error("Invalid frame count (%s)" % arg) | |
732 | return |
|
847 | return | |
733 | skipped = 0 |
|
848 | skipped = 0 | |
734 | if count < 0: |
|
849 | if count < 0: | |
735 | _newframe = 0 |
|
850 | _newframe = 0 | |
736 | else: |
|
851 | else: | |
737 | counter = 0 |
|
852 | counter = 0 | |
738 | hidden_frames = self.hidden_frames(self.stack) |
|
853 | hidden_frames = self.hidden_frames(self.stack) | |
739 | for i in range(self.curindex - 1, -1, -1): |
|
854 | for i in range(self.curindex - 1, -1, -1): | |
740 | if hidden_frames[i] and self.skip_hidden: |
|
855 | if hidden_frames[i] and self.skip_hidden: | |
741 | skipped += 1 |
|
856 | skipped += 1 | |
742 | continue |
|
857 | continue | |
743 | counter += 1 |
|
858 | counter += 1 | |
744 | if counter >= count: |
|
859 | if counter >= count: | |
745 | break |
|
860 | break | |
746 | else: |
|
861 | else: | |
747 | # if no break occured. |
|
862 | # if no break occured. | |
748 | self.error( |
|
863 | self.error( | |
749 | "all frames above hidden, use `skip_hidden False` to get get into those." |
|
864 | "all frames above hidden, use `skip_hidden False` to get get into those." | |
750 | ) |
|
865 | ) | |
751 | return |
|
866 | return | |
752 |
|
867 | |||
753 | Colors = self.color_scheme_table.active_colors |
|
868 | Colors = self.color_scheme_table.active_colors | |
754 | ColorsNormal = Colors.Normal |
|
869 | ColorsNormal = Colors.Normal | |
755 | _newframe = i |
|
870 | _newframe = i | |
756 | self._select_frame(_newframe) |
|
871 | self._select_frame(_newframe) | |
757 | if skipped: |
|
872 | if skipped: | |
758 | print( |
|
873 | print( | |
759 | f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" |
|
874 | f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" | |
760 | ) |
|
875 | ) | |
761 |
|
876 | |||
762 | def do_down(self, arg): |
|
877 | def do_down(self, arg): | |
763 | """d(own) [count] |
|
878 | """d(own) [count] | |
764 | Move the current frame count (default one) levels down in the |
|
879 | Move the current frame count (default one) levels down in the | |
765 | stack trace (to a newer frame). |
|
880 | stack trace (to a newer frame). | |
766 |
|
881 | |||
767 | Will skip hidden frames. |
|
882 | Will skip hidden frames. | |
768 | """ |
|
883 | """ | |
769 | if self.curindex + 1 == len(self.stack): |
|
884 | if self.curindex + 1 == len(self.stack): | |
770 | self.error("Newest frame") |
|
885 | self.error("Newest frame") | |
771 | return |
|
886 | return | |
772 | try: |
|
887 | try: | |
773 | count = int(arg or 1) |
|
888 | count = int(arg or 1) | |
774 | except ValueError: |
|
889 | except ValueError: | |
775 | self.error("Invalid frame count (%s)" % arg) |
|
890 | self.error("Invalid frame count (%s)" % arg) | |
776 | return |
|
891 | return | |
777 | if count < 0: |
|
892 | if count < 0: | |
778 | _newframe = len(self.stack) - 1 |
|
893 | _newframe = len(self.stack) - 1 | |
779 | else: |
|
894 | else: | |
780 | counter = 0 |
|
895 | counter = 0 | |
781 | skipped = 0 |
|
896 | skipped = 0 | |
782 | hidden_frames = self.hidden_frames(self.stack) |
|
897 | hidden_frames = self.hidden_frames(self.stack) | |
783 | for i in range(self.curindex + 1, len(self.stack)): |
|
898 | for i in range(self.curindex + 1, len(self.stack)): | |
784 | if hidden_frames[i] and self.skip_hidden: |
|
899 | if hidden_frames[i] and self.skip_hidden: | |
785 | skipped += 1 |
|
900 | skipped += 1 | |
786 | continue |
|
901 | continue | |
787 | counter += 1 |
|
902 | counter += 1 | |
788 | if counter >= count: |
|
903 | if counter >= count: | |
789 | break |
|
904 | break | |
790 | else: |
|
905 | else: | |
791 | self.error( |
|
906 | self.error( | |
792 | "all frames bellow hidden, use `skip_hidden False` to get get into those." |
|
907 | "all frames bellow hidden, use `skip_hidden False` to get get into those." | |
793 | ) |
|
908 | ) | |
794 | return |
|
909 | return | |
795 |
|
910 | |||
796 | Colors = self.color_scheme_table.active_colors |
|
911 | Colors = self.color_scheme_table.active_colors | |
797 | ColorsNormal = Colors.Normal |
|
912 | ColorsNormal = Colors.Normal | |
798 | if skipped: |
|
913 | if skipped: | |
799 | print( |
|
914 | print( | |
800 | f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" |
|
915 | f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n" | |
801 | ) |
|
916 | ) | |
802 | _newframe = i |
|
917 | _newframe = i | |
803 |
|
918 | |||
804 | self._select_frame(_newframe) |
|
919 | self._select_frame(_newframe) | |
805 |
|
920 | |||
806 | do_d = do_down |
|
921 | do_d = do_down | |
807 | do_u = do_up |
|
922 | do_u = do_up | |
808 |
|
923 | |||
809 | def do_context(self, context): |
|
924 | def do_context(self, context): | |
810 | """context number_of_lines |
|
925 | """context number_of_lines | |
811 | Set the number of lines of source code to show when displaying |
|
926 | Set the number of lines of source code to show when displaying | |
812 | stacktrace information. |
|
927 | stacktrace information. | |
813 | """ |
|
928 | """ | |
814 | try: |
|
929 | try: | |
815 | new_context = int(context) |
|
930 | new_context = int(context) | |
816 | if new_context <= 0: |
|
931 | if new_context <= 0: | |
817 | raise ValueError() |
|
932 | raise ValueError() | |
818 | self.context = new_context |
|
933 | self.context = new_context | |
819 | except ValueError: |
|
934 | except ValueError: | |
820 | self.error("The 'context' command requires a positive integer argument.") |
|
935 | self.error("The 'context' command requires a positive integer argument.") | |
821 |
|
936 | |||
822 |
|
937 | |||
823 | class InterruptiblePdb(Pdb): |
|
938 | class InterruptiblePdb(Pdb): | |
824 | """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" |
|
939 | """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" | |
825 |
|
940 | |||
826 | def cmdloop(self): |
|
941 | def cmdloop(self): | |
827 | """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" |
|
942 | """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" | |
828 | try: |
|
943 | try: | |
829 | return OldPdb.cmdloop(self) |
|
944 | return OldPdb.cmdloop(self) | |
830 | except KeyboardInterrupt: |
|
945 | except KeyboardInterrupt: | |
831 | self.stop_here = lambda frame: False |
|
946 | self.stop_here = lambda frame: False | |
832 | self.do_quit("") |
|
947 | self.do_quit("") | |
833 | sys.settrace(None) |
|
948 | sys.settrace(None) | |
834 | self.quitting = False |
|
949 | self.quitting = False | |
835 | raise |
|
950 | raise | |
836 |
|
951 | |||
837 | def _cmdloop(self): |
|
952 | def _cmdloop(self): | |
838 | while True: |
|
953 | while True: | |
839 | try: |
|
954 | try: | |
840 | # keyboard interrupts allow for an easy way to cancel |
|
955 | # keyboard interrupts allow for an easy way to cancel | |
841 | # the current command, so allow them during interactive input |
|
956 | # the current command, so allow them during interactive input | |
842 | self.allow_kbdint = True |
|
957 | self.allow_kbdint = True | |
843 | self.cmdloop() |
|
958 | self.cmdloop() | |
844 | self.allow_kbdint = False |
|
959 | self.allow_kbdint = False | |
845 | break |
|
960 | break | |
846 | except KeyboardInterrupt: |
|
961 | except KeyboardInterrupt: | |
847 | self.message('--KeyboardInterrupt--') |
|
962 | self.message('--KeyboardInterrupt--') | |
848 | raise |
|
963 | raise | |
849 |
|
964 | |||
850 |
|
965 | |||
851 | def set_trace(frame=None): |
|
966 | def set_trace(frame=None): | |
852 | """ |
|
967 | """ | |
853 | Start debugging from `frame`. |
|
968 | Start debugging from `frame`. | |
854 |
|
969 | |||
855 | If frame is not specified, debugging starts from caller's frame. |
|
970 | If frame is not specified, debugging starts from caller's frame. | |
856 | """ |
|
971 | """ | |
857 | Pdb().set_trace(frame or sys._getframe().f_back) |
|
972 | Pdb().set_trace(frame or sys._getframe().f_back) |
@@ -1,1234 +1,1256 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Top-level display functions for displaying object in different formats.""" |
|
2 | """Top-level display functions for displaying object in different formats.""" | |
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 binascii import b2a_base64, hexlify |
|
8 | from binascii import b2a_base64, hexlify | |
9 | import html |
|
9 | import html | |
10 | import json |
|
10 | import json | |
11 | import mimetypes |
|
11 | import mimetypes | |
12 | import os |
|
12 | import os | |
13 | import struct |
|
13 | import struct | |
14 | import warnings |
|
14 | import warnings | |
15 | from copy import deepcopy |
|
15 | from copy import deepcopy | |
16 | from os.path import splitext |
|
16 | from os.path import splitext | |
17 | from pathlib import Path, PurePath |
|
17 | from pathlib import Path, PurePath | |
18 |
|
18 | |||
19 | from IPython.utils.py3compat import cast_unicode |
|
19 | from IPython.utils.py3compat import cast_unicode | |
20 | from IPython.testing.skipdoctest import skip_doctest |
|
20 | from IPython.testing.skipdoctest import skip_doctest | |
21 | from . import display_functions |
|
21 | from . import display_functions | |
22 |
|
22 | |||
23 |
|
23 | |||
24 | __all__ = ['display_pretty', 'display_html', 'display_markdown', |
|
24 | __all__ = ['display_pretty', 'display_html', 'display_markdown', | |
25 | 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json', |
|
25 | 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json', | |
26 | 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', |
|
26 | 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', | |
27 | 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON', |
|
27 | 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON', | |
28 | 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats', |
|
28 | 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats', | |
29 | 'set_matplotlib_close', |
|
29 | 'set_matplotlib_close', | |
30 | 'Video'] |
|
30 | 'Video'] | |
31 |
|
31 | |||
32 | _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"] |
|
32 | _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"] | |
33 |
|
33 | |||
34 | __all__ = __all__ + _deprecated_names |
|
34 | __all__ = __all__ + _deprecated_names | |
35 |
|
35 | |||
36 |
|
36 | |||
37 | # ----- warn to import from IPython.display ----- |
|
37 | # ----- warn to import from IPython.display ----- | |
38 |
|
38 | |||
39 | from warnings import warn |
|
39 | from warnings import warn | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | def __getattr__(name): |
|
42 | def __getattr__(name): | |
43 | if name in _deprecated_names: |
|
43 | if name in _deprecated_names: | |
44 | warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2) |
|
44 | warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2) | |
45 | return getattr(display_functions, name) |
|
45 | return getattr(display_functions, name) | |
46 |
|
46 | |||
47 | if name in globals().keys(): |
|
47 | if name in globals().keys(): | |
48 | return globals()[name] |
|
48 | return globals()[name] | |
49 | else: |
|
49 | else: | |
50 | raise AttributeError(f"module {__name__} has no attribute {name}") |
|
50 | raise AttributeError(f"module {__name__} has no attribute {name}") | |
51 |
|
51 | |||
52 |
|
52 | |||
53 | #----------------------------------------------------------------------------- |
|
53 | #----------------------------------------------------------------------------- | |
54 | # utility functions |
|
54 | # utility functions | |
55 | #----------------------------------------------------------------------------- |
|
55 | #----------------------------------------------------------------------------- | |
56 |
|
56 | |||
57 | def _safe_exists(path): |
|
57 | def _safe_exists(path): | |
58 | """Check path, but don't let exceptions raise""" |
|
58 | """Check path, but don't let exceptions raise""" | |
59 | try: |
|
59 | try: | |
60 | return os.path.exists(path) |
|
60 | return os.path.exists(path) | |
61 | except Exception: |
|
61 | except Exception: | |
62 | return False |
|
62 | return False | |
63 |
|
63 | |||
64 |
|
64 | |||
65 | def _display_mimetype(mimetype, objs, raw=False, metadata=None): |
|
65 | def _display_mimetype(mimetype, objs, raw=False, metadata=None): | |
66 | """internal implementation of all display_foo methods |
|
66 | """internal implementation of all display_foo methods | |
67 |
|
67 | |||
68 | Parameters |
|
68 | Parameters | |
69 | ---------- |
|
69 | ---------- | |
70 | mimetype : str |
|
70 | mimetype : str | |
71 | The mimetype to be published (e.g. 'image/png') |
|
71 | The mimetype to be published (e.g. 'image/png') | |
72 | *objs : object |
|
72 | *objs : object | |
73 | The Python objects to display, or if raw=True raw text data to |
|
73 | The Python objects to display, or if raw=True raw text data to | |
74 | display. |
|
74 | display. | |
75 | raw : bool |
|
75 | raw : bool | |
76 | Are the data objects raw data or Python objects that need to be |
|
76 | Are the data objects raw data or Python objects that need to be | |
77 | formatted before display? [default: False] |
|
77 | formatted before display? [default: False] | |
78 | metadata : dict (optional) |
|
78 | metadata : dict (optional) | |
79 | Metadata to be associated with the specific mimetype output. |
|
79 | Metadata to be associated with the specific mimetype output. | |
80 | """ |
|
80 | """ | |
81 | if metadata: |
|
81 | if metadata: | |
82 | metadata = {mimetype: metadata} |
|
82 | metadata = {mimetype: metadata} | |
83 | if raw: |
|
83 | if raw: | |
84 | # turn list of pngdata into list of { 'image/png': pngdata } |
|
84 | # turn list of pngdata into list of { 'image/png': pngdata } | |
85 | objs = [ {mimetype: obj} for obj in objs ] |
|
85 | objs = [ {mimetype: obj} for obj in objs ] | |
86 | display(*objs, raw=raw, metadata=metadata, include=[mimetype]) |
|
86 | display(*objs, raw=raw, metadata=metadata, include=[mimetype]) | |
87 |
|
87 | |||
88 | #----------------------------------------------------------------------------- |
|
88 | #----------------------------------------------------------------------------- | |
89 | # Main functions |
|
89 | # Main functions | |
90 | #----------------------------------------------------------------------------- |
|
90 | #----------------------------------------------------------------------------- | |
91 |
|
91 | |||
92 |
|
92 | |||
93 | def display_pretty(*objs, **kwargs): |
|
93 | def display_pretty(*objs, **kwargs): | |
94 | """Display the pretty (default) representation of an object. |
|
94 | """Display the pretty (default) representation of an object. | |
95 |
|
95 | |||
96 | Parameters |
|
96 | Parameters | |
97 | ---------- |
|
97 | ---------- | |
98 | *objs : object |
|
98 | *objs : object | |
99 | The Python objects to display, or if raw=True raw text data to |
|
99 | The Python objects to display, or if raw=True raw text data to | |
100 | display. |
|
100 | display. | |
101 | raw : bool |
|
101 | raw : bool | |
102 | Are the data objects raw data or Python objects that need to be |
|
102 | Are the data objects raw data or Python objects that need to be | |
103 | formatted before display? [default: False] |
|
103 | formatted before display? [default: False] | |
104 | metadata : dict (optional) |
|
104 | metadata : dict (optional) | |
105 | Metadata to be associated with the specific mimetype output. |
|
105 | Metadata to be associated with the specific mimetype output. | |
106 | """ |
|
106 | """ | |
107 | _display_mimetype('text/plain', objs, **kwargs) |
|
107 | _display_mimetype('text/plain', objs, **kwargs) | |
108 |
|
108 | |||
109 |
|
109 | |||
110 | def display_html(*objs, **kwargs): |
|
110 | def display_html(*objs, **kwargs): | |
111 | """Display the HTML representation of an object. |
|
111 | """Display the HTML representation of an object. | |
112 |
|
112 | |||
113 | Note: If raw=False and the object does not have a HTML |
|
113 | Note: If raw=False and the object does not have a HTML | |
114 | representation, no HTML will be shown. |
|
114 | representation, no HTML will be shown. | |
115 |
|
115 | |||
116 | Parameters |
|
116 | Parameters | |
117 | ---------- |
|
117 | ---------- | |
118 | *objs : object |
|
118 | *objs : object | |
119 | The Python objects to display, or if raw=True raw HTML data to |
|
119 | The Python objects to display, or if raw=True raw HTML data to | |
120 | display. |
|
120 | display. | |
121 | raw : bool |
|
121 | raw : bool | |
122 | Are the data objects raw data or Python objects that need to be |
|
122 | Are the data objects raw data or Python objects that need to be | |
123 | formatted before display? [default: False] |
|
123 | formatted before display? [default: False] | |
124 | metadata : dict (optional) |
|
124 | metadata : dict (optional) | |
125 | Metadata to be associated with the specific mimetype output. |
|
125 | Metadata to be associated with the specific mimetype output. | |
126 | """ |
|
126 | """ | |
127 | _display_mimetype('text/html', objs, **kwargs) |
|
127 | _display_mimetype('text/html', objs, **kwargs) | |
128 |
|
128 | |||
129 |
|
129 | |||
130 | def display_markdown(*objs, **kwargs): |
|
130 | def display_markdown(*objs, **kwargs): | |
131 | """Displays the Markdown representation of an object. |
|
131 | """Displays the Markdown representation of an object. | |
132 |
|
132 | |||
133 | Parameters |
|
133 | Parameters | |
134 | ---------- |
|
134 | ---------- | |
135 | *objs : object |
|
135 | *objs : object | |
136 | The Python objects to display, or if raw=True raw markdown data to |
|
136 | The Python objects to display, or if raw=True raw markdown data to | |
137 | display. |
|
137 | display. | |
138 | raw : bool |
|
138 | raw : bool | |
139 | Are the data objects raw data or Python objects that need to be |
|
139 | Are the data objects raw data or Python objects that need to be | |
140 | formatted before display? [default: False] |
|
140 | formatted before display? [default: False] | |
141 | metadata : dict (optional) |
|
141 | metadata : dict (optional) | |
142 | Metadata to be associated with the specific mimetype output. |
|
142 | Metadata to be associated with the specific mimetype output. | |
143 | """ |
|
143 | """ | |
144 |
|
144 | |||
145 | _display_mimetype('text/markdown', objs, **kwargs) |
|
145 | _display_mimetype('text/markdown', objs, **kwargs) | |
146 |
|
146 | |||
147 |
|
147 | |||
148 | def display_svg(*objs, **kwargs): |
|
148 | def display_svg(*objs, **kwargs): | |
149 | """Display the SVG representation of an object. |
|
149 | """Display the SVG representation of an object. | |
150 |
|
150 | |||
151 | Parameters |
|
151 | Parameters | |
152 | ---------- |
|
152 | ---------- | |
153 | *objs : object |
|
153 | *objs : object | |
154 | The Python objects to display, or if raw=True raw svg data to |
|
154 | The Python objects to display, or if raw=True raw svg data to | |
155 | display. |
|
155 | display. | |
156 | raw : bool |
|
156 | raw : bool | |
157 | Are the data objects raw data or Python objects that need to be |
|
157 | Are the data objects raw data or Python objects that need to be | |
158 | formatted before display? [default: False] |
|
158 | formatted before display? [default: False] | |
159 | metadata : dict (optional) |
|
159 | metadata : dict (optional) | |
160 | Metadata to be associated with the specific mimetype output. |
|
160 | Metadata to be associated with the specific mimetype output. | |
161 | """ |
|
161 | """ | |
162 | _display_mimetype('image/svg+xml', objs, **kwargs) |
|
162 | _display_mimetype('image/svg+xml', objs, **kwargs) | |
163 |
|
163 | |||
164 |
|
164 | |||
165 | def display_png(*objs, **kwargs): |
|
165 | def display_png(*objs, **kwargs): | |
166 | """Display the PNG representation of an object. |
|
166 | """Display the PNG representation of an object. | |
167 |
|
167 | |||
168 | Parameters |
|
168 | Parameters | |
169 | ---------- |
|
169 | ---------- | |
170 | *objs : object |
|
170 | *objs : object | |
171 | The Python objects to display, or if raw=True raw png data to |
|
171 | The Python objects to display, or if raw=True raw png data to | |
172 | display. |
|
172 | display. | |
173 | raw : bool |
|
173 | raw : bool | |
174 | Are the data objects raw data or Python objects that need to be |
|
174 | Are the data objects raw data or Python objects that need to be | |
175 | formatted before display? [default: False] |
|
175 | formatted before display? [default: False] | |
176 | metadata : dict (optional) |
|
176 | metadata : dict (optional) | |
177 | Metadata to be associated with the specific mimetype output. |
|
177 | Metadata to be associated with the specific mimetype output. | |
178 | """ |
|
178 | """ | |
179 | _display_mimetype('image/png', objs, **kwargs) |
|
179 | _display_mimetype('image/png', objs, **kwargs) | |
180 |
|
180 | |||
181 |
|
181 | |||
182 | def display_jpeg(*objs, **kwargs): |
|
182 | def display_jpeg(*objs, **kwargs): | |
183 | """Display the JPEG representation of an object. |
|
183 | """Display the JPEG representation of an object. | |
184 |
|
184 | |||
185 | Parameters |
|
185 | Parameters | |
186 | ---------- |
|
186 | ---------- | |
187 | *objs : object |
|
187 | *objs : object | |
188 | The Python objects to display, or if raw=True raw JPEG data to |
|
188 | The Python objects to display, or if raw=True raw JPEG data to | |
189 | display. |
|
189 | display. | |
190 | raw : bool |
|
190 | raw : bool | |
191 | Are the data objects raw data or Python objects that need to be |
|
191 | Are the data objects raw data or Python objects that need to be | |
192 | formatted before display? [default: False] |
|
192 | formatted before display? [default: False] | |
193 | metadata : dict (optional) |
|
193 | metadata : dict (optional) | |
194 | Metadata to be associated with the specific mimetype output. |
|
194 | Metadata to be associated with the specific mimetype output. | |
195 | """ |
|
195 | """ | |
196 | _display_mimetype('image/jpeg', objs, **kwargs) |
|
196 | _display_mimetype('image/jpeg', objs, **kwargs) | |
197 |
|
197 | |||
198 |
|
198 | |||
199 | def display_latex(*objs, **kwargs): |
|
199 | def display_latex(*objs, **kwargs): | |
200 | """Display the LaTeX representation of an object. |
|
200 | """Display the LaTeX representation of an object. | |
201 |
|
201 | |||
202 | Parameters |
|
202 | Parameters | |
203 | ---------- |
|
203 | ---------- | |
204 | *objs : object |
|
204 | *objs : object | |
205 | The Python objects to display, or if raw=True raw latex data to |
|
205 | The Python objects to display, or if raw=True raw latex data to | |
206 | display. |
|
206 | display. | |
207 | raw : bool |
|
207 | raw : bool | |
208 | Are the data objects raw data or Python objects that need to be |
|
208 | Are the data objects raw data or Python objects that need to be | |
209 | formatted before display? [default: False] |
|
209 | formatted before display? [default: False] | |
210 | metadata : dict (optional) |
|
210 | metadata : dict (optional) | |
211 | Metadata to be associated with the specific mimetype output. |
|
211 | Metadata to be associated with the specific mimetype output. | |
212 | """ |
|
212 | """ | |
213 | _display_mimetype('text/latex', objs, **kwargs) |
|
213 | _display_mimetype('text/latex', objs, **kwargs) | |
214 |
|
214 | |||
215 |
|
215 | |||
216 | def display_json(*objs, **kwargs): |
|
216 | def display_json(*objs, **kwargs): | |
217 | """Display the JSON representation of an object. |
|
217 | """Display the JSON representation of an object. | |
218 |
|
218 | |||
219 | Note that not many frontends support displaying JSON. |
|
219 | Note that not many frontends support displaying JSON. | |
220 |
|
220 | |||
221 | Parameters |
|
221 | Parameters | |
222 | ---------- |
|
222 | ---------- | |
223 | *objs : object |
|
223 | *objs : object | |
224 | The Python objects to display, or if raw=True raw json data to |
|
224 | The Python objects to display, or if raw=True raw json data to | |
225 | display. |
|
225 | display. | |
226 | raw : bool |
|
226 | raw : bool | |
227 | Are the data objects raw data or Python objects that need to be |
|
227 | Are the data objects raw data or Python objects that need to be | |
228 | formatted before display? [default: False] |
|
228 | formatted before display? [default: False] | |
229 | metadata : dict (optional) |
|
229 | metadata : dict (optional) | |
230 | Metadata to be associated with the specific mimetype output. |
|
230 | Metadata to be associated with the specific mimetype output. | |
231 | """ |
|
231 | """ | |
232 | _display_mimetype('application/json', objs, **kwargs) |
|
232 | _display_mimetype('application/json', objs, **kwargs) | |
233 |
|
233 | |||
234 |
|
234 | |||
235 | def display_javascript(*objs, **kwargs): |
|
235 | def display_javascript(*objs, **kwargs): | |
236 | """Display the Javascript representation of an object. |
|
236 | """Display the Javascript representation of an object. | |
237 |
|
237 | |||
238 | Parameters |
|
238 | Parameters | |
239 | ---------- |
|
239 | ---------- | |
240 | *objs : object |
|
240 | *objs : object | |
241 | The Python objects to display, or if raw=True raw javascript data to |
|
241 | The Python objects to display, or if raw=True raw javascript data to | |
242 | display. |
|
242 | display. | |
243 | raw : bool |
|
243 | raw : bool | |
244 | Are the data objects raw data or Python objects that need to be |
|
244 | Are the data objects raw data or Python objects that need to be | |
245 | formatted before display? [default: False] |
|
245 | formatted before display? [default: False] | |
246 | metadata : dict (optional) |
|
246 | metadata : dict (optional) | |
247 | Metadata to be associated with the specific mimetype output. |
|
247 | Metadata to be associated with the specific mimetype output. | |
248 | """ |
|
248 | """ | |
249 | _display_mimetype('application/javascript', objs, **kwargs) |
|
249 | _display_mimetype('application/javascript', objs, **kwargs) | |
250 |
|
250 | |||
251 |
|
251 | |||
252 | def display_pdf(*objs, **kwargs): |
|
252 | def display_pdf(*objs, **kwargs): | |
253 | """Display the PDF representation of an object. |
|
253 | """Display the PDF representation of an object. | |
254 |
|
254 | |||
255 | Parameters |
|
255 | Parameters | |
256 | ---------- |
|
256 | ---------- | |
257 | *objs : object |
|
257 | *objs : object | |
258 | The Python objects to display, or if raw=True raw javascript data to |
|
258 | The Python objects to display, or if raw=True raw javascript data to | |
259 | display. |
|
259 | display. | |
260 | raw : bool |
|
260 | raw : bool | |
261 | Are the data objects raw data or Python objects that need to be |
|
261 | Are the data objects raw data or Python objects that need to be | |
262 | formatted before display? [default: False] |
|
262 | formatted before display? [default: False] | |
263 | metadata : dict (optional) |
|
263 | metadata : dict (optional) | |
264 | Metadata to be associated with the specific mimetype output. |
|
264 | Metadata to be associated with the specific mimetype output. | |
265 | """ |
|
265 | """ | |
266 | _display_mimetype('application/pdf', objs, **kwargs) |
|
266 | _display_mimetype('application/pdf', objs, **kwargs) | |
267 |
|
267 | |||
268 |
|
268 | |||
269 | #----------------------------------------------------------------------------- |
|
269 | #----------------------------------------------------------------------------- | |
270 | # Smart classes |
|
270 | # Smart classes | |
271 | #----------------------------------------------------------------------------- |
|
271 | #----------------------------------------------------------------------------- | |
272 |
|
272 | |||
273 |
|
273 | |||
274 | class DisplayObject(object): |
|
274 | class DisplayObject(object): | |
275 | """An object that wraps data to be displayed.""" |
|
275 | """An object that wraps data to be displayed.""" | |
276 |
|
276 | |||
277 | _read_flags = 'r' |
|
277 | _read_flags = 'r' | |
278 | _show_mem_addr = False |
|
278 | _show_mem_addr = False | |
279 | metadata = None |
|
279 | metadata = None | |
280 |
|
280 | |||
281 | def __init__(self, data=None, url=None, filename=None, metadata=None): |
|
281 | def __init__(self, data=None, url=None, filename=None, metadata=None): | |
282 | """Create a display object given raw data. |
|
282 | """Create a display object given raw data. | |
283 |
|
283 | |||
284 | When this object is returned by an expression or passed to the |
|
284 | When this object is returned by an expression or passed to the | |
285 | display function, it will result in the data being displayed |
|
285 | display function, it will result in the data being displayed | |
286 | in the frontend. The MIME type of the data should match the |
|
286 | in the frontend. The MIME type of the data should match the | |
287 | subclasses used, so the Png subclass should be used for 'image/png' |
|
287 | subclasses used, so the Png subclass should be used for 'image/png' | |
288 | data. If the data is a URL, the data will first be downloaded |
|
288 | data. If the data is a URL, the data will first be downloaded | |
289 | and then displayed. If |
|
289 | and then displayed. If | |
290 |
|
290 | |||
291 | Parameters |
|
291 | Parameters | |
292 | ---------- |
|
292 | ---------- | |
293 | data : unicode, str or bytes |
|
293 | data : unicode, str or bytes | |
294 | The raw data or a URL or file to load the data from |
|
294 | The raw data or a URL or file to load the data from | |
295 | url : unicode |
|
295 | url : unicode | |
296 | A URL to download the data from. |
|
296 | A URL to download the data from. | |
297 | filename : unicode |
|
297 | filename : unicode | |
298 | Path to a local file to load the data from. |
|
298 | Path to a local file to load the data from. | |
299 | metadata : dict |
|
299 | metadata : dict | |
300 | Dict of metadata associated to be the object when displayed |
|
300 | Dict of metadata associated to be the object when displayed | |
301 | """ |
|
301 | """ | |
302 | if isinstance(data, (Path, PurePath)): |
|
302 | if isinstance(data, (Path, PurePath)): | |
303 | data = str(data) |
|
303 | data = str(data) | |
304 |
|
304 | |||
305 | if data is not None and isinstance(data, str): |
|
305 | if data is not None and isinstance(data, str): | |
306 | if data.startswith('http') and url is None: |
|
306 | if data.startswith('http') and url is None: | |
307 | url = data |
|
307 | url = data | |
308 | filename = None |
|
308 | filename = None | |
309 | data = None |
|
309 | data = None | |
310 | elif _safe_exists(data) and filename is None: |
|
310 | elif _safe_exists(data) and filename is None: | |
311 | url = None |
|
311 | url = None | |
312 | filename = data |
|
312 | filename = data | |
313 | data = None |
|
313 | data = None | |
314 |
|
314 | |||
315 | self.url = url |
|
315 | self.url = url | |
316 | self.filename = filename |
|
316 | self.filename = filename | |
317 | # because of @data.setter methods in |
|
317 | # because of @data.setter methods in | |
318 | # subclasses ensure url and filename are set |
|
318 | # subclasses ensure url and filename are set | |
319 | # before assigning to self.data |
|
319 | # before assigning to self.data | |
320 | self.data = data |
|
320 | self.data = data | |
321 |
|
321 | |||
322 | if metadata is not None: |
|
322 | if metadata is not None: | |
323 | self.metadata = metadata |
|
323 | self.metadata = metadata | |
324 | elif self.metadata is None: |
|
324 | elif self.metadata is None: | |
325 | self.metadata = {} |
|
325 | self.metadata = {} | |
326 |
|
326 | |||
327 | self.reload() |
|
327 | self.reload() | |
328 | self._check_data() |
|
328 | self._check_data() | |
329 |
|
329 | |||
330 | def __repr__(self): |
|
330 | def __repr__(self): | |
331 | if not self._show_mem_addr: |
|
331 | if not self._show_mem_addr: | |
332 | cls = self.__class__ |
|
332 | cls = self.__class__ | |
333 | r = "<%s.%s object>" % (cls.__module__, cls.__name__) |
|
333 | r = "<%s.%s object>" % (cls.__module__, cls.__name__) | |
334 | else: |
|
334 | else: | |
335 | r = super(DisplayObject, self).__repr__() |
|
335 | r = super(DisplayObject, self).__repr__() | |
336 | return r |
|
336 | return r | |
337 |
|
337 | |||
338 | def _check_data(self): |
|
338 | def _check_data(self): | |
339 | """Override in subclasses if there's something to check.""" |
|
339 | """Override in subclasses if there's something to check.""" | |
340 | pass |
|
340 | pass | |
341 |
|
341 | |||
342 | def _data_and_metadata(self): |
|
342 | def _data_and_metadata(self): | |
343 | """shortcut for returning metadata with shape information, if defined""" |
|
343 | """shortcut for returning metadata with shape information, if defined""" | |
344 | if self.metadata: |
|
344 | if self.metadata: | |
345 | return self.data, deepcopy(self.metadata) |
|
345 | return self.data, deepcopy(self.metadata) | |
346 | else: |
|
346 | else: | |
347 | return self.data |
|
347 | return self.data | |
348 |
|
348 | |||
349 | def reload(self): |
|
349 | def reload(self): | |
350 | """Reload the raw data from file or URL.""" |
|
350 | """Reload the raw data from file or URL.""" | |
351 | if self.filename is not None: |
|
351 | if self.filename is not None: | |
352 | with open(self.filename, self._read_flags) as f: |
|
352 | with open(self.filename, self._read_flags) as f: | |
353 | self.data = f.read() |
|
353 | self.data = f.read() | |
354 | elif self.url is not None: |
|
354 | elif self.url is not None: | |
355 | # Deferred import |
|
355 | # Deferred import | |
356 | from urllib.request import urlopen |
|
356 | from urllib.request import urlopen | |
357 | response = urlopen(self.url) |
|
357 | response = urlopen(self.url) | |
358 | data = response.read() |
|
358 | data = response.read() | |
359 | # extract encoding from header, if there is one: |
|
359 | # extract encoding from header, if there is one: | |
360 | encoding = None |
|
360 | encoding = None | |
361 | if 'content-type' in response.headers: |
|
361 | if 'content-type' in response.headers: | |
362 | for sub in response.headers['content-type'].split(';'): |
|
362 | for sub in response.headers['content-type'].split(';'): | |
363 | sub = sub.strip() |
|
363 | sub = sub.strip() | |
364 | if sub.startswith('charset'): |
|
364 | if sub.startswith('charset'): | |
365 | encoding = sub.split('=')[-1].strip() |
|
365 | encoding = sub.split('=')[-1].strip() | |
366 | break |
|
366 | break | |
367 | if 'content-encoding' in response.headers: |
|
367 | if 'content-encoding' in response.headers: | |
368 | # TODO: do deflate? |
|
368 | # TODO: do deflate? | |
369 | if 'gzip' in response.headers['content-encoding']: |
|
369 | if 'gzip' in response.headers['content-encoding']: | |
370 | import gzip |
|
370 | import gzip | |
371 | from io import BytesIO |
|
371 | from io import BytesIO | |
372 | with gzip.open(BytesIO(data), 'rt', encoding=encoding) as fp: |
|
372 | with gzip.open(BytesIO(data), 'rt', encoding=encoding) as fp: | |
373 | encoding = None |
|
373 | encoding = None | |
374 | data = fp.read() |
|
374 | data = fp.read() | |
375 |
|
375 | |||
376 | # decode data, if an encoding was specified |
|
376 | # decode data, if an encoding was specified | |
377 | # We only touch self.data once since |
|
377 | # We only touch self.data once since | |
378 | # subclasses such as SVG have @data.setter methods |
|
378 | # subclasses such as SVG have @data.setter methods | |
379 | # that transform self.data into ... well svg. |
|
379 | # that transform self.data into ... well svg. | |
380 | if encoding: |
|
380 | if encoding: | |
381 | self.data = data.decode(encoding, 'replace') |
|
381 | self.data = data.decode(encoding, 'replace') | |
382 | else: |
|
382 | else: | |
383 | self.data = data |
|
383 | self.data = data | |
384 |
|
384 | |||
385 |
|
385 | |||
386 | class TextDisplayObject(DisplayObject): |
|
386 | class TextDisplayObject(DisplayObject): | |
387 | """Validate that display data is text""" |
|
387 | """Validate that display data is text""" | |
388 | def _check_data(self): |
|
388 | def _check_data(self): | |
389 | if self.data is not None and not isinstance(self.data, str): |
|
389 | if self.data is not None and not isinstance(self.data, str): | |
390 | raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data)) |
|
390 | raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data)) | |
391 |
|
391 | |||
392 | class Pretty(TextDisplayObject): |
|
392 | class Pretty(TextDisplayObject): | |
393 |
|
393 | |||
394 | def _repr_pretty_(self, pp, cycle): |
|
394 | def _repr_pretty_(self, pp, cycle): | |
395 | return pp.text(self.data) |
|
395 | return pp.text(self.data) | |
396 |
|
396 | |||
397 |
|
397 | |||
398 | class HTML(TextDisplayObject): |
|
398 | class HTML(TextDisplayObject): | |
399 |
|
399 | |||
400 | def __init__(self, data=None, url=None, filename=None, metadata=None): |
|
400 | def __init__(self, data=None, url=None, filename=None, metadata=None): | |
401 | def warn(): |
|
401 | def warn(): | |
402 | if not data: |
|
402 | if not data: | |
403 | return False |
|
403 | return False | |
404 |
|
404 | |||
405 | # |
|
405 | # | |
406 | # Avoid calling lower() on the entire data, because it could be a |
|
406 | # Avoid calling lower() on the entire data, because it could be a | |
407 | # long string and we're only interested in its beginning and end. |
|
407 | # long string and we're only interested in its beginning and end. | |
408 | # |
|
408 | # | |
409 | prefix = data[:10].lower() |
|
409 | prefix = data[:10].lower() | |
410 | suffix = data[-10:].lower() |
|
410 | suffix = data[-10:].lower() | |
411 | return prefix.startswith("<iframe ") and suffix.endswith("</iframe>") |
|
411 | return prefix.startswith("<iframe ") and suffix.endswith("</iframe>") | |
412 |
|
412 | |||
413 | if warn(): |
|
413 | if warn(): | |
414 | warnings.warn("Consider using IPython.display.IFrame instead") |
|
414 | warnings.warn("Consider using IPython.display.IFrame instead") | |
415 | super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata) |
|
415 | super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata) | |
416 |
|
416 | |||
417 | def _repr_html_(self): |
|
417 | def _repr_html_(self): | |
418 | return self._data_and_metadata() |
|
418 | return self._data_and_metadata() | |
419 |
|
419 | |||
420 | def __html__(self): |
|
420 | def __html__(self): | |
421 | """ |
|
421 | """ | |
422 | This method exists to inform other HTML-using modules (e.g. Markupsafe, |
|
422 | This method exists to inform other HTML-using modules (e.g. Markupsafe, | |
423 | htmltag, etc) that this object is HTML and does not need things like |
|
423 | htmltag, etc) that this object is HTML and does not need things like | |
424 | special characters (<>&) escaped. |
|
424 | special characters (<>&) escaped. | |
425 | """ |
|
425 | """ | |
426 | return self._repr_html_() |
|
426 | return self._repr_html_() | |
427 |
|
427 | |||
428 |
|
428 | |||
429 | class Markdown(TextDisplayObject): |
|
429 | class Markdown(TextDisplayObject): | |
430 |
|
430 | |||
431 | def _repr_markdown_(self): |
|
431 | def _repr_markdown_(self): | |
432 | return self._data_and_metadata() |
|
432 | return self._data_and_metadata() | |
433 |
|
433 | |||
434 |
|
434 | |||
435 | class Math(TextDisplayObject): |
|
435 | class Math(TextDisplayObject): | |
436 |
|
436 | |||
437 | def _repr_latex_(self): |
|
437 | def _repr_latex_(self): | |
438 | s = r"$\displaystyle %s$" % self.data.strip('$') |
|
438 | s = r"$\displaystyle %s$" % self.data.strip('$') | |
439 | if self.metadata: |
|
439 | if self.metadata: | |
440 | return s, deepcopy(self.metadata) |
|
440 | return s, deepcopy(self.metadata) | |
441 | else: |
|
441 | else: | |
442 | return s |
|
442 | return s | |
443 |
|
443 | |||
444 |
|
444 | |||
445 | class Latex(TextDisplayObject): |
|
445 | class Latex(TextDisplayObject): | |
446 |
|
446 | |||
447 | def _repr_latex_(self): |
|
447 | def _repr_latex_(self): | |
448 | return self._data_and_metadata() |
|
448 | return self._data_and_metadata() | |
449 |
|
449 | |||
450 |
|
450 | |||
451 | class SVG(DisplayObject): |
|
451 | class SVG(DisplayObject): | |
452 | """Embed an SVG into the display. |
|
452 | """Embed an SVG into the display. | |
453 |
|
453 | |||
454 | Note if you just want to view a svg image via a URL use `:class:Image` with |
|
454 | Note if you just want to view a svg image via a URL use `:class:Image` with | |
455 | a url=URL keyword argument. |
|
455 | a url=URL keyword argument. | |
456 | """ |
|
456 | """ | |
457 |
|
457 | |||
458 | _read_flags = 'rb' |
|
458 | _read_flags = 'rb' | |
459 | # wrap data in a property, which extracts the <svg> tag, discarding |
|
459 | # wrap data in a property, which extracts the <svg> tag, discarding | |
460 | # document headers |
|
460 | # document headers | |
461 | _data = None |
|
461 | _data = None | |
462 |
|
462 | |||
463 | @property |
|
463 | @property | |
464 | def data(self): |
|
464 | def data(self): | |
465 | return self._data |
|
465 | return self._data | |
466 |
|
466 | |||
467 | @data.setter |
|
467 | @data.setter | |
468 | def data(self, svg): |
|
468 | def data(self, svg): | |
469 | if svg is None: |
|
469 | if svg is None: | |
470 | self._data = None |
|
470 | self._data = None | |
471 | return |
|
471 | return | |
472 | # parse into dom object |
|
472 | # parse into dom object | |
473 | from xml.dom import minidom |
|
473 | from xml.dom import minidom | |
474 | x = minidom.parseString(svg) |
|
474 | x = minidom.parseString(svg) | |
475 | # get svg tag (should be 1) |
|
475 | # get svg tag (should be 1) | |
476 | found_svg = x.getElementsByTagName('svg') |
|
476 | found_svg = x.getElementsByTagName('svg') | |
477 | if found_svg: |
|
477 | if found_svg: | |
478 | svg = found_svg[0].toxml() |
|
478 | svg = found_svg[0].toxml() | |
479 | else: |
|
479 | else: | |
480 | # fallback on the input, trust the user |
|
480 | # fallback on the input, trust the user | |
481 | # but this is probably an error. |
|
481 | # but this is probably an error. | |
482 | pass |
|
482 | pass | |
483 | svg = cast_unicode(svg) |
|
483 | svg = cast_unicode(svg) | |
484 | self._data = svg |
|
484 | self._data = svg | |
485 |
|
485 | |||
486 | def _repr_svg_(self): |
|
486 | def _repr_svg_(self): | |
487 | return self._data_and_metadata() |
|
487 | return self._data_and_metadata() | |
488 |
|
488 | |||
489 | class ProgressBar(DisplayObject): |
|
489 | class ProgressBar(DisplayObject): | |
490 | """Progressbar supports displaying a progressbar like element |
|
490 | """Progressbar supports displaying a progressbar like element | |
491 | """ |
|
491 | """ | |
492 | def __init__(self, total): |
|
492 | def __init__(self, total): | |
493 | """Creates a new progressbar |
|
493 | """Creates a new progressbar | |
494 |
|
494 | |||
495 | Parameters |
|
495 | Parameters | |
496 | ---------- |
|
496 | ---------- | |
497 | total : int |
|
497 | total : int | |
498 | maximum size of the progressbar |
|
498 | maximum size of the progressbar | |
499 | """ |
|
499 | """ | |
500 | self.total = total |
|
500 | self.total = total | |
501 | self._progress = 0 |
|
501 | self._progress = 0 | |
502 | self.html_width = '60ex' |
|
502 | self.html_width = '60ex' | |
503 | self.text_width = 60 |
|
503 | self.text_width = 60 | |
504 | self._display_id = hexlify(os.urandom(8)).decode('ascii') |
|
504 | self._display_id = hexlify(os.urandom(8)).decode('ascii') | |
505 |
|
505 | |||
506 | def __repr__(self): |
|
506 | def __repr__(self): | |
507 | fraction = self.progress / self.total |
|
507 | fraction = self.progress / self.total | |
508 | filled = '=' * int(fraction * self.text_width) |
|
508 | filled = '=' * int(fraction * self.text_width) | |
509 | rest = ' ' * (self.text_width - len(filled)) |
|
509 | rest = ' ' * (self.text_width - len(filled)) | |
510 | return '[{}{}] {}/{}'.format( |
|
510 | return '[{}{}] {}/{}'.format( | |
511 | filled, rest, |
|
511 | filled, rest, | |
512 | self.progress, self.total, |
|
512 | self.progress, self.total, | |
513 | ) |
|
513 | ) | |
514 |
|
514 | |||
515 | def _repr_html_(self): |
|
515 | def _repr_html_(self): | |
516 | return "<progress style='width:{}' max='{}' value='{}'></progress>".format( |
|
516 | return "<progress style='width:{}' max='{}' value='{}'></progress>".format( | |
517 | self.html_width, self.total, self.progress) |
|
517 | self.html_width, self.total, self.progress) | |
518 |
|
518 | |||
519 | def display(self): |
|
519 | def display(self): | |
520 | display(self, display_id=self._display_id) |
|
520 | display(self, display_id=self._display_id) | |
521 |
|
521 | |||
522 | def update(self): |
|
522 | def update(self): | |
523 | display(self, display_id=self._display_id, update=True) |
|
523 | display(self, display_id=self._display_id, update=True) | |
524 |
|
524 | |||
525 | @property |
|
525 | @property | |
526 | def progress(self): |
|
526 | def progress(self): | |
527 | return self._progress |
|
527 | return self._progress | |
528 |
|
528 | |||
529 | @progress.setter |
|
529 | @progress.setter | |
530 | def progress(self, value): |
|
530 | def progress(self, value): | |
531 | self._progress = value |
|
531 | self._progress = value | |
532 | self.update() |
|
532 | self.update() | |
533 |
|
533 | |||
534 | def __iter__(self): |
|
534 | def __iter__(self): | |
535 | self.display() |
|
535 | self.display() | |
536 | self._progress = -1 # First iteration is 0 |
|
536 | self._progress = -1 # First iteration is 0 | |
537 | return self |
|
537 | return self | |
538 |
|
538 | |||
539 | def __next__(self): |
|
539 | def __next__(self): | |
540 | """Returns current value and increments display by one.""" |
|
540 | """Returns current value and increments display by one.""" | |
541 | self.progress += 1 |
|
541 | self.progress += 1 | |
542 | if self.progress < self.total: |
|
542 | if self.progress < self.total: | |
543 | return self.progress |
|
543 | return self.progress | |
544 | else: |
|
544 | else: | |
545 | raise StopIteration() |
|
545 | raise StopIteration() | |
546 |
|
546 | |||
547 | class JSON(DisplayObject): |
|
547 | class JSON(DisplayObject): | |
548 | """JSON expects a JSON-able dict or list |
|
548 | """JSON expects a JSON-able dict or list | |
549 |
|
549 | |||
550 | not an already-serialized JSON string. |
|
550 | not an already-serialized JSON string. | |
551 |
|
551 | |||
552 | Scalar types (None, number, string) are not allowed, only dict or list containers. |
|
552 | Scalar types (None, number, string) are not allowed, only dict or list containers. | |
553 | """ |
|
553 | """ | |
554 | # wrap data in a property, which warns about passing already-serialized JSON |
|
554 | # wrap data in a property, which warns about passing already-serialized JSON | |
555 | _data = None |
|
555 | _data = None | |
556 | def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs): |
|
556 | def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs): | |
557 | """Create a JSON display object given raw data. |
|
557 | """Create a JSON display object given raw data. | |
558 |
|
558 | |||
559 | Parameters |
|
559 | Parameters | |
560 | ---------- |
|
560 | ---------- | |
561 | data : dict or list |
|
561 | data : dict or list | |
562 | JSON data to display. Not an already-serialized JSON string. |
|
562 | JSON data to display. Not an already-serialized JSON string. | |
563 | Scalar types (None, number, string) are not allowed, only dict |
|
563 | Scalar types (None, number, string) are not allowed, only dict | |
564 | or list containers. |
|
564 | or list containers. | |
565 | url : unicode |
|
565 | url : unicode | |
566 | A URL to download the data from. |
|
566 | A URL to download the data from. | |
567 | filename : unicode |
|
567 | filename : unicode | |
568 | Path to a local file to load the data from. |
|
568 | Path to a local file to load the data from. | |
569 | expanded : boolean |
|
569 | expanded : boolean | |
570 | Metadata to control whether a JSON display component is expanded. |
|
570 | Metadata to control whether a JSON display component is expanded. | |
571 | metadata : dict |
|
571 | metadata : dict | |
572 | Specify extra metadata to attach to the json display object. |
|
572 | Specify extra metadata to attach to the json display object. | |
573 | root : str |
|
573 | root : str | |
574 | The name of the root element of the JSON tree |
|
574 | The name of the root element of the JSON tree | |
575 | """ |
|
575 | """ | |
576 | self.metadata = { |
|
576 | self.metadata = { | |
577 | 'expanded': expanded, |
|
577 | 'expanded': expanded, | |
578 | 'root': root, |
|
578 | 'root': root, | |
579 | } |
|
579 | } | |
580 | if metadata: |
|
580 | if metadata: | |
581 | self.metadata.update(metadata) |
|
581 | self.metadata.update(metadata) | |
582 | if kwargs: |
|
582 | if kwargs: | |
583 | self.metadata.update(kwargs) |
|
583 | self.metadata.update(kwargs) | |
584 | super(JSON, self).__init__(data=data, url=url, filename=filename) |
|
584 | super(JSON, self).__init__(data=data, url=url, filename=filename) | |
585 |
|
585 | |||
586 | def _check_data(self): |
|
586 | def _check_data(self): | |
587 | if self.data is not None and not isinstance(self.data, (dict, list)): |
|
587 | if self.data is not None and not isinstance(self.data, (dict, list)): | |
588 | raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data)) |
|
588 | raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data)) | |
589 |
|
589 | |||
590 | @property |
|
590 | @property | |
591 | def data(self): |
|
591 | def data(self): | |
592 | return self._data |
|
592 | return self._data | |
593 |
|
593 | |||
594 | @data.setter |
|
594 | @data.setter | |
595 | def data(self, data): |
|
595 | def data(self, data): | |
596 | if isinstance(data, (Path, PurePath)): |
|
596 | if isinstance(data, (Path, PurePath)): | |
597 | data = str(data) |
|
597 | data = str(data) | |
598 |
|
598 | |||
599 | if isinstance(data, str): |
|
599 | if isinstance(data, str): | |
600 | if self.filename is None and self.url is None: |
|
600 | if self.filename is None and self.url is None: | |
601 | warnings.warn("JSON expects JSONable dict or list, not JSON strings") |
|
601 | warnings.warn("JSON expects JSONable dict or list, not JSON strings") | |
602 | data = json.loads(data) |
|
602 | data = json.loads(data) | |
603 | self._data = data |
|
603 | self._data = data | |
604 |
|
604 | |||
605 | def _data_and_metadata(self): |
|
605 | def _data_and_metadata(self): | |
606 | return self.data, self.metadata |
|
606 | return self.data, self.metadata | |
607 |
|
607 | |||
608 | def _repr_json_(self): |
|
608 | def _repr_json_(self): | |
609 | return self._data_and_metadata() |
|
609 | return self._data_and_metadata() | |
610 |
|
610 | |||
611 | _css_t = """var link = document.createElement("link"); |
|
611 | _css_t = """var link = document.createElement("link"); | |
612 | link.ref = "stylesheet"; |
|
612 | link.ref = "stylesheet"; | |
613 | link.type = "text/css"; |
|
613 | link.type = "text/css"; | |
614 | link.href = "%s"; |
|
614 | link.href = "%s"; | |
615 | document.head.appendChild(link); |
|
615 | document.head.appendChild(link); | |
616 | """ |
|
616 | """ | |
617 |
|
617 | |||
618 | _lib_t1 = """new Promise(function(resolve, reject) { |
|
618 | _lib_t1 = """new Promise(function(resolve, reject) { | |
619 | var script = document.createElement("script"); |
|
619 | var script = document.createElement("script"); | |
620 | script.onload = resolve; |
|
620 | script.onload = resolve; | |
621 | script.onerror = reject; |
|
621 | script.onerror = reject; | |
622 | script.src = "%s"; |
|
622 | script.src = "%s"; | |
623 | document.head.appendChild(script); |
|
623 | document.head.appendChild(script); | |
624 | }).then(() => { |
|
624 | }).then(() => { | |
625 | """ |
|
625 | """ | |
626 |
|
626 | |||
627 | _lib_t2 = """ |
|
627 | _lib_t2 = """ | |
628 | });""" |
|
628 | });""" | |
629 |
|
629 | |||
630 | class GeoJSON(JSON): |
|
630 | class GeoJSON(JSON): | |
631 | """GeoJSON expects JSON-able dict |
|
631 | """GeoJSON expects JSON-able dict | |
632 |
|
632 | |||
633 | not an already-serialized JSON string. |
|
633 | not an already-serialized JSON string. | |
634 |
|
634 | |||
635 | Scalar types (None, number, string) are not allowed, only dict containers. |
|
635 | Scalar types (None, number, string) are not allowed, only dict containers. | |
636 | """ |
|
636 | """ | |
637 |
|
637 | |||
638 | def __init__(self, *args, **kwargs): |
|
638 | def __init__(self, *args, **kwargs): | |
639 | """Create a GeoJSON display object given raw data. |
|
639 | """Create a GeoJSON display object given raw data. | |
640 |
|
640 | |||
641 | Parameters |
|
641 | Parameters | |
642 | ---------- |
|
642 | ---------- | |
643 | data : dict or list |
|
643 | data : dict or list | |
644 | VegaLite data. Not an already-serialized JSON string. |
|
644 | VegaLite data. Not an already-serialized JSON string. | |
645 | Scalar types (None, number, string) are not allowed, only dict |
|
645 | Scalar types (None, number, string) are not allowed, only dict | |
646 | or list containers. |
|
646 | or list containers. | |
647 | url_template : string |
|
647 | url_template : string | |
648 | Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template |
|
648 | Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template | |
649 | layer_options : dict |
|
649 | layer_options : dict | |
650 | Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options |
|
650 | Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options | |
651 | url : unicode |
|
651 | url : unicode | |
652 | A URL to download the data from. |
|
652 | A URL to download the data from. | |
653 | filename : unicode |
|
653 | filename : unicode | |
654 | Path to a local file to load the data from. |
|
654 | Path to a local file to load the data from. | |
655 | metadata : dict |
|
655 | metadata : dict | |
656 | Specify extra metadata to attach to the json display object. |
|
656 | Specify extra metadata to attach to the json display object. | |
657 |
|
657 | |||
658 | Examples |
|
658 | Examples | |
659 | -------- |
|
659 | -------- | |
660 | The following will display an interactive map of Mars with a point of |
|
660 | The following will display an interactive map of Mars with a point of | |
661 | interest on frontend that do support GeoJSON display. |
|
661 | interest on frontend that do support GeoJSON display. | |
662 |
|
662 | |||
663 | >>> from IPython.display import GeoJSON |
|
663 | >>> from IPython.display import GeoJSON | |
664 |
|
664 | |||
665 | >>> GeoJSON(data={ |
|
665 | >>> GeoJSON(data={ | |
666 | ... "type": "Feature", |
|
666 | ... "type": "Feature", | |
667 | ... "geometry": { |
|
667 | ... "geometry": { | |
668 | ... "type": "Point", |
|
668 | ... "type": "Point", | |
669 | ... "coordinates": [-81.327, 296.038] |
|
669 | ... "coordinates": [-81.327, 296.038] | |
670 | ... } |
|
670 | ... } | |
671 | ... }, |
|
671 | ... }, | |
672 | ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png", |
|
672 | ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png", | |
673 | ... layer_options={ |
|
673 | ... layer_options={ | |
674 | ... "basemap_id": "celestia_mars-shaded-16k_global", |
|
674 | ... "basemap_id": "celestia_mars-shaded-16k_global", | |
675 | ... "attribution" : "Celestia/praesepe", |
|
675 | ... "attribution" : "Celestia/praesepe", | |
676 | ... "minZoom" : 0, |
|
676 | ... "minZoom" : 0, | |
677 | ... "maxZoom" : 18, |
|
677 | ... "maxZoom" : 18, | |
678 | ... }) |
|
678 | ... }) | |
679 | <IPython.core.display.GeoJSON object> |
|
679 | <IPython.core.display.GeoJSON object> | |
680 |
|
680 | |||
681 | In the terminal IPython, you will only see the text representation of |
|
681 | In the terminal IPython, you will only see the text representation of | |
682 | the GeoJSON object. |
|
682 | the GeoJSON object. | |
683 |
|
683 | |||
684 | """ |
|
684 | """ | |
685 |
|
685 | |||
686 | super(GeoJSON, self).__init__(*args, **kwargs) |
|
686 | super(GeoJSON, self).__init__(*args, **kwargs) | |
687 |
|
687 | |||
688 |
|
688 | |||
689 | def _ipython_display_(self): |
|
689 | def _ipython_display_(self): | |
690 | bundle = { |
|
690 | bundle = { | |
691 | 'application/geo+json': self.data, |
|
691 | 'application/geo+json': self.data, | |
692 | 'text/plain': '<IPython.display.GeoJSON object>' |
|
692 | 'text/plain': '<IPython.display.GeoJSON object>' | |
693 | } |
|
693 | } | |
694 | metadata = { |
|
694 | metadata = { | |
695 | 'application/geo+json': self.metadata |
|
695 | 'application/geo+json': self.metadata | |
696 | } |
|
696 | } | |
697 | display(bundle, metadata=metadata, raw=True) |
|
697 | display(bundle, metadata=metadata, raw=True) | |
698 |
|
698 | |||
699 | class Javascript(TextDisplayObject): |
|
699 | class Javascript(TextDisplayObject): | |
700 |
|
700 | |||
701 | def __init__(self, data=None, url=None, filename=None, lib=None, css=None): |
|
701 | def __init__(self, data=None, url=None, filename=None, lib=None, css=None): | |
702 | """Create a Javascript display object given raw data. |
|
702 | """Create a Javascript display object given raw data. | |
703 |
|
703 | |||
704 | When this object is returned by an expression or passed to the |
|
704 | When this object is returned by an expression or passed to the | |
705 | display function, it will result in the data being displayed |
|
705 | display function, it will result in the data being displayed | |
706 | in the frontend. If the data is a URL, the data will first be |
|
706 | in the frontend. If the data is a URL, the data will first be | |
707 | downloaded and then displayed. |
|
707 | downloaded and then displayed. | |
708 |
|
708 | |||
709 | In the Notebook, the containing element will be available as `element`, |
|
709 | In the Notebook, the containing element will be available as `element`, | |
710 | and jQuery will be available. Content appended to `element` will be |
|
710 | and jQuery will be available. Content appended to `element` will be | |
711 | visible in the output area. |
|
711 | visible in the output area. | |
712 |
|
712 | |||
713 | Parameters |
|
713 | Parameters | |
714 | ---------- |
|
714 | ---------- | |
715 | data : unicode, str or bytes |
|
715 | data : unicode, str or bytes | |
716 | The Javascript source code or a URL to download it from. |
|
716 | The Javascript source code or a URL to download it from. | |
717 | url : unicode |
|
717 | url : unicode | |
718 | A URL to download the data from. |
|
718 | A URL to download the data from. | |
719 | filename : unicode |
|
719 | filename : unicode | |
720 | Path to a local file to load the data from. |
|
720 | Path to a local file to load the data from. | |
721 | lib : list or str |
|
721 | lib : list or str | |
722 | A sequence of Javascript library URLs to load asynchronously before |
|
722 | A sequence of Javascript library URLs to load asynchronously before | |
723 | running the source code. The full URLs of the libraries should |
|
723 | running the source code. The full URLs of the libraries should | |
724 | be given. A single Javascript library URL can also be given as a |
|
724 | be given. A single Javascript library URL can also be given as a | |
725 | string. |
|
725 | string. | |
726 | css : list or str |
|
726 | css : list or str | |
727 | A sequence of css files to load before running the source code. |
|
727 | A sequence of css files to load before running the source code. | |
728 | The full URLs of the css files should be given. A single css URL |
|
728 | The full URLs of the css files should be given. A single css URL | |
729 | can also be given as a string. |
|
729 | can also be given as a string. | |
730 | """ |
|
730 | """ | |
731 | if isinstance(lib, str): |
|
731 | if isinstance(lib, str): | |
732 | lib = [lib] |
|
732 | lib = [lib] | |
733 | elif lib is None: |
|
733 | elif lib is None: | |
734 | lib = [] |
|
734 | lib = [] | |
735 | if isinstance(css, str): |
|
735 | if isinstance(css, str): | |
736 | css = [css] |
|
736 | css = [css] | |
737 | elif css is None: |
|
737 | elif css is None: | |
738 | css = [] |
|
738 | css = [] | |
739 | if not isinstance(lib, (list,tuple)): |
|
739 | if not isinstance(lib, (list,tuple)): | |
740 | raise TypeError('expected sequence, got: %r' % lib) |
|
740 | raise TypeError('expected sequence, got: %r' % lib) | |
741 | if not isinstance(css, (list,tuple)): |
|
741 | if not isinstance(css, (list,tuple)): | |
742 | raise TypeError('expected sequence, got: %r' % css) |
|
742 | raise TypeError('expected sequence, got: %r' % css) | |
743 | self.lib = lib |
|
743 | self.lib = lib | |
744 | self.css = css |
|
744 | self.css = css | |
745 | super(Javascript, self).__init__(data=data, url=url, filename=filename) |
|
745 | super(Javascript, self).__init__(data=data, url=url, filename=filename) | |
746 |
|
746 | |||
747 | def _repr_javascript_(self): |
|
747 | def _repr_javascript_(self): | |
748 | r = '' |
|
748 | r = '' | |
749 | for c in self.css: |
|
749 | for c in self.css: | |
750 | r += _css_t % c |
|
750 | r += _css_t % c | |
751 | for l in self.lib: |
|
751 | for l in self.lib: | |
752 | r += _lib_t1 % l |
|
752 | r += _lib_t1 % l | |
753 | r += self.data |
|
753 | r += self.data | |
754 | r += _lib_t2*len(self.lib) |
|
754 | r += _lib_t2*len(self.lib) | |
755 | return r |
|
755 | return r | |
756 |
|
756 | |||
757 | # constants for identifying png/jpeg data |
|
757 | # constants for identifying png/jpeg data | |
758 | _PNG = b'\x89PNG\r\n\x1a\n' |
|
758 | _PNG = b'\x89PNG\r\n\x1a\n' | |
759 | _JPEG = b'\xff\xd8' |
|
759 | _JPEG = b'\xff\xd8' | |
760 |
|
760 | |||
761 | def _pngxy(data): |
|
761 | def _pngxy(data): | |
762 | """read the (width, height) from a PNG header""" |
|
762 | """read the (width, height) from a PNG header""" | |
763 | ihdr = data.index(b'IHDR') |
|
763 | ihdr = data.index(b'IHDR') | |
764 | # next 8 bytes are width/height |
|
764 | # next 8 bytes are width/height | |
765 | return struct.unpack('>ii', data[ihdr+4:ihdr+12]) |
|
765 | return struct.unpack('>ii', data[ihdr+4:ihdr+12]) | |
766 |
|
766 | |||
767 | def _jpegxy(data): |
|
767 | def _jpegxy(data): | |
768 | """read the (width, height) from a JPEG header""" |
|
768 | """read the (width, height) from a JPEG header""" | |
769 | # adapted from http://www.64lines.com/jpeg-width-height |
|
769 | # adapted from http://www.64lines.com/jpeg-width-height | |
770 |
|
770 | |||
771 | idx = 4 |
|
771 | idx = 4 | |
772 | while True: |
|
772 | while True: | |
773 | block_size = struct.unpack('>H', data[idx:idx+2])[0] |
|
773 | block_size = struct.unpack('>H', data[idx:idx+2])[0] | |
774 | idx = idx + block_size |
|
774 | idx = idx + block_size | |
775 | if data[idx:idx+2] == b'\xFF\xC0': |
|
775 | if data[idx:idx+2] == b'\xFF\xC0': | |
776 | # found Start of Frame |
|
776 | # found Start of Frame | |
777 | iSOF = idx |
|
777 | iSOF = idx | |
778 | break |
|
778 | break | |
779 | else: |
|
779 | else: | |
780 | # read another block |
|
780 | # read another block | |
781 | idx += 2 |
|
781 | idx += 2 | |
782 |
|
782 | |||
783 | h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9]) |
|
783 | h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9]) | |
784 | return w, h |
|
784 | return w, h | |
785 |
|
785 | |||
786 | def _gifxy(data): |
|
786 | def _gifxy(data): | |
787 | """read the (width, height) from a GIF header""" |
|
787 | """read the (width, height) from a GIF header""" | |
788 | return struct.unpack('<HH', data[6:10]) |
|
788 | return struct.unpack('<HH', data[6:10]) | |
789 |
|
789 | |||
790 |
|
790 | |||
791 | class Image(DisplayObject): |
|
791 | class Image(DisplayObject): | |
792 |
|
792 | |||
793 | _read_flags = 'rb' |
|
793 | _read_flags = 'rb' | |
794 | _FMT_JPEG = u'jpeg' |
|
794 | _FMT_JPEG = u'jpeg' | |
795 | _FMT_PNG = u'png' |
|
795 | _FMT_PNG = u'png' | |
796 | _FMT_GIF = u'gif' |
|
796 | _FMT_GIF = u'gif' | |
797 | _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF] |
|
797 | _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF] | |
798 | _MIMETYPES = { |
|
798 | _MIMETYPES = { | |
799 | _FMT_PNG: 'image/png', |
|
799 | _FMT_PNG: 'image/png', | |
800 | _FMT_JPEG: 'image/jpeg', |
|
800 | _FMT_JPEG: 'image/jpeg', | |
801 | _FMT_GIF: 'image/gif', |
|
801 | _FMT_GIF: 'image/gif', | |
802 | } |
|
802 | } | |
803 |
|
803 | |||
804 | def __init__( |
|
804 | def __init__( | |
805 | self, |
|
805 | self, | |
806 | data=None, |
|
806 | data=None, | |
807 | url=None, |
|
807 | url=None, | |
808 | filename=None, |
|
808 | filename=None, | |
809 | format=None, |
|
809 | format=None, | |
810 | embed=None, |
|
810 | embed=None, | |
811 | width=None, |
|
811 | width=None, | |
812 | height=None, |
|
812 | height=None, | |
813 | retina=False, |
|
813 | retina=False, | |
814 | unconfined=False, |
|
814 | unconfined=False, | |
815 | metadata=None, |
|
815 | metadata=None, | |
816 | alt=None, |
|
816 | alt=None, | |
817 | ): |
|
817 | ): | |
818 | """Create a PNG/JPEG/GIF image object given raw data. |
|
818 | """Create a PNG/JPEG/GIF image object given raw data. | |
819 |
|
819 | |||
820 | When this object is returned by an input cell or passed to the |
|
820 | When this object is returned by an input cell or passed to the | |
821 | display function, it will result in the image being displayed |
|
821 | display function, it will result in the image being displayed | |
822 | in the frontend. |
|
822 | in the frontend. | |
823 |
|
823 | |||
824 | Parameters |
|
824 | Parameters | |
825 | ---------- |
|
825 | ---------- | |
826 | data : unicode, str or bytes |
|
826 | data : unicode, str or bytes | |
827 | The raw image data or a URL or filename to load the data from. |
|
827 | The raw image data or a URL or filename to load the data from. | |
828 | This always results in embedded image data. |
|
828 | This always results in embedded image data. | |
829 | url : unicode |
|
829 | url : unicode | |
830 | A URL to download the data from. If you specify `url=`, |
|
830 | A URL to download the data from. If you specify `url=`, | |
831 | the image data will not be embedded unless you also specify `embed=True`. |
|
831 | the image data will not be embedded unless you also specify `embed=True`. | |
832 | filename : unicode |
|
832 | filename : unicode | |
833 | Path to a local file to load the data from. |
|
833 | Path to a local file to load the data from. | |
834 | Images from a file are always embedded. |
|
834 | Images from a file are always embedded. | |
835 | format : unicode |
|
835 | format : unicode | |
836 | The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given |
|
836 | The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given | |
837 | for format will be inferred from the filename extension. |
|
837 | for format will be inferred from the filename extension. | |
838 | embed : bool |
|
838 | embed : bool | |
839 | Should the image data be embedded using a data URI (True) or be |
|
839 | Should the image data be embedded using a data URI (True) or be | |
840 | loaded using an <img> tag. Set this to True if you want the image |
|
840 | loaded using an <img> tag. Set this to True if you want the image | |
841 | to be viewable later with no internet connection in the notebook. |
|
841 | to be viewable later with no internet connection in the notebook. | |
842 |
|
842 | |||
843 | Default is `True`, unless the keyword argument `url` is set, then |
|
843 | Default is `True`, unless the keyword argument `url` is set, then | |
844 | default value is `False`. |
|
844 | default value is `False`. | |
845 |
|
845 | |||
846 | Note that QtConsole is not able to display images if `embed` is set to `False` |
|
846 | Note that QtConsole is not able to display images if `embed` is set to `False` | |
847 | width : int |
|
847 | width : int | |
848 | Width in pixels to which to constrain the image in html |
|
848 | Width in pixels to which to constrain the image in html | |
849 | height : int |
|
849 | height : int | |
850 | Height in pixels to which to constrain the image in html |
|
850 | Height in pixels to which to constrain the image in html | |
851 | retina : bool |
|
851 | retina : bool | |
852 | Automatically set the width and height to half of the measured |
|
852 | Automatically set the width and height to half of the measured | |
853 | width and height. |
|
853 | width and height. | |
854 | This only works for embedded images because it reads the width/height |
|
854 | This only works for embedded images because it reads the width/height | |
855 | from image data. |
|
855 | from image data. | |
856 | For non-embedded images, you can just set the desired display width |
|
856 | For non-embedded images, you can just set the desired display width | |
857 | and height directly. |
|
857 | and height directly. | |
858 | unconfined : bool |
|
858 | unconfined : bool | |
859 | Set unconfined=True to disable max-width confinement of the image. |
|
859 | Set unconfined=True to disable max-width confinement of the image. | |
860 | metadata : dict |
|
860 | metadata : dict | |
861 | Specify extra metadata to attach to the image. |
|
861 | Specify extra metadata to attach to the image. | |
862 | alt : unicode |
|
862 | alt : unicode | |
863 | Alternative text for the image, for use by screen readers. |
|
863 | Alternative text for the image, for use by screen readers. | |
864 |
|
864 | |||
865 | Examples |
|
865 | Examples | |
866 | -------- |
|
866 | -------- | |
867 | embedded image data, works in qtconsole and notebook |
|
867 | embedded image data, works in qtconsole and notebook | |
868 | when passed positionally, the first arg can be any of raw image data, |
|
868 | when passed positionally, the first arg can be any of raw image data, | |
869 | a URL, or a filename from which to load image data. |
|
869 | a URL, or a filename from which to load image data. | |
870 | The result is always embedding image data for inline images. |
|
870 | The result is always embedding image data for inline images. | |
871 |
|
871 | |||
872 | >>> Image('http://www.google.fr/images/srpr/logo3w.png') |
|
872 | >>> Image('http://www.google.fr/images/srpr/logo3w.png') | |
873 | <IPython.core.display.Image object> |
|
873 | <IPython.core.display.Image object> | |
874 |
|
874 | |||
875 | >>> Image('/path/to/image.jpg') |
|
875 | >>> Image('/path/to/image.jpg') | |
876 | <IPython.core.display.Image object> |
|
876 | <IPython.core.display.Image object> | |
877 |
|
877 | |||
878 | >>> Image(b'RAW_PNG_DATA...') |
|
878 | >>> Image(b'RAW_PNG_DATA...') | |
879 | <IPython.core.display.Image object> |
|
879 | <IPython.core.display.Image object> | |
880 |
|
880 | |||
881 | Specifying Image(url=...) does not embed the image data, |
|
881 | Specifying Image(url=...) does not embed the image data, | |
882 | it only generates ``<img>`` tag with a link to the source. |
|
882 | it only generates ``<img>`` tag with a link to the source. | |
883 | This will not work in the qtconsole or offline. |
|
883 | This will not work in the qtconsole or offline. | |
884 |
|
884 | |||
885 | >>> Image(url='http://www.google.fr/images/srpr/logo3w.png') |
|
885 | >>> Image(url='http://www.google.fr/images/srpr/logo3w.png') | |
886 | <IPython.core.display.Image object> |
|
886 | <IPython.core.display.Image object> | |
887 |
|
887 | |||
888 | """ |
|
888 | """ | |
889 | if isinstance(data, (Path, PurePath)): |
|
889 | if isinstance(data, (Path, PurePath)): | |
890 | data = str(data) |
|
890 | data = str(data) | |
891 |
|
891 | |||
892 | if filename is not None: |
|
892 | if filename is not None: | |
893 | ext = self._find_ext(filename) |
|
893 | ext = self._find_ext(filename) | |
894 | elif url is not None: |
|
894 | elif url is not None: | |
895 | ext = self._find_ext(url) |
|
895 | ext = self._find_ext(url) | |
896 | elif data is None: |
|
896 | elif data is None: | |
897 | raise ValueError("No image data found. Expecting filename, url, or data.") |
|
897 | raise ValueError("No image data found. Expecting filename, url, or data.") | |
898 | elif isinstance(data, str) and ( |
|
898 | elif isinstance(data, str) and ( | |
899 | data.startswith('http') or _safe_exists(data) |
|
899 | data.startswith('http') or _safe_exists(data) | |
900 | ): |
|
900 | ): | |
901 | ext = self._find_ext(data) |
|
901 | ext = self._find_ext(data) | |
902 | else: |
|
902 | else: | |
903 | ext = None |
|
903 | ext = None | |
904 |
|
904 | |||
905 | if format is None: |
|
905 | if format is None: | |
906 | if ext is not None: |
|
906 | if ext is not None: | |
907 | if ext == u'jpg' or ext == u'jpeg': |
|
907 | if ext == u'jpg' or ext == u'jpeg': | |
908 | format = self._FMT_JPEG |
|
908 | format = self._FMT_JPEG | |
909 | elif ext == u'png': |
|
909 | elif ext == u'png': | |
910 | format = self._FMT_PNG |
|
910 | format = self._FMT_PNG | |
911 | elif ext == u'gif': |
|
911 | elif ext == u'gif': | |
912 | format = self._FMT_GIF |
|
912 | format = self._FMT_GIF | |
913 | else: |
|
913 | else: | |
914 | format = ext.lower() |
|
914 | format = ext.lower() | |
915 | elif isinstance(data, bytes): |
|
915 | elif isinstance(data, bytes): | |
916 | # infer image type from image data header, |
|
916 | # infer image type from image data header, | |
917 | # only if format has not been specified. |
|
917 | # only if format has not been specified. | |
918 | if data[:2] == _JPEG: |
|
918 | if data[:2] == _JPEG: | |
919 | format = self._FMT_JPEG |
|
919 | format = self._FMT_JPEG | |
920 |
|
920 | |||
921 | # failed to detect format, default png |
|
921 | # failed to detect format, default png | |
922 | if format is None: |
|
922 | if format is None: | |
923 | format = self._FMT_PNG |
|
923 | format = self._FMT_PNG | |
924 |
|
924 | |||
925 | if format.lower() == 'jpg': |
|
925 | if format.lower() == 'jpg': | |
926 | # jpg->jpeg |
|
926 | # jpg->jpeg | |
927 | format = self._FMT_JPEG |
|
927 | format = self._FMT_JPEG | |
928 |
|
928 | |||
929 | self.format = format.lower() |
|
929 | self.format = format.lower() | |
930 | self.embed = embed if embed is not None else (url is None) |
|
930 | self.embed = embed if embed is not None else (url is None) | |
931 |
|
931 | |||
932 | if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS: |
|
932 | if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS: | |
933 | raise ValueError("Cannot embed the '%s' image format" % (self.format)) |
|
933 | raise ValueError("Cannot embed the '%s' image format" % (self.format)) | |
934 | if self.embed: |
|
934 | if self.embed: | |
935 | self._mimetype = self._MIMETYPES.get(self.format) |
|
935 | self._mimetype = self._MIMETYPES.get(self.format) | |
936 |
|
936 | |||
937 | self.width = width |
|
937 | self.width = width | |
938 | self.height = height |
|
938 | self.height = height | |
939 | self.retina = retina |
|
939 | self.retina = retina | |
940 | self.unconfined = unconfined |
|
940 | self.unconfined = unconfined | |
941 | self.alt = alt |
|
941 | self.alt = alt | |
942 | super(Image, self).__init__(data=data, url=url, filename=filename, |
|
942 | super(Image, self).__init__(data=data, url=url, filename=filename, | |
943 | metadata=metadata) |
|
943 | metadata=metadata) | |
944 |
|
944 | |||
945 | if self.width is None and self.metadata.get('width', {}): |
|
945 | if self.width is None and self.metadata.get('width', {}): | |
946 | self.width = metadata['width'] |
|
946 | self.width = metadata['width'] | |
947 |
|
947 | |||
948 | if self.height is None and self.metadata.get('height', {}): |
|
948 | if self.height is None and self.metadata.get('height', {}): | |
949 | self.height = metadata['height'] |
|
949 | self.height = metadata['height'] | |
950 |
|
950 | |||
951 | if self.alt is None and self.metadata.get("alt", {}): |
|
951 | if self.alt is None and self.metadata.get("alt", {}): | |
952 | self.alt = metadata["alt"] |
|
952 | self.alt = metadata["alt"] | |
953 |
|
953 | |||
954 | if retina: |
|
954 | if retina: | |
955 | self._retina_shape() |
|
955 | self._retina_shape() | |
956 |
|
956 | |||
957 |
|
957 | |||
958 | def _retina_shape(self): |
|
958 | def _retina_shape(self): | |
959 | """load pixel-doubled width and height from image data""" |
|
959 | """load pixel-doubled width and height from image data""" | |
960 | if not self.embed: |
|
960 | if not self.embed: | |
961 | return |
|
961 | return | |
962 | if self.format == self._FMT_PNG: |
|
962 | if self.format == self._FMT_PNG: | |
963 | w, h = _pngxy(self.data) |
|
963 | w, h = _pngxy(self.data) | |
964 | elif self.format == self._FMT_JPEG: |
|
964 | elif self.format == self._FMT_JPEG: | |
965 | w, h = _jpegxy(self.data) |
|
965 | w, h = _jpegxy(self.data) | |
966 | elif self.format == self._FMT_GIF: |
|
966 | elif self.format == self._FMT_GIF: | |
967 | w, h = _gifxy(self.data) |
|
967 | w, h = _gifxy(self.data) | |
968 | else: |
|
968 | else: | |
969 | # retina only supports png |
|
969 | # retina only supports png | |
970 | return |
|
970 | return | |
971 | self.width = w // 2 |
|
971 | self.width = w // 2 | |
972 | self.height = h // 2 |
|
972 | self.height = h // 2 | |
973 |
|
973 | |||
974 | def reload(self): |
|
974 | def reload(self): | |
975 | """Reload the raw data from file or URL.""" |
|
975 | """Reload the raw data from file or URL.""" | |
976 | if self.embed: |
|
976 | if self.embed: | |
977 | super(Image,self).reload() |
|
977 | super(Image,self).reload() | |
978 | if self.retina: |
|
978 | if self.retina: | |
979 | self._retina_shape() |
|
979 | self._retina_shape() | |
980 |
|
980 | |||
981 | def _repr_html_(self): |
|
981 | def _repr_html_(self): | |
982 | if not self.embed: |
|
982 | if not self.embed: | |
983 | width = height = klass = alt = "" |
|
983 | width = height = klass = alt = "" | |
984 | if self.width: |
|
984 | if self.width: | |
985 | width = ' width="%d"' % self.width |
|
985 | width = ' width="%d"' % self.width | |
986 | if self.height: |
|
986 | if self.height: | |
987 | height = ' height="%d"' % self.height |
|
987 | height = ' height="%d"' % self.height | |
988 | if self.unconfined: |
|
988 | if self.unconfined: | |
989 | klass = ' class="unconfined"' |
|
989 | klass = ' class="unconfined"' | |
990 | if self.alt: |
|
990 | if self.alt: | |
991 | alt = ' alt="%s"' % html.escape(self.alt) |
|
991 | alt = ' alt="%s"' % html.escape(self.alt) | |
992 | return '<img src="{url}"{width}{height}{klass}{alt}/>'.format( |
|
992 | return '<img src="{url}"{width}{height}{klass}{alt}/>'.format( | |
993 | url=self.url, |
|
993 | url=self.url, | |
994 | width=width, |
|
994 | width=width, | |
995 | height=height, |
|
995 | height=height, | |
996 | klass=klass, |
|
996 | klass=klass, | |
997 | alt=alt, |
|
997 | alt=alt, | |
998 | ) |
|
998 | ) | |
999 |
|
999 | |||
1000 | def _repr_mimebundle_(self, include=None, exclude=None): |
|
1000 | def _repr_mimebundle_(self, include=None, exclude=None): | |
1001 | """Return the image as a mimebundle |
|
1001 | """Return the image as a mimebundle | |
1002 |
|
1002 | |||
1003 | Any new mimetype support should be implemented here. |
|
1003 | Any new mimetype support should be implemented here. | |
1004 | """ |
|
1004 | """ | |
1005 | if self.embed: |
|
1005 | if self.embed: | |
1006 | mimetype = self._mimetype |
|
1006 | mimetype = self._mimetype | |
1007 | data, metadata = self._data_and_metadata(always_both=True) |
|
1007 | data, metadata = self._data_and_metadata(always_both=True) | |
1008 | if metadata: |
|
1008 | if metadata: | |
1009 | metadata = {mimetype: metadata} |
|
1009 | metadata = {mimetype: metadata} | |
1010 | return {mimetype: data}, metadata |
|
1010 | return {mimetype: data}, metadata | |
1011 | else: |
|
1011 | else: | |
1012 | return {'text/html': self._repr_html_()} |
|
1012 | return {'text/html': self._repr_html_()} | |
1013 |
|
1013 | |||
1014 | def _data_and_metadata(self, always_both=False): |
|
1014 | def _data_and_metadata(self, always_both=False): | |
1015 | """shortcut for returning metadata with shape information, if defined""" |
|
1015 | """shortcut for returning metadata with shape information, if defined""" | |
1016 | try: |
|
1016 | try: | |
1017 | b64_data = b2a_base64(self.data).decode('ascii') |
|
1017 | b64_data = b2a_base64(self.data).decode('ascii') | |
1018 | except TypeError as e: |
|
1018 | except TypeError as e: | |
1019 | raise FileNotFoundError( |
|
1019 | raise FileNotFoundError( | |
1020 | "No such file or directory: '%s'" % (self.data)) from e |
|
1020 | "No such file or directory: '%s'" % (self.data)) from e | |
1021 | md = {} |
|
1021 | md = {} | |
1022 | if self.metadata: |
|
1022 | if self.metadata: | |
1023 | md.update(self.metadata) |
|
1023 | md.update(self.metadata) | |
1024 | if self.width: |
|
1024 | if self.width: | |
1025 | md['width'] = self.width |
|
1025 | md['width'] = self.width | |
1026 | if self.height: |
|
1026 | if self.height: | |
1027 | md['height'] = self.height |
|
1027 | md['height'] = self.height | |
1028 | if self.unconfined: |
|
1028 | if self.unconfined: | |
1029 | md['unconfined'] = self.unconfined |
|
1029 | md['unconfined'] = self.unconfined | |
1030 | if self.alt: |
|
1030 | if self.alt: | |
1031 | md["alt"] = self.alt |
|
1031 | md["alt"] = self.alt | |
1032 | if md or always_both: |
|
1032 | if md or always_both: | |
1033 | return b64_data, md |
|
1033 | return b64_data, md | |
1034 | else: |
|
1034 | else: | |
1035 | return b64_data |
|
1035 | return b64_data | |
1036 |
|
1036 | |||
1037 | def _repr_png_(self): |
|
1037 | def _repr_png_(self): | |
1038 | if self.embed and self.format == self._FMT_PNG: |
|
1038 | if self.embed and self.format == self._FMT_PNG: | |
1039 | return self._data_and_metadata() |
|
1039 | return self._data_and_metadata() | |
1040 |
|
1040 | |||
1041 | def _repr_jpeg_(self): |
|
1041 | def _repr_jpeg_(self): | |
1042 | if self.embed and self.format == self._FMT_JPEG: |
|
1042 | if self.embed and self.format == self._FMT_JPEG: | |
1043 | return self._data_and_metadata() |
|
1043 | return self._data_and_metadata() | |
1044 |
|
1044 | |||
1045 | def _find_ext(self, s): |
|
1045 | def _find_ext(self, s): | |
1046 | base, ext = splitext(s) |
|
1046 | base, ext = splitext(s) | |
1047 |
|
1047 | |||
1048 | if not ext: |
|
1048 | if not ext: | |
1049 | return base |
|
1049 | return base | |
1050 |
|
1050 | |||
1051 | # `splitext` includes leading period, so we skip it |
|
1051 | # `splitext` includes leading period, so we skip it | |
1052 | return ext[1:].lower() |
|
1052 | return ext[1:].lower() | |
1053 |
|
1053 | |||
1054 |
|
1054 | |||
1055 | class Video(DisplayObject): |
|
1055 | class Video(DisplayObject): | |
1056 |
|
1056 | |||
1057 | def __init__(self, data=None, url=None, filename=None, embed=False, |
|
1057 | def __init__(self, data=None, url=None, filename=None, embed=False, | |
1058 | mimetype=None, width=None, height=None, html_attributes="controls"): |
|
1058 | mimetype=None, width=None, height=None, html_attributes="controls"): | |
1059 | """Create a video object given raw data or an URL. |
|
1059 | """Create a video object given raw data or an URL. | |
1060 |
|
1060 | |||
1061 | When this object is returned by an input cell or passed to the |
|
1061 | When this object is returned by an input cell or passed to the | |
1062 | display function, it will result in the video being displayed |
|
1062 | display function, it will result in the video being displayed | |
1063 | in the frontend. |
|
1063 | in the frontend. | |
1064 |
|
1064 | |||
1065 | Parameters |
|
1065 | Parameters | |
1066 | ---------- |
|
1066 | ---------- | |
1067 | data : unicode, str or bytes |
|
1067 | data : unicode, str or bytes | |
1068 | The raw video data or a URL or filename to load the data from. |
|
1068 | The raw video data or a URL or filename to load the data from. | |
1069 | Raw data will require passing ``embed=True``. |
|
1069 | Raw data will require passing ``embed=True``. | |
1070 | url : unicode |
|
1070 | url : unicode | |
1071 | A URL for the video. If you specify ``url=``, |
|
1071 | A URL for the video. If you specify ``url=``, | |
1072 | the image data will not be embedded. |
|
1072 | the image data will not be embedded. | |
1073 | filename : unicode |
|
1073 | filename : unicode | |
1074 | Path to a local file containing the video. |
|
1074 | Path to a local file containing the video. | |
1075 | Will be interpreted as a local URL unless ``embed=True``. |
|
1075 | Will be interpreted as a local URL unless ``embed=True``. | |
1076 | embed : bool |
|
1076 | embed : bool | |
1077 | Should the video be embedded using a data URI (True) or be |
|
1077 | Should the video be embedded using a data URI (True) or be | |
1078 | loaded using a <video> tag (False). |
|
1078 | loaded using a <video> tag (False). | |
1079 |
|
1079 | |||
1080 | Since videos are large, embedding them should be avoided, if possible. |
|
1080 | Since videos are large, embedding them should be avoided, if possible. | |
1081 | You must confirm embedding as your intention by passing ``embed=True``. |
|
1081 | You must confirm embedding as your intention by passing ``embed=True``. | |
1082 |
|
1082 | |||
1083 | Local files can be displayed with URLs without embedding the content, via:: |
|
1083 | Local files can be displayed with URLs without embedding the content, via:: | |
1084 |
|
1084 | |||
1085 | Video('./video.mp4') |
|
1085 | Video('./video.mp4') | |
1086 | mimetype : unicode |
|
1086 | mimetype : unicode | |
1087 | Specify the mimetype for embedded videos. |
|
1087 | Specify the mimetype for embedded videos. | |
1088 | Default will be guessed from file extension, if available. |
|
1088 | Default will be guessed from file extension, if available. | |
1089 | width : int |
|
1089 | width : int | |
1090 | Width in pixels to which to constrain the video in HTML. |
|
1090 | Width in pixels to which to constrain the video in HTML. | |
1091 | If not supplied, defaults to the width of the video. |
|
1091 | If not supplied, defaults to the width of the video. | |
1092 | height : int |
|
1092 | height : int | |
1093 | Height in pixels to which to constrain the video in html. |
|
1093 | Height in pixels to which to constrain the video in html. | |
1094 | If not supplied, defaults to the height of the video. |
|
1094 | If not supplied, defaults to the height of the video. | |
1095 | html_attributes : str |
|
1095 | html_attributes : str | |
1096 | Attributes for the HTML ``<video>`` block. |
|
1096 | Attributes for the HTML ``<video>`` block. | |
1097 | Default: ``"controls"`` to get video controls. |
|
1097 | Default: ``"controls"`` to get video controls. | |
1098 | Other examples: ``"controls muted"`` for muted video with controls, |
|
1098 | Other examples: ``"controls muted"`` for muted video with controls, | |
1099 | ``"loop autoplay"`` for looping autoplaying video without controls. |
|
1099 | ``"loop autoplay"`` for looping autoplaying video without controls. | |
1100 |
|
1100 | |||
1101 | Examples |
|
1101 | Examples | |
1102 | -------- |
|
1102 | -------- | |
1103 | :: |
|
1103 | :: | |
1104 |
|
1104 | |||
1105 | Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4') |
|
1105 | Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4') | |
1106 | Video('path/to/video.mp4') |
|
1106 | Video('path/to/video.mp4') | |
1107 | Video('path/to/video.mp4', embed=True) |
|
1107 | Video('path/to/video.mp4', embed=True) | |
1108 | Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay") |
|
1108 | Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay") | |
1109 | Video(b'raw-videodata', embed=True) |
|
1109 | Video(b'raw-videodata', embed=True) | |
1110 | """ |
|
1110 | """ | |
1111 | if isinstance(data, (Path, PurePath)): |
|
1111 | if isinstance(data, (Path, PurePath)): | |
1112 | data = str(data) |
|
1112 | data = str(data) | |
1113 |
|
1113 | |||
1114 | if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')): |
|
1114 | if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')): | |
1115 | url = data |
|
1115 | url = data | |
1116 | data = None |
|
1116 | data = None | |
1117 | elif data is not None and os.path.exists(data): |
|
1117 | elif data is not None and os.path.exists(data): | |
1118 | filename = data |
|
1118 | filename = data | |
1119 | data = None |
|
1119 | data = None | |
1120 |
|
1120 | |||
1121 | if data and not embed: |
|
1121 | if data and not embed: | |
1122 | msg = ''.join([ |
|
1122 | msg = ''.join([ | |
1123 | "To embed videos, you must pass embed=True ", |
|
1123 | "To embed videos, you must pass embed=True ", | |
1124 | "(this may make your notebook files huge)\n", |
|
1124 | "(this may make your notebook files huge)\n", | |
1125 | "Consider passing Video(url='...')", |
|
1125 | "Consider passing Video(url='...')", | |
1126 | ]) |
|
1126 | ]) | |
1127 | raise ValueError(msg) |
|
1127 | raise ValueError(msg) | |
1128 |
|
1128 | |||
1129 | self.mimetype = mimetype |
|
1129 | self.mimetype = mimetype | |
1130 | self.embed = embed |
|
1130 | self.embed = embed | |
1131 | self.width = width |
|
1131 | self.width = width | |
1132 | self.height = height |
|
1132 | self.height = height | |
1133 | self.html_attributes = html_attributes |
|
1133 | self.html_attributes = html_attributes | |
1134 | super(Video, self).__init__(data=data, url=url, filename=filename) |
|
1134 | super(Video, self).__init__(data=data, url=url, filename=filename) | |
1135 |
|
1135 | |||
1136 | def _repr_html_(self): |
|
1136 | def _repr_html_(self): | |
1137 | width = height = '' |
|
1137 | width = height = '' | |
1138 | if self.width: |
|
1138 | if self.width: | |
1139 | width = ' width="%d"' % self.width |
|
1139 | width = ' width="%d"' % self.width | |
1140 | if self.height: |
|
1140 | if self.height: | |
1141 | height = ' height="%d"' % self.height |
|
1141 | height = ' height="%d"' % self.height | |
1142 |
|
1142 | |||
1143 | # External URLs and potentially local files are not embedded into the |
|
1143 | # External URLs and potentially local files are not embedded into the | |
1144 | # notebook output. |
|
1144 | # notebook output. | |
1145 | if not self.embed: |
|
1145 | if not self.embed: | |
1146 | url = self.url if self.url is not None else self.filename |
|
1146 | url = self.url if self.url is not None else self.filename | |
1147 | output = """<video src="{0}" {1} {2} {3}> |
|
1147 | output = """<video src="{0}" {1} {2} {3}> | |
1148 | Your browser does not support the <code>video</code> element. |
|
1148 | Your browser does not support the <code>video</code> element. | |
1149 | </video>""".format(url, self.html_attributes, width, height) |
|
1149 | </video>""".format(url, self.html_attributes, width, height) | |
1150 | return output |
|
1150 | return output | |
1151 |
|
1151 | |||
1152 | # Embedded videos are base64-encoded. |
|
1152 | # Embedded videos are base64-encoded. | |
1153 | mimetype = self.mimetype |
|
1153 | mimetype = self.mimetype | |
1154 | if self.filename is not None: |
|
1154 | if self.filename is not None: | |
1155 | if not mimetype: |
|
1155 | if not mimetype: | |
1156 | mimetype, _ = mimetypes.guess_type(self.filename) |
|
1156 | mimetype, _ = mimetypes.guess_type(self.filename) | |
1157 |
|
1157 | |||
1158 | with open(self.filename, 'rb') as f: |
|
1158 | with open(self.filename, 'rb') as f: | |
1159 | video = f.read() |
|
1159 | video = f.read() | |
1160 | else: |
|
1160 | else: | |
1161 | video = self.data |
|
1161 | video = self.data | |
1162 | if isinstance(video, str): |
|
1162 | if isinstance(video, str): | |
1163 | # unicode input is already b64-encoded |
|
1163 | # unicode input is already b64-encoded | |
1164 | b64_video = video |
|
1164 | b64_video = video | |
1165 | else: |
|
1165 | else: | |
1166 | b64_video = b2a_base64(video).decode('ascii').rstrip() |
|
1166 | b64_video = b2a_base64(video).decode('ascii').rstrip() | |
1167 |
|
1167 | |||
1168 | output = """<video {0} {1} {2}> |
|
1168 | output = """<video {0} {1} {2}> | |
1169 | <source src="data:{3};base64,{4}" type="{3}"> |
|
1169 | <source src="data:{3};base64,{4}" type="{3}"> | |
1170 | Your browser does not support the video tag. |
|
1170 | Your browser does not support the video tag. | |
1171 | </video>""".format(self.html_attributes, width, height, mimetype, b64_video) |
|
1171 | </video>""".format(self.html_attributes, width, height, mimetype, b64_video) | |
1172 | return output |
|
1172 | return output | |
1173 |
|
1173 | |||
1174 | def reload(self): |
|
1174 | def reload(self): | |
1175 | # TODO |
|
1175 | # TODO | |
1176 | pass |
|
1176 | pass | |
1177 |
|
1177 | |||
1178 |
|
1178 | |||
1179 | @skip_doctest |
|
1179 | @skip_doctest | |
1180 | def set_matplotlib_formats(*formats, **kwargs): |
|
1180 | def set_matplotlib_formats(*formats, **kwargs): | |
1181 | """Select figure formats for the inline backend. Optionally pass quality for JPEG. |
|
1181 | """ | |
|
1182 | .. deprecated:: 7.23 | |||
|
1183 | ||||
|
1184 | use `matplotlib_inline.backend_inline.set_matplotlib_formats()` | |||
|
1185 | ||||
|
1186 | Select figure formats for the inline backend. Optionally pass quality for JPEG. | |||
1182 |
|
1187 | |||
1183 | For example, this enables PNG and JPEG output with a JPEG quality of 90%:: |
|
1188 | For example, this enables PNG and JPEG output with a JPEG quality of 90%:: | |
1184 |
|
1189 | |||
1185 | In [1]: set_matplotlib_formats('png', 'jpeg', quality=90) |
|
1190 | In [1]: set_matplotlib_formats('png', 'jpeg', quality=90) | |
1186 |
|
1191 | |||
1187 | To set this in your config files use the following:: |
|
1192 | To set this in your config files use the following:: | |
1188 |
|
1193 | |||
1189 | c.InlineBackend.figure_formats = {'png', 'jpeg'} |
|
1194 | c.InlineBackend.figure_formats = {'png', 'jpeg'} | |
1190 | c.InlineBackend.print_figure_kwargs.update({'quality' : 90}) |
|
1195 | c.InlineBackend.print_figure_kwargs.update({'quality' : 90}) | |
1191 |
|
1196 | |||
1192 | Parameters |
|
1197 | Parameters | |
1193 | ---------- |
|
1198 | ---------- | |
1194 | *formats : strs |
|
1199 | *formats : strs | |
1195 | One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'. |
|
1200 | One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'. | |
1196 | **kwargs |
|
1201 | **kwargs | |
1197 | Keyword args will be relayed to ``figure.canvas.print_figure``. |
|
1202 | Keyword args will be relayed to ``figure.canvas.print_figure``. | |
1198 | """ |
|
1203 | """ | |
1199 | from IPython.core.interactiveshell import InteractiveShell |
|
1204 | warnings.warn( | |
1200 | from IPython.core.pylabtools import select_figure_formats |
|
1205 | "`set_matplotlib_formats` is deprecated since IPython 7.23, directly " | |
1201 | # build kwargs, starting with InlineBackend config |
|
1206 | "use `matplotlib_inline.backend_inline.set_matplotlib_formats()`", | |
1202 | kw = {} |
|
1207 | DeprecationWarning, | |
1203 | from ipykernel.pylab.config import InlineBackend |
|
1208 | stacklevel=2, | |
1204 | cfg = InlineBackend.instance() |
|
1209 | ) | |
1205 | kw.update(cfg.print_figure_kwargs) |
|
1210 | ||
1206 | kw.update(**kwargs) |
|
1211 | from matplotlib_inline.backend_inline import ( | |
1207 | shell = InteractiveShell.instance() |
|
1212 | set_matplotlib_formats as set_matplotlib_formats_orig, | |
1208 | select_figure_formats(shell, formats, **kw) |
|
1213 | ) | |
|
1214 | ||||
|
1215 | set_matplotlib_formats_orig(*formats, **kwargs) | |||
1209 |
|
1216 | |||
1210 | @skip_doctest |
|
1217 | @skip_doctest | |
1211 | def set_matplotlib_close(close=True): |
|
1218 | def set_matplotlib_close(close=True): | |
1212 | """Set whether the inline backend closes all figures automatically or not. |
|
1219 | """ | |
|
1220 | .. deprecated:: 7.23 | |||
|
1221 | ||||
|
1222 | use `matplotlib_inline.backend_inline.set_matplotlib_close()` | |||
|
1223 | ||||
|
1224 | ||||
|
1225 | Set whether the inline backend closes all figures automatically or not. | |||
1213 |
|
1226 | |||
1214 | By default, the inline backend used in the IPython Notebook will close all |
|
1227 | By default, the inline backend used in the IPython Notebook will close all | |
1215 | matplotlib figures automatically after each cell is run. This means that |
|
1228 | matplotlib figures automatically after each cell is run. This means that | |
1216 | plots in different cells won't interfere. Sometimes, you may want to make |
|
1229 | plots in different cells won't interfere. Sometimes, you may want to make | |
1217 | a plot in one cell and then refine it in later cells. This can be accomplished |
|
1230 | a plot in one cell and then refine it in later cells. This can be accomplished | |
1218 | by:: |
|
1231 | by:: | |
1219 |
|
1232 | |||
1220 | In [1]: set_matplotlib_close(False) |
|
1233 | In [1]: set_matplotlib_close(False) | |
1221 |
|
1234 | |||
1222 | To set this in your config files use the following:: |
|
1235 | To set this in your config files use the following:: | |
1223 |
|
1236 | |||
1224 | c.InlineBackend.close_figures = False |
|
1237 | c.InlineBackend.close_figures = False | |
1225 |
|
1238 | |||
1226 | Parameters |
|
1239 | Parameters | |
1227 | ---------- |
|
1240 | ---------- | |
1228 | close : bool |
|
1241 | close : bool | |
1229 | Should all matplotlib figures be automatically closed after each cell is |
|
1242 | Should all matplotlib figures be automatically closed after each cell is | |
1230 | run? |
|
1243 | run? | |
1231 | """ |
|
1244 | """ | |
1232 | from ipykernel.pylab.config import InlineBackend |
|
1245 | warnings.warn( | |
1233 | cfg = InlineBackend.instance() |
|
1246 | "`set_matplotlib_close` is deprecated since IPython 7.23, directly " | |
1234 | cfg.close_figures = close |
|
1247 | "use `matplotlib_inline.backend_inline.set_matplotlib_close()`", | |
|
1248 | DeprecationWarning, | |||
|
1249 | stacklevel=2, | |||
|
1250 | ) | |||
|
1251 | ||||
|
1252 | from matplotlib_inline.backend_inline import ( | |||
|
1253 | set_matplotlib_close as set_matplotlib_close_orig, | |||
|
1254 | ) | |||
|
1255 | ||||
|
1256 | set_matplotlib_close_orig(close) |
@@ -1,367 +1,374 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Top-level display functions for displaying object in different formats.""" |
|
2 | """Top-level display functions for displaying object in different formats.""" | |
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 binascii import b2a_hex |
|
8 | from binascii import b2a_hex | |
9 | import os |
|
9 | import os | |
10 | import sys |
|
10 | import sys | |
11 |
|
11 | |||
12 | __all__ = ['display', 'clear_output', 'publish_display_data', 'update_display', 'DisplayHandle'] |
|
12 | __all__ = ['display', 'clear_output', 'publish_display_data', 'update_display', 'DisplayHandle'] | |
13 |
|
13 | |||
14 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
15 | # utility functions |
|
15 | # utility functions | |
16 | #----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
17 |
|
17 | |||
18 |
|
18 | |||
19 | def _merge(d1, d2): |
|
19 | def _merge(d1, d2): | |
20 | """Like update, but merges sub-dicts instead of clobbering at the top level. |
|
20 | """Like update, but merges sub-dicts instead of clobbering at the top level. | |
21 |
|
21 | |||
22 | Updates d1 in-place |
|
22 | Updates d1 in-place | |
23 | """ |
|
23 | """ | |
24 |
|
24 | |||
25 | if not isinstance(d2, dict) or not isinstance(d1, dict): |
|
25 | if not isinstance(d2, dict) or not isinstance(d1, dict): | |
26 | return d2 |
|
26 | return d2 | |
27 | for key, value in d2.items(): |
|
27 | for key, value in d2.items(): | |
28 | d1[key] = _merge(d1.get(key), value) |
|
28 | d1[key] = _merge(d1.get(key), value) | |
29 | return d1 |
|
29 | return d1 | |
30 |
|
30 | |||
31 |
|
31 | |||
32 | #----------------------------------------------------------------------------- |
|
32 | #----------------------------------------------------------------------------- | |
33 | # Main functions |
|
33 | # Main functions | |
34 | #----------------------------------------------------------------------------- |
|
34 | #----------------------------------------------------------------------------- | |
35 |
|
35 | |||
36 |
|
36 | |||
37 | # use * to indicate transient is keyword-only |
|
37 | # use * to indicate transient is keyword-only | |
38 | def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs): |
|
38 | def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs): | |
39 | """Publish data and metadata to all frontends. |
|
39 | """Publish data and metadata to all frontends. | |
40 |
|
40 | |||
41 | See the ``display_data`` message in the messaging documentation for |
|
41 | See the ``display_data`` message in the messaging documentation for | |
42 | more details about this message type. |
|
42 | more details about this message type. | |
43 |
|
43 | |||
44 | Keys of data and metadata can be any mime-type. |
|
44 | Keys of data and metadata can be any mime-type. | |
45 |
|
45 | |||
46 | Parameters |
|
46 | Parameters | |
47 | ---------- |
|
47 | ---------- | |
48 | data : dict |
|
48 | data : dict | |
49 | A dictionary having keys that are valid MIME types (like |
|
49 | A dictionary having keys that are valid MIME types (like | |
50 | 'text/plain' or 'image/svg+xml') and values that are the data for |
|
50 | 'text/plain' or 'image/svg+xml') and values that are the data for | |
51 | that MIME type. The data itself must be a JSON'able data |
|
51 | that MIME type. The data itself must be a JSON'able data | |
52 | structure. Minimally all data should have the 'text/plain' data, |
|
52 | structure. Minimally all data should have the 'text/plain' data, | |
53 | which can be displayed by all frontends. If more than the plain |
|
53 | which can be displayed by all frontends. If more than the plain | |
54 | text is given, it is up to the frontend to decide which |
|
54 | text is given, it is up to the frontend to decide which | |
55 | representation to use. |
|
55 | representation to use. | |
56 | metadata : dict |
|
56 | metadata : dict | |
57 | A dictionary for metadata related to the data. This can contain |
|
57 | A dictionary for metadata related to the data. This can contain | |
58 | arbitrary key, value pairs that frontends can use to interpret |
|
58 | arbitrary key, value pairs that frontends can use to interpret | |
59 | the data. mime-type keys matching those in data can be used |
|
59 | the data. mime-type keys matching those in data can be used | |
60 | to specify metadata about particular representations. |
|
60 | to specify metadata about particular representations. | |
61 | source : str, deprecated |
|
61 | source : str, deprecated | |
62 | Unused. |
|
62 | Unused. | |
63 | transient : dict, keyword-only |
|
63 | transient : dict, keyword-only | |
64 | A dictionary of transient data, such as display_id. |
|
64 | A dictionary of transient data, such as display_id. | |
65 |
|
|
65 | """ | |
66 | from IPython.core.interactiveshell import InteractiveShell |
|
66 | from IPython.core.interactiveshell import InteractiveShell | |
67 |
|
67 | |||
68 | display_pub = InteractiveShell.instance().display_pub |
|
68 | display_pub = InteractiveShell.instance().display_pub | |
69 |
|
69 | |||
70 | # only pass transient if supplied, |
|
70 | # only pass transient if supplied, | |
71 | # to avoid errors with older ipykernel. |
|
71 | # to avoid errors with older ipykernel. | |
72 | # TODO: We could check for ipykernel version and provide a detailed upgrade message. |
|
72 | # TODO: We could check for ipykernel version and provide a detailed upgrade message. | |
73 | if transient: |
|
73 | if transient: | |
74 | kwargs['transient'] = transient |
|
74 | kwargs['transient'] = transient | |
75 |
|
75 | |||
76 | display_pub.publish( |
|
76 | display_pub.publish( | |
77 | data=data, |
|
77 | data=data, | |
78 | metadata=metadata, |
|
78 | metadata=metadata, | |
79 | **kwargs |
|
79 | **kwargs | |
80 | ) |
|
80 | ) | |
81 |
|
81 | |||
82 |
|
82 | |||
83 | def _new_id(): |
|
83 | def _new_id(): | |
84 | """Generate a new random text id with urandom""" |
|
84 | """Generate a new random text id with urandom""" | |
85 | return b2a_hex(os.urandom(16)).decode('ascii') |
|
85 | return b2a_hex(os.urandom(16)).decode('ascii') | |
86 |
|
86 | |||
87 |
|
87 | |||
88 | def display(*objs, include=None, exclude=None, metadata=None, transient=None, display_id=None, **kwargs): |
|
88 | def display( | |
|
89 | *objs, | |||
|
90 | include=None, | |||
|
91 | exclude=None, | |||
|
92 | metadata=None, | |||
|
93 | transient=None, | |||
|
94 | display_id=None, | |||
|
95 | raw=False, | |||
|
96 | clear=False, | |||
|
97 | **kwargs | |||
|
98 | ): | |||
89 | """Display a Python object in all frontends. |
|
99 | """Display a Python object in all frontends. | |
90 |
|
100 | |||
91 | By default all representations will be computed and sent to the frontends. |
|
101 | By default all representations will be computed and sent to the frontends. | |
92 | Frontends can decide which representation is used and how. |
|
102 | Frontends can decide which representation is used and how. | |
93 |
|
103 | |||
94 | In terminal IPython this will be similar to using :func:`print`, for use in richer |
|
104 | In terminal IPython this will be similar to using :func:`print`, for use in richer | |
95 | frontends see Jupyter notebook examples with rich display logic. |
|
105 | frontends see Jupyter notebook examples with rich display logic. | |
96 |
|
106 | |||
97 | Parameters |
|
107 | Parameters | |
98 | ---------- |
|
108 | ---------- | |
99 | *objs : object |
|
109 | *objs : object | |
100 | The Python objects to display. |
|
110 | The Python objects to display. | |
101 | raw : bool, optional |
|
111 | raw : bool, optional | |
102 | Are the objects to be displayed already mimetype-keyed dicts of raw display data, |
|
112 | Are the objects to be displayed already mimetype-keyed dicts of raw display data, | |
103 | or Python objects that need to be formatted before display? [default: False] |
|
113 | or Python objects that need to be formatted before display? [default: False] | |
104 | include : list, tuple or set, optional |
|
114 | include : list, tuple or set, optional | |
105 | A list of format type strings (MIME types) to include in the |
|
115 | A list of format type strings (MIME types) to include in the | |
106 | format data dict. If this is set *only* the format types included |
|
116 | format data dict. If this is set *only* the format types included | |
107 | in this list will be computed. |
|
117 | in this list will be computed. | |
108 | exclude : list, tuple or set, optional |
|
118 | exclude : list, tuple or set, optional | |
109 | A list of format type strings (MIME types) to exclude in the format |
|
119 | A list of format type strings (MIME types) to exclude in the format | |
110 | data dict. If this is set all format types will be computed, |
|
120 | data dict. If this is set all format types will be computed, | |
111 | except for those included in this argument. |
|
121 | except for those included in this argument. | |
112 | metadata : dict, optional |
|
122 | metadata : dict, optional | |
113 | A dictionary of metadata to associate with the output. |
|
123 | A dictionary of metadata to associate with the output. | |
114 | mime-type keys in this dictionary will be associated with the individual |
|
124 | mime-type keys in this dictionary will be associated with the individual | |
115 | representation formats, if they exist. |
|
125 | representation formats, if they exist. | |
116 | transient : dict, optional |
|
126 | transient : dict, optional | |
117 | A dictionary of transient data to associate with the output. |
|
127 | A dictionary of transient data to associate with the output. | |
118 | Data in this dict should not be persisted to files (e.g. notebooks). |
|
128 | Data in this dict should not be persisted to files (e.g. notebooks). | |
119 | display_id : str, bool optional |
|
129 | display_id : str, bool optional | |
120 | Set an id for the display. |
|
130 | Set an id for the display. | |
121 | This id can be used for updating this display area later via update_display. |
|
131 | This id can be used for updating this display area later via update_display. | |
122 | If given as `True`, generate a new `display_id` |
|
132 | If given as `True`, generate a new `display_id` | |
123 | kwargs: additional keyword-args, optional |
|
133 | clear : bool, optional | |
|
134 | Should the output area be cleared before displaying anything? If True, | |||
|
135 | this will wait for additional output before clearing. [default: False] | |||
|
136 | **kwargs : additional keyword-args, optional | |||
124 | Additional keyword-arguments are passed through to the display publisher. |
|
137 | Additional keyword-arguments are passed through to the display publisher. | |
125 |
|
138 | |||
126 | Returns |
|
139 | Returns | |
127 | ------- |
|
140 | ------- | |
128 |
|
||||
129 | handle: DisplayHandle |
|
141 | handle: DisplayHandle | |
130 | Returns a handle on updatable displays for use with :func:`update_display`, |
|
142 | Returns a handle on updatable displays for use with :func:`update_display`, | |
131 | if `display_id` is given. Returns :any:`None` if no `display_id` is given |
|
143 | if `display_id` is given. Returns :any:`None` if no `display_id` is given | |
132 | (default). |
|
144 | (default). | |
133 |
|
145 | |||
134 | Examples |
|
146 | Examples | |
135 | -------- |
|
147 | -------- | |
136 |
|
||||
137 | >>> class Json(object): |
|
148 | >>> class Json(object): | |
138 | ... def __init__(self, json): |
|
149 | ... def __init__(self, json): | |
139 | ... self.json = json |
|
150 | ... self.json = json | |
140 | ... def _repr_pretty_(self, pp, cycle): |
|
151 | ... def _repr_pretty_(self, pp, cycle): | |
141 | ... import json |
|
152 | ... import json | |
142 | ... pp.text(json.dumps(self.json, indent=2)) |
|
153 | ... pp.text(json.dumps(self.json, indent=2)) | |
143 | ... def __repr__(self): |
|
154 | ... def __repr__(self): | |
144 | ... return str(self.json) |
|
155 | ... return str(self.json) | |
145 | ... |
|
156 | ... | |
146 |
|
157 | |||
147 | >>> d = Json({1:2, 3: {4:5}}) |
|
158 | >>> d = Json({1:2, 3: {4:5}}) | |
148 |
|
159 | |||
149 | >>> print(d) |
|
160 | >>> print(d) | |
150 | {1: 2, 3: {4: 5}} |
|
161 | {1: 2, 3: {4: 5}} | |
151 |
|
162 | |||
152 | >>> display(d) |
|
163 | >>> display(d) | |
153 | { |
|
164 | { | |
154 | "1": 2, |
|
165 | "1": 2, | |
155 | "3": { |
|
166 | "3": { | |
156 | "4": 5 |
|
167 | "4": 5 | |
157 | } |
|
168 | } | |
158 | } |
|
169 | } | |
159 |
|
170 | |||
160 | >>> def int_formatter(integer, pp, cycle): |
|
171 | >>> def int_formatter(integer, pp, cycle): | |
161 | ... pp.text('I'*integer) |
|
172 | ... pp.text('I'*integer) | |
162 |
|
173 | |||
163 | >>> plain = get_ipython().display_formatter.formatters['text/plain'] |
|
174 | >>> plain = get_ipython().display_formatter.formatters['text/plain'] | |
164 | >>> plain.for_type(int, int_formatter) |
|
175 | >>> plain.for_type(int, int_formatter) | |
165 | <function _repr_pprint at 0x...> |
|
176 | <function _repr_pprint at 0x...> | |
166 | >>> display(7-5) |
|
177 | >>> display(7-5) | |
167 | II |
|
178 | II | |
168 |
|
179 | |||
169 | >>> del plain.type_printers[int] |
|
180 | >>> del plain.type_printers[int] | |
170 | >>> display(7-5) |
|
181 | >>> display(7-5) | |
171 | 2 |
|
182 | 2 | |
172 |
|
183 | |||
173 | See Also |
|
184 | See Also | |
174 | -------- |
|
185 | -------- | |
175 |
|
||||
176 | :func:`update_display` |
|
186 | :func:`update_display` | |
177 |
|
187 | |||
178 | Notes |
|
188 | Notes | |
179 | ----- |
|
189 | ----- | |
180 |
|
||||
181 | In Python, objects can declare their textual representation using the |
|
190 | In Python, objects can declare their textual representation using the | |
182 | `__repr__` method. IPython expands on this idea and allows objects to declare |
|
191 | `__repr__` method. IPython expands on this idea and allows objects to declare | |
183 | other, rich representations including: |
|
192 | other, rich representations including: | |
184 |
|
193 | |||
185 | - HTML |
|
194 | - HTML | |
186 | - JSON |
|
195 | - JSON | |
187 | - PNG |
|
196 | - PNG | |
188 | - JPEG |
|
197 | - JPEG | |
189 | - SVG |
|
198 | - SVG | |
190 | - LaTeX |
|
199 | - LaTeX | |
191 |
|
200 | |||
192 | A single object can declare some or all of these representations; all are |
|
201 | A single object can declare some or all of these representations; all are | |
193 | handled by IPython's display system. |
|
202 | handled by IPython's display system. | |
194 |
|
203 | |||
195 | The main idea of the first approach is that you have to implement special |
|
204 | The main idea of the first approach is that you have to implement special | |
196 | display methods when you define your class, one for each representation you |
|
205 | display methods when you define your class, one for each representation you | |
197 | want to use. Here is a list of the names of the special methods and the |
|
206 | want to use. Here is a list of the names of the special methods and the | |
198 | values they must return: |
|
207 | values they must return: | |
199 |
|
208 | |||
200 | - `_repr_html_`: return raw HTML as a string, or a tuple (see below). |
|
209 | - `_repr_html_`: return raw HTML as a string, or a tuple (see below). | |
201 | - `_repr_json_`: return a JSONable dict, or a tuple (see below). |
|
210 | - `_repr_json_`: return a JSONable dict, or a tuple (see below). | |
202 | - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below). |
|
211 | - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below). | |
203 | - `_repr_png_`: return raw PNG data, or a tuple (see below). |
|
212 | - `_repr_png_`: return raw PNG data, or a tuple (see below). | |
204 | - `_repr_svg_`: return raw SVG data as a string, or a tuple (see below). |
|
213 | - `_repr_svg_`: return raw SVG data as a string, or a tuple (see below). | |
205 | - `_repr_latex_`: return LaTeX commands in a string surrounded by "$", |
|
214 | - `_repr_latex_`: return LaTeX commands in a string surrounded by "$", | |
206 | or a tuple (see below). |
|
215 | or a tuple (see below). | |
207 | - `_repr_mimebundle_`: return a full mimebundle containing the mapping |
|
216 | - `_repr_mimebundle_`: return a full mimebundle containing the mapping | |
208 | from all mimetypes to data. |
|
217 | from all mimetypes to data. | |
209 | Use this for any mime-type not listed above. |
|
218 | Use this for any mime-type not listed above. | |
210 |
|
219 | |||
211 | The above functions may also return the object's metadata alonside the |
|
220 | The above functions may also return the object's metadata alonside the | |
212 | data. If the metadata is available, the functions will return a tuple |
|
221 | data. If the metadata is available, the functions will return a tuple | |
213 | containing the data and metadata, in that order. If there is no metadata |
|
222 | containing the data and metadata, in that order. If there is no metadata | |
214 | available, then the functions will return the data only. |
|
223 | available, then the functions will return the data only. | |
215 |
|
224 | |||
216 | When you are directly writing your own classes, you can adapt them for |
|
225 | When you are directly writing your own classes, you can adapt them for | |
217 | display in IPython by following the above approach. But in practice, you |
|
226 | display in IPython by following the above approach. But in practice, you | |
218 | often need to work with existing classes that you can't easily modify. |
|
227 | often need to work with existing classes that you can't easily modify. | |
219 |
|
228 | |||
220 | You can refer to the documentation on integrating with the display system in |
|
229 | You can refer to the documentation on integrating with the display system in | |
221 | order to register custom formatters for already existing types |
|
230 | order to register custom formatters for already existing types | |
222 | (:ref:`integrating_rich_display`). |
|
231 | (:ref:`integrating_rich_display`). | |
223 |
|
232 | |||
224 | .. versionadded:: 5.4 display available without import |
|
233 | .. versionadded:: 5.4 display available without import | |
225 | .. versionadded:: 6.1 display available without import |
|
234 | .. versionadded:: 6.1 display available without import | |
226 |
|
235 | |||
227 | Since IPython 5.4 and 6.1 :func:`display` is automatically made available to |
|
236 | Since IPython 5.4 and 6.1 :func:`display` is automatically made available to | |
228 | the user without import. If you are using display in a document that might |
|
237 | the user without import. If you are using display in a document that might | |
229 | be used in a pure python context or with older version of IPython, use the |
|
238 | be used in a pure python context or with older version of IPython, use the | |
230 | following import at the top of your file:: |
|
239 | following import at the top of your file:: | |
231 |
|
240 | |||
232 | from IPython.display import display |
|
241 | from IPython.display import display | |
233 |
|
242 | |||
234 | """ |
|
243 | """ | |
235 | from IPython.core.interactiveshell import InteractiveShell |
|
244 | from IPython.core.interactiveshell import InteractiveShell | |
236 |
|
245 | |||
237 | if not InteractiveShell.initialized(): |
|
246 | if not InteractiveShell.initialized(): | |
238 | # Directly print objects. |
|
247 | # Directly print objects. | |
239 | print(*objs) |
|
248 | print(*objs) | |
240 | return |
|
249 | return | |
241 |
|
250 | |||
242 | raw = kwargs.pop('raw', False) |
|
|||
243 | if transient is None: |
|
251 | if transient is None: | |
244 | transient = {} |
|
252 | transient = {} | |
245 | if metadata is None: |
|
253 | if metadata is None: | |
246 | metadata={} |
|
254 | metadata={} | |
247 | if display_id: |
|
255 | if display_id: | |
248 | if display_id is True: |
|
256 | if display_id is True: | |
249 | display_id = _new_id() |
|
257 | display_id = _new_id() | |
250 | transient['display_id'] = display_id |
|
258 | transient['display_id'] = display_id | |
251 | if kwargs.get('update') and 'display_id' not in transient: |
|
259 | if kwargs.get('update') and 'display_id' not in transient: | |
252 | raise TypeError('display_id required for update_display') |
|
260 | raise TypeError('display_id required for update_display') | |
253 | if transient: |
|
261 | if transient: | |
254 | kwargs['transient'] = transient |
|
262 | kwargs['transient'] = transient | |
255 |
|
263 | |||
256 | if not objs and display_id: |
|
264 | if not objs and display_id: | |
257 | # if given no objects, but still a request for a display_id, |
|
265 | # if given no objects, but still a request for a display_id, | |
258 | # we assume the user wants to insert an empty output that |
|
266 | # we assume the user wants to insert an empty output that | |
259 | # can be updated later |
|
267 | # can be updated later | |
260 | objs = [{}] |
|
268 | objs = [{}] | |
261 | raw = True |
|
269 | raw = True | |
262 |
|
270 | |||
263 | if not raw: |
|
271 | if not raw: | |
264 | format = InteractiveShell.instance().display_formatter.format |
|
272 | format = InteractiveShell.instance().display_formatter.format | |
265 |
|
273 | |||
|
274 | if clear: | |||
|
275 | clear_output(wait=True) | |||
|
276 | ||||
266 | for obj in objs: |
|
277 | for obj in objs: | |
267 | if raw: |
|
278 | if raw: | |
268 | publish_display_data(data=obj, metadata=metadata, **kwargs) |
|
279 | publish_display_data(data=obj, metadata=metadata, **kwargs) | |
269 | else: |
|
280 | else: | |
270 | format_dict, md_dict = format(obj, include=include, exclude=exclude) |
|
281 | format_dict, md_dict = format(obj, include=include, exclude=exclude) | |
271 | if not format_dict: |
|
282 | if not format_dict: | |
272 | # nothing to display (e.g. _ipython_display_ took over) |
|
283 | # nothing to display (e.g. _ipython_display_ took over) | |
273 | continue |
|
284 | continue | |
274 | if metadata: |
|
285 | if metadata: | |
275 | # kwarg-specified metadata gets precedence |
|
286 | # kwarg-specified metadata gets precedence | |
276 | _merge(md_dict, metadata) |
|
287 | _merge(md_dict, metadata) | |
277 | publish_display_data(data=format_dict, metadata=md_dict, **kwargs) |
|
288 | publish_display_data(data=format_dict, metadata=md_dict, **kwargs) | |
278 | if display_id: |
|
289 | if display_id: | |
279 | return DisplayHandle(display_id) |
|
290 | return DisplayHandle(display_id) | |
280 |
|
291 | |||
281 |
|
292 | |||
282 | # use * for keyword-only display_id arg |
|
293 | # use * for keyword-only display_id arg | |
283 | def update_display(obj, *, display_id, **kwargs): |
|
294 | def update_display(obj, *, display_id, **kwargs): | |
284 | """Update an existing display by id |
|
295 | """Update an existing display by id | |
285 |
|
296 | |||
286 | Parameters |
|
297 | Parameters | |
287 | ---------- |
|
298 | ---------- | |
288 |
|
299 | obj | ||
289 | obj: |
|
|||
290 | The object with which to update the display |
|
300 | The object with which to update the display | |
291 | display_id: keyword-only |
|
301 | display_id : keyword-only | |
292 | The id of the display to update |
|
302 | The id of the display to update | |
293 |
|
303 | |||
294 | See Also |
|
304 | See Also | |
295 | -------- |
|
305 | -------- | |
296 |
|
||||
297 | :func:`display` |
|
306 | :func:`display` | |
298 | """ |
|
307 | """ | |
299 | kwargs['update'] = True |
|
308 | kwargs['update'] = True | |
300 | display(obj, display_id=display_id, **kwargs) |
|
309 | display(obj, display_id=display_id, **kwargs) | |
301 |
|
310 | |||
302 |
|
311 | |||
303 | class DisplayHandle(object): |
|
312 | class DisplayHandle(object): | |
304 | """A handle on an updatable display |
|
313 | """A handle on an updatable display | |
305 |
|
314 | |||
306 | Call `.update(obj)` to display a new object. |
|
315 | Call `.update(obj)` to display a new object. | |
307 |
|
316 | |||
308 | Call `.display(obj`) to add a new instance of this display, |
|
317 | Call `.display(obj`) to add a new instance of this display, | |
309 | and update existing instances. |
|
318 | and update existing instances. | |
310 |
|
319 | |||
311 | See Also |
|
320 | See Also | |
312 | -------- |
|
321 | -------- | |
313 |
|
322 | |||
314 | :func:`display`, :func:`update_display` |
|
323 | :func:`display`, :func:`update_display` | |
315 |
|
324 | |||
316 | """ |
|
325 | """ | |
317 |
|
326 | |||
318 | def __init__(self, display_id=None): |
|
327 | def __init__(self, display_id=None): | |
319 | if display_id is None: |
|
328 | if display_id is None: | |
320 | display_id = _new_id() |
|
329 | display_id = _new_id() | |
321 | self.display_id = display_id |
|
330 | self.display_id = display_id | |
322 |
|
331 | |||
323 | def __repr__(self): |
|
332 | def __repr__(self): | |
324 | return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id) |
|
333 | return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id) | |
325 |
|
334 | |||
326 | def display(self, obj, **kwargs): |
|
335 | def display(self, obj, **kwargs): | |
327 | """Make a new display with my id, updating existing instances. |
|
336 | """Make a new display with my id, updating existing instances. | |
328 |
|
337 | |||
329 | Parameters |
|
338 | Parameters | |
330 | ---------- |
|
339 | ---------- | |
331 |
|
340 | obj | ||
332 | obj: |
|
|||
333 | object to display |
|
341 | object to display | |
334 |
**kwargs |
|
342 | **kwargs | |
335 | additional keyword arguments passed to display |
|
343 | additional keyword arguments passed to display | |
336 | """ |
|
344 | """ | |
337 | display(obj, display_id=self.display_id, **kwargs) |
|
345 | display(obj, display_id=self.display_id, **kwargs) | |
338 |
|
346 | |||
339 | def update(self, obj, **kwargs): |
|
347 | def update(self, obj, **kwargs): | |
340 | """Update existing displays with my id |
|
348 | """Update existing displays with my id | |
341 |
|
349 | |||
342 | Parameters |
|
350 | Parameters | |
343 | ---------- |
|
351 | ---------- | |
344 |
|
352 | obj | ||
345 | obj: |
|
|||
346 | object to display |
|
353 | object to display | |
347 |
**kwargs |
|
354 | **kwargs | |
348 | additional keyword arguments passed to update_display |
|
355 | additional keyword arguments passed to update_display | |
349 | """ |
|
356 | """ | |
350 | update_display(obj, display_id=self.display_id, **kwargs) |
|
357 | update_display(obj, display_id=self.display_id, **kwargs) | |
351 |
|
358 | |||
352 |
|
359 | |||
353 | def clear_output(wait=False): |
|
360 | def clear_output(wait=False): | |
354 | """Clear the output of the current cell receiving output. |
|
361 | """Clear the output of the current cell receiving output. | |
355 |
|
362 | |||
356 | Parameters |
|
363 | Parameters | |
357 | ---------- |
|
364 | ---------- | |
358 | wait : bool [default: false] |
|
365 | wait : bool [default: false] | |
359 | Wait to clear the output until new output is available to replace it.""" |
|
366 | Wait to clear the output until new output is available to replace it.""" | |
360 | from IPython.core.interactiveshell import InteractiveShell |
|
367 | from IPython.core.interactiveshell import InteractiveShell | |
361 | if InteractiveShell.initialized(): |
|
368 | if InteractiveShell.initialized(): | |
362 | InteractiveShell.instance().display_pub.clear_output(wait) |
|
369 | InteractiveShell.instance().display_pub.clear_output(wait) | |
363 | else: |
|
370 | else: | |
364 | print('\033[2K\r', end='') |
|
371 | print('\033[2K\r', end='') | |
365 | sys.stdout.flush() |
|
372 | sys.stdout.flush() | |
366 | print('\033[2K\r', end='') |
|
373 | print('\033[2K\r', end='') | |
367 | sys.stderr.flush() |
|
374 | sys.stderr.flush() |
@@ -1,325 +1,325 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Displayhook for IPython. |
|
2 | """Displayhook for IPython. | |
3 |
|
3 | |||
4 | This defines a callable class that IPython uses for `sys.displayhook`. |
|
4 | This defines a callable class that IPython uses for `sys.displayhook`. | |
5 | """ |
|
5 | """ | |
6 |
|
6 | |||
7 | # Copyright (c) IPython Development Team. |
|
7 | # Copyright (c) IPython Development Team. | |
8 | # Distributed under the terms of the Modified BSD License. |
|
8 | # Distributed under the terms of the Modified BSD License. | |
9 |
|
9 | |||
10 | import builtins as builtin_mod |
|
10 | import builtins as builtin_mod | |
11 | import sys |
|
11 | import sys | |
12 | import io as _io |
|
12 | import io as _io | |
13 | import tokenize |
|
13 | import tokenize | |
14 |
|
14 | |||
15 | from traitlets.config.configurable import Configurable |
|
15 | from traitlets.config.configurable import Configurable | |
16 | from traitlets import Instance, Float |
|
16 | from traitlets import Instance, Float | |
17 | from warnings import warn |
|
17 | from warnings import warn | |
18 |
|
18 | |||
19 | # TODO: Move the various attributes (cache_size, [others now moved]). Some |
|
19 | # TODO: Move the various attributes (cache_size, [others now moved]). Some | |
20 | # of these are also attributes of InteractiveShell. They should be on ONE object |
|
20 | # of these are also attributes of InteractiveShell. They should be on ONE object | |
21 | # only and the other objects should ask that one object for their values. |
|
21 | # only and the other objects should ask that one object for their values. | |
22 |
|
22 | |||
23 | class DisplayHook(Configurable): |
|
23 | class DisplayHook(Configurable): | |
24 | """The custom IPython displayhook to replace sys.displayhook. |
|
24 | """The custom IPython displayhook to replace sys.displayhook. | |
25 |
|
25 | |||
26 | This class does many things, but the basic idea is that it is a callable |
|
26 | This class does many things, but the basic idea is that it is a callable | |
27 | that gets called anytime user code returns a value. |
|
27 | that gets called anytime user code returns a value. | |
28 | """ |
|
28 | """ | |
29 |
|
29 | |||
30 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', |
|
30 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', | |
31 | allow_none=True) |
|
31 | allow_none=True) | |
32 | exec_result = Instance('IPython.core.interactiveshell.ExecutionResult', |
|
32 | exec_result = Instance('IPython.core.interactiveshell.ExecutionResult', | |
33 | allow_none=True) |
|
33 | allow_none=True) | |
34 | cull_fraction = Float(0.2) |
|
34 | cull_fraction = Float(0.2) | |
35 |
|
35 | |||
36 | def __init__(self, shell=None, cache_size=1000, **kwargs): |
|
36 | def __init__(self, shell=None, cache_size=1000, **kwargs): | |
37 | super(DisplayHook, self).__init__(shell=shell, **kwargs) |
|
37 | super(DisplayHook, self).__init__(shell=shell, **kwargs) | |
38 | cache_size_min = 3 |
|
38 | cache_size_min = 3 | |
39 | if cache_size <= 0: |
|
39 | if cache_size <= 0: | |
40 | self.do_full_cache = 0 |
|
40 | self.do_full_cache = 0 | |
41 | cache_size = 0 |
|
41 | cache_size = 0 | |
42 | elif cache_size < cache_size_min: |
|
42 | elif cache_size < cache_size_min: | |
43 | self.do_full_cache = 0 |
|
43 | self.do_full_cache = 0 | |
44 | cache_size = 0 |
|
44 | cache_size = 0 | |
45 | warn('caching was disabled (min value for cache size is %s).' % |
|
45 | warn('caching was disabled (min value for cache size is %s).' % | |
46 | cache_size_min,stacklevel=3) |
|
46 | cache_size_min,stacklevel=3) | |
47 | else: |
|
47 | else: | |
48 | self.do_full_cache = 1 |
|
48 | self.do_full_cache = 1 | |
49 |
|
49 | |||
50 | self.cache_size = cache_size |
|
50 | self.cache_size = cache_size | |
51 |
|
51 | |||
52 | # we need a reference to the user-level namespace |
|
52 | # we need a reference to the user-level namespace | |
53 | self.shell = shell |
|
53 | self.shell = shell | |
54 |
|
54 | |||
55 | self._,self.__,self.___ = '','','' |
|
55 | self._,self.__,self.___ = '','','' | |
56 |
|
56 | |||
57 | # these are deliberately global: |
|
57 | # these are deliberately global: | |
58 | to_user_ns = {'_':self._,'__':self.__,'___':self.___} |
|
58 | to_user_ns = {'_':self._,'__':self.__,'___':self.___} | |
59 | self.shell.user_ns.update(to_user_ns) |
|
59 | self.shell.user_ns.update(to_user_ns) | |
60 |
|
60 | |||
61 | @property |
|
61 | @property | |
62 | def prompt_count(self): |
|
62 | def prompt_count(self): | |
63 | return self.shell.execution_count |
|
63 | return self.shell.execution_count | |
64 |
|
64 | |||
65 | #------------------------------------------------------------------------- |
|
65 | #------------------------------------------------------------------------- | |
66 | # Methods used in __call__. Override these methods to modify the behavior |
|
66 | # Methods used in __call__. Override these methods to modify the behavior | |
67 | # of the displayhook. |
|
67 | # of the displayhook. | |
68 | #------------------------------------------------------------------------- |
|
68 | #------------------------------------------------------------------------- | |
69 |
|
69 | |||
70 | def check_for_underscore(self): |
|
70 | def check_for_underscore(self): | |
71 | """Check if the user has set the '_' variable by hand.""" |
|
71 | """Check if the user has set the '_' variable by hand.""" | |
72 | # If something injected a '_' variable in __builtin__, delete |
|
72 | # If something injected a '_' variable in __builtin__, delete | |
73 | # ipython's automatic one so we don't clobber that. gettext() in |
|
73 | # ipython's automatic one so we don't clobber that. gettext() in | |
74 | # particular uses _, so we need to stay away from it. |
|
74 | # particular uses _, so we need to stay away from it. | |
75 | if '_' in builtin_mod.__dict__: |
|
75 | if '_' in builtin_mod.__dict__: | |
76 | try: |
|
76 | try: | |
77 | user_value = self.shell.user_ns['_'] |
|
77 | user_value = self.shell.user_ns['_'] | |
78 | if user_value is not self._: |
|
78 | if user_value is not self._: | |
79 | return |
|
79 | return | |
80 | del self.shell.user_ns['_'] |
|
80 | del self.shell.user_ns['_'] | |
81 | except KeyError: |
|
81 | except KeyError: | |
82 | pass |
|
82 | pass | |
83 |
|
83 | |||
84 | def quiet(self): |
|
84 | def quiet(self): | |
85 | """Should we silence the display hook because of ';'?""" |
|
85 | """Should we silence the display hook because of ';'?""" | |
86 | # do not print output if input ends in ';' |
|
86 | # do not print output if input ends in ';' | |
87 |
|
87 | |||
88 | try: |
|
88 | try: | |
89 | cell = self.shell.history_manager.input_hist_parsed[-1] |
|
89 | cell = self.shell.history_manager.input_hist_parsed[-1] | |
90 | except IndexError: |
|
90 | except IndexError: | |
91 | # some uses of ipshellembed may fail here |
|
91 | # some uses of ipshellembed may fail here | |
92 | return False |
|
92 | return False | |
93 |
|
93 | |||
94 | sio = _io.StringIO(cell) |
|
94 | sio = _io.StringIO(cell) | |
95 | tokens = list(tokenize.generate_tokens(sio.readline)) |
|
95 | tokens = list(tokenize.generate_tokens(sio.readline)) | |
96 |
|
96 | |||
97 | for token in reversed(tokens): |
|
97 | for token in reversed(tokens): | |
98 | if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT): |
|
98 | if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT): | |
99 | continue |
|
99 | continue | |
100 | if (token[0] == tokenize.OP) and (token[1] == ';'): |
|
100 | if (token[0] == tokenize.OP) and (token[1] == ';'): | |
101 | return True |
|
101 | return True | |
102 | else: |
|
102 | else: | |
103 | return False |
|
103 | return False | |
104 |
|
104 | |||
105 | def start_displayhook(self): |
|
105 | def start_displayhook(self): | |
106 | """Start the displayhook, initializing resources.""" |
|
106 | """Start the displayhook, initializing resources.""" | |
107 | pass |
|
107 | pass | |
108 |
|
108 | |||
109 | def write_output_prompt(self): |
|
109 | def write_output_prompt(self): | |
110 | """Write the output prompt. |
|
110 | """Write the output prompt. | |
111 |
|
111 | |||
112 | The default implementation simply writes the prompt to |
|
112 | The default implementation simply writes the prompt to | |
113 | ``sys.stdout``. |
|
113 | ``sys.stdout``. | |
114 | """ |
|
114 | """ | |
115 | # Use write, not print which adds an extra space. |
|
115 | # Use write, not print which adds an extra space. | |
116 | sys.stdout.write(self.shell.separate_out) |
|
116 | sys.stdout.write(self.shell.separate_out) | |
117 | outprompt = 'Out[{}]: '.format(self.shell.execution_count) |
|
117 | outprompt = 'Out[{}]: '.format(self.shell.execution_count) | |
118 | if self.do_full_cache: |
|
118 | if self.do_full_cache: | |
119 | sys.stdout.write(outprompt) |
|
119 | sys.stdout.write(outprompt) | |
120 |
|
120 | |||
121 | def compute_format_data(self, result): |
|
121 | def compute_format_data(self, result): | |
122 | """Compute format data of the object to be displayed. |
|
122 | """Compute format data of the object to be displayed. | |
123 |
|
123 | |||
124 | The format data is a generalization of the :func:`repr` of an object. |
|
124 | The format data is a generalization of the :func:`repr` of an object. | |
125 | In the default implementation the format data is a :class:`dict` of |
|
125 | In the default implementation the format data is a :class:`dict` of | |
126 | key value pair where the keys are valid MIME types and the values |
|
126 | key value pair where the keys are valid MIME types and the values | |
127 | are JSON'able data structure containing the raw data for that MIME |
|
127 | are JSON'able data structure containing the raw data for that MIME | |
128 | type. It is up to frontends to determine pick a MIME to to use and |
|
128 | type. It is up to frontends to determine pick a MIME to to use and | |
129 | display that data in an appropriate manner. |
|
129 | display that data in an appropriate manner. | |
130 |
|
130 | |||
131 | This method only computes the format data for the object and should |
|
131 | This method only computes the format data for the object and should | |
132 | NOT actually print or write that to a stream. |
|
132 | NOT actually print or write that to a stream. | |
133 |
|
133 | |||
134 | Parameters |
|
134 | Parameters | |
135 | ---------- |
|
135 | ---------- | |
136 | result : object |
|
136 | result : object | |
137 | The Python object passed to the display hook, whose format will be |
|
137 | The Python object passed to the display hook, whose format will be | |
138 | computed. |
|
138 | computed. | |
139 |
|
139 | |||
140 | Returns |
|
140 | Returns | |
141 | ------- |
|
141 | ------- | |
142 | (format_dict, md_dict) : dict |
|
142 | (format_dict, md_dict) : dict | |
143 | format_dict is a :class:`dict` whose keys are valid MIME types and values are |
|
143 | format_dict is a :class:`dict` whose keys are valid MIME types and values are | |
144 | JSON'able raw data for that MIME type. It is recommended that |
|
144 | JSON'able raw data for that MIME type. It is recommended that | |
145 | all return values of this should always include the "text/plain" |
|
145 | all return values of this should always include the "text/plain" | |
146 | MIME type representation of the object. |
|
146 | MIME type representation of the object. | |
147 | md_dict is a :class:`dict` with the same MIME type keys |
|
147 | md_dict is a :class:`dict` with the same MIME type keys | |
148 | of metadata associated with each output. |
|
148 | of metadata associated with each output. | |
149 |
|
149 | |||
150 | """ |
|
150 | """ | |
151 | return self.shell.display_formatter.format(result) |
|
151 | return self.shell.display_formatter.format(result) | |
152 |
|
152 | |||
153 | # This can be set to True by the write_output_prompt method in a subclass |
|
153 | # This can be set to True by the write_output_prompt method in a subclass | |
154 | prompt_end_newline = False |
|
154 | prompt_end_newline = False | |
155 |
|
155 | |||
156 | def write_format_data(self, format_dict, md_dict=None) -> None: |
|
156 | def write_format_data(self, format_dict, md_dict=None) -> None: | |
157 | """Write the format data dict to the frontend. |
|
157 | """Write the format data dict to the frontend. | |
158 |
|
158 | |||
159 | This default version of this method simply writes the plain text |
|
159 | This default version of this method simply writes the plain text | |
160 | representation of the object to ``sys.stdout``. Subclasses should |
|
160 | representation of the object to ``sys.stdout``. Subclasses should | |
161 | override this method to send the entire `format_dict` to the |
|
161 | override this method to send the entire `format_dict` to the | |
162 | frontends. |
|
162 | frontends. | |
163 |
|
163 | |||
164 | Parameters |
|
164 | Parameters | |
165 | ---------- |
|
165 | ---------- | |
166 | format_dict : dict |
|
166 | format_dict : dict | |
167 | The format dict for the object passed to `sys.displayhook`. |
|
167 | The format dict for the object passed to `sys.displayhook`. | |
168 | md_dict : dict (optional) |
|
168 | md_dict : dict (optional) | |
169 | The metadata dict to be associated with the display data. |
|
169 | The metadata dict to be associated with the display data. | |
170 | """ |
|
170 | """ | |
171 | if 'text/plain' not in format_dict: |
|
171 | if 'text/plain' not in format_dict: | |
172 | # nothing to do |
|
172 | # nothing to do | |
173 | return |
|
173 | return | |
174 | # We want to print because we want to always make sure we have a |
|
174 | # We want to print because we want to always make sure we have a | |
175 | # newline, even if all the prompt separators are ''. This is the |
|
175 | # newline, even if all the prompt separators are ''. This is the | |
176 | # standard IPython behavior. |
|
176 | # standard IPython behavior. | |
177 | result_repr = format_dict['text/plain'] |
|
177 | result_repr = format_dict['text/plain'] | |
178 | if '\n' in result_repr: |
|
178 | if '\n' in result_repr: | |
179 | # So that multi-line strings line up with the left column of |
|
179 | # So that multi-line strings line up with the left column of | |
180 | # the screen, instead of having the output prompt mess up |
|
180 | # the screen, instead of having the output prompt mess up | |
181 | # their first line. |
|
181 | # their first line. | |
182 | # We use the prompt template instead of the expanded prompt |
|
182 | # We use the prompt template instead of the expanded prompt | |
183 | # because the expansion may add ANSI escapes that will interfere |
|
183 | # because the expansion may add ANSI escapes that will interfere | |
184 | # with our ability to determine whether or not we should add |
|
184 | # with our ability to determine whether or not we should add | |
185 | # a newline. |
|
185 | # a newline. | |
186 | if not self.prompt_end_newline: |
|
186 | if not self.prompt_end_newline: | |
187 | # But avoid extraneous empty lines. |
|
187 | # But avoid extraneous empty lines. | |
188 | result_repr = '\n' + result_repr |
|
188 | result_repr = '\n' + result_repr | |
189 |
|
189 | |||
190 | try: |
|
190 | try: | |
191 | print(result_repr) |
|
191 | print(result_repr) | |
192 | except UnicodeEncodeError: |
|
192 | except UnicodeEncodeError: | |
193 | # If a character is not supported by the terminal encoding replace |
|
193 | # If a character is not supported by the terminal encoding replace | |
194 | # it with its \u or \x representation |
|
194 | # it with its \u or \x representation | |
195 | print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding)) |
|
195 | print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding)) | |
196 |
|
196 | |||
197 | def update_user_ns(self, result): |
|
197 | def update_user_ns(self, result): | |
198 | """Update user_ns with various things like _, __, _1, etc.""" |
|
198 | """Update user_ns with various things like _, __, _1, etc.""" | |
199 |
|
199 | |||
200 | # Avoid recursive reference when displaying _oh/Out |
|
200 | # Avoid recursive reference when displaying _oh/Out | |
201 | if self.cache_size and result is not self.shell.user_ns['_oh']: |
|
201 | if self.cache_size and result is not self.shell.user_ns['_oh']: | |
202 | if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache: |
|
202 | if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache: | |
203 | self.cull_cache() |
|
203 | self.cull_cache() | |
204 |
|
204 | |||
205 | # Don't overwrite '_' and friends if '_' is in __builtin__ |
|
205 | # Don't overwrite '_' and friends if '_' is in __builtin__ | |
206 | # (otherwise we cause buggy behavior for things like gettext). and |
|
206 | # (otherwise we cause buggy behavior for things like gettext). and | |
207 | # do not overwrite _, __ or ___ if one of these has been assigned |
|
207 | # do not overwrite _, __ or ___ if one of these has been assigned | |
208 | # by the user. |
|
208 | # by the user. | |
209 | update_unders = True |
|
209 | update_unders = True | |
210 | for unders in ['_'*i for i in range(1,4)]: |
|
210 | for unders in ['_'*i for i in range(1,4)]: | |
211 | if not unders in self.shell.user_ns: |
|
211 | if not unders in self.shell.user_ns: | |
212 | continue |
|
212 | continue | |
213 | if getattr(self, unders) is not self.shell.user_ns.get(unders): |
|
213 | if getattr(self, unders) is not self.shell.user_ns.get(unders): | |
214 | update_unders = False |
|
214 | update_unders = False | |
215 |
|
215 | |||
216 | self.___ = self.__ |
|
216 | self.___ = self.__ | |
217 | self.__ = self._ |
|
217 | self.__ = self._ | |
218 | self._ = result |
|
218 | self._ = result | |
219 |
|
219 | |||
220 | if ('_' not in builtin_mod.__dict__) and (update_unders): |
|
220 | if ('_' not in builtin_mod.__dict__) and (update_unders): | |
221 | self.shell.push({'_':self._, |
|
221 | self.shell.push({'_':self._, | |
222 | '__':self.__, |
|
222 | '__':self.__, | |
223 | '___':self.___}, interactive=False) |
|
223 | '___':self.___}, interactive=False) | |
224 |
|
224 | |||
225 | # hackish access to top-level namespace to create _1,_2... dynamically |
|
225 | # hackish access to top-level namespace to create _1,_2... dynamically | |
226 | to_main = {} |
|
226 | to_main = {} | |
227 | if self.do_full_cache: |
|
227 | if self.do_full_cache: | |
228 | new_result = '_%s' % self.prompt_count |
|
228 | new_result = '_%s' % self.prompt_count | |
229 | to_main[new_result] = result |
|
229 | to_main[new_result] = result | |
230 | self.shell.push(to_main, interactive=False) |
|
230 | self.shell.push(to_main, interactive=False) | |
231 | self.shell.user_ns['_oh'][self.prompt_count] = result |
|
231 | self.shell.user_ns['_oh'][self.prompt_count] = result | |
232 |
|
232 | |||
233 | def fill_exec_result(self, result): |
|
233 | def fill_exec_result(self, result): | |
234 | if self.exec_result is not None: |
|
234 | if self.exec_result is not None: | |
235 | self.exec_result.result = result |
|
235 | self.exec_result.result = result | |
236 |
|
236 | |||
237 | def log_output(self, format_dict): |
|
237 | def log_output(self, format_dict): | |
238 | """Log the output.""" |
|
238 | """Log the output.""" | |
239 | if 'text/plain' not in format_dict: |
|
239 | if 'text/plain' not in format_dict: | |
240 | # nothing to do |
|
240 | # nothing to do | |
241 | return |
|
241 | return | |
242 | if self.shell.logger.log_output: |
|
242 | if self.shell.logger.log_output: | |
243 | self.shell.logger.log_write(format_dict['text/plain'], 'output') |
|
243 | self.shell.logger.log_write(format_dict['text/plain'], 'output') | |
244 | self.shell.history_manager.output_hist_reprs[self.prompt_count] = \ |
|
244 | self.shell.history_manager.output_hist_reprs[self.prompt_count] = \ | |
245 | format_dict['text/plain'] |
|
245 | format_dict['text/plain'] | |
246 |
|
246 | |||
247 | def finish_displayhook(self): |
|
247 | def finish_displayhook(self): | |
248 | """Finish up all displayhook activities.""" |
|
248 | """Finish up all displayhook activities.""" | |
249 | sys.stdout.write(self.shell.separate_out2) |
|
249 | sys.stdout.write(self.shell.separate_out2) | |
250 | sys.stdout.flush() |
|
250 | sys.stdout.flush() | |
251 |
|
251 | |||
252 | def __call__(self, result=None): |
|
252 | def __call__(self, result=None): | |
253 | """Printing with history cache management. |
|
253 | """Printing with history cache management. | |
254 |
|
254 | |||
255 | This is invoked every time the interpreter needs to print, and is |
|
255 | This is invoked every time the interpreter needs to print, and is | |
256 | activated by setting the variable sys.displayhook to it. |
|
256 | activated by setting the variable sys.displayhook to it. | |
257 | """ |
|
257 | """ | |
258 | self.check_for_underscore() |
|
258 | self.check_for_underscore() | |
259 | if result is not None and not self.quiet(): |
|
259 | if result is not None and not self.quiet(): | |
260 | self.start_displayhook() |
|
260 | self.start_displayhook() | |
261 | self.write_output_prompt() |
|
261 | self.write_output_prompt() | |
262 | format_dict, md_dict = self.compute_format_data(result) |
|
262 | format_dict, md_dict = self.compute_format_data(result) | |
263 | self.update_user_ns(result) |
|
263 | self.update_user_ns(result) | |
264 | self.fill_exec_result(result) |
|
264 | self.fill_exec_result(result) | |
265 | if format_dict: |
|
265 | if format_dict: | |
266 | self.write_format_data(format_dict, md_dict) |
|
266 | self.write_format_data(format_dict, md_dict) | |
267 | self.log_output(format_dict) |
|
267 | self.log_output(format_dict) | |
268 | self.finish_displayhook() |
|
268 | self.finish_displayhook() | |
269 |
|
269 | |||
270 | def cull_cache(self): |
|
270 | def cull_cache(self): | |
271 | """Output cache is full, cull the oldest entries""" |
|
271 | """Output cache is full, cull the oldest entries""" | |
272 | oh = self.shell.user_ns.get('_oh', {}) |
|
272 | oh = self.shell.user_ns.get('_oh', {}) | |
273 | sz = len(oh) |
|
273 | sz = len(oh) | |
274 | cull_count = max(int(sz * self.cull_fraction), 2) |
|
274 | cull_count = max(int(sz * self.cull_fraction), 2) | |
275 | warn('Output cache limit (currently {sz} entries) hit.\n' |
|
275 | warn('Output cache limit (currently {sz} entries) hit.\n' | |
276 | 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count)) |
|
276 | 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count)) | |
277 |
|
277 | |||
278 | for i, n in enumerate(sorted(oh)): |
|
278 | for i, n in enumerate(sorted(oh)): | |
279 | if i >= cull_count: |
|
279 | if i >= cull_count: | |
280 | break |
|
280 | break | |
281 | self.shell.user_ns.pop('_%i' % n, None) |
|
281 | self.shell.user_ns.pop('_%i' % n, None) | |
282 | oh.pop(n, None) |
|
282 | oh.pop(n, None) | |
283 |
|
283 | |||
284 |
|
284 | |||
285 | def flush(self): |
|
285 | def flush(self): | |
286 | if not self.do_full_cache: |
|
286 | if not self.do_full_cache: | |
287 | raise ValueError("You shouldn't have reached the cache flush " |
|
287 | raise ValueError("You shouldn't have reached the cache flush " | |
288 | "if full caching is not enabled!") |
|
288 | "if full caching is not enabled!") | |
289 | # delete auto-generated vars from global namespace |
|
289 | # delete auto-generated vars from global namespace | |
290 |
|
290 | |||
291 | for n in range(1,self.prompt_count + 1): |
|
291 | for n in range(1,self.prompt_count + 1): | |
292 | key = '_'+repr(n) |
|
292 | key = '_'+repr(n) | |
293 | try: |
|
293 | try: | |
294 | del self.shell.user_ns[key] |
|
294 | del self.shell.user_ns[key] | |
295 | except: pass |
|
295 | except: pass | |
296 | # In some embedded circumstances, the user_ns doesn't have the |
|
296 | # In some embedded circumstances, the user_ns doesn't have the | |
297 | # '_oh' key set up. |
|
297 | # '_oh' key set up. | |
298 | oh = self.shell.user_ns.get('_oh', None) |
|
298 | oh = self.shell.user_ns.get('_oh', None) | |
299 | if oh is not None: |
|
299 | if oh is not None: | |
300 | oh.clear() |
|
300 | oh.clear() | |
301 |
|
301 | |||
302 | # Release our own references to objects: |
|
302 | # Release our own references to objects: | |
303 | self._, self.__, self.___ = '', '', '' |
|
303 | self._, self.__, self.___ = '', '', '' | |
304 |
|
304 | |||
305 | if '_' not in builtin_mod.__dict__: |
|
305 | if '_' not in builtin_mod.__dict__: | |
306 | self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___}) |
|
306 | self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___}) | |
307 | import gc |
|
307 | import gc | |
308 | # TODO: Is this really needed? |
|
308 | # TODO: Is this really needed? | |
309 | # IronPython blocks here forever |
|
309 | # IronPython blocks here forever | |
310 | if sys.platform != "cli": |
|
310 | if sys.platform != "cli": | |
311 | gc.collect() |
|
311 | gc.collect() | |
312 |
|
312 | |||
313 |
|
313 | |||
314 | class CapturingDisplayHook(object): |
|
314 | class CapturingDisplayHook(object): | |
315 | def __init__(self, shell, outputs=None): |
|
315 | def __init__(self, shell, outputs=None): | |
316 | self.shell = shell |
|
316 | self.shell = shell | |
317 | if outputs is None: |
|
317 | if outputs is None: | |
318 | outputs = [] |
|
318 | outputs = [] | |
319 | self.outputs = outputs |
|
319 | self.outputs = outputs | |
320 |
|
320 | |||
321 | def __call__(self, result=None): |
|
321 | def __call__(self, result=None): | |
322 | if result is None: |
|
322 | if result is None: | |
323 | return |
|
323 | return | |
324 | format_dict, md_dict = self.shell.display_formatter.format(result) |
|
324 | format_dict, md_dict = self.shell.display_formatter.format(result) | |
325 | self.outputs.append({ 'data': format_dict, 'metadata': md_dict }) |
|
325 | self.outputs.append({ 'data': format_dict, 'metadata': md_dict }) |
@@ -1,138 +1,138 b'' | |||||
1 | """An interface for publishing rich data to frontends. |
|
1 | """An interface for publishing rich data to frontends. | |
2 |
|
2 | |||
3 | There are two components of the display system: |
|
3 | There are two components of the display system: | |
4 |
|
4 | |||
5 | * Display formatters, which take a Python object and compute the |
|
5 | * Display formatters, which take a Python object and compute the | |
6 | representation of the object in various formats (text, HTML, SVG, etc.). |
|
6 | representation of the object in various formats (text, HTML, SVG, etc.). | |
7 | * The display publisher that is used to send the representation data to the |
|
7 | * The display publisher that is used to send the representation data to the | |
8 | various frontends. |
|
8 | various frontends. | |
9 |
|
9 | |||
10 | This module defines the logic display publishing. The display publisher uses |
|
10 | This module defines the logic display publishing. The display publisher uses | |
11 | the ``display_data`` message type that is defined in the IPython messaging |
|
11 | the ``display_data`` message type that is defined in the IPython messaging | |
12 | spec. |
|
12 | spec. | |
13 | """ |
|
13 | """ | |
14 |
|
14 | |||
15 | # Copyright (c) IPython Development Team. |
|
15 | # Copyright (c) IPython Development Team. | |
16 | # Distributed under the terms of the Modified BSD License. |
|
16 | # Distributed under the terms of the Modified BSD License. | |
17 |
|
17 | |||
18 |
|
18 | |||
19 | import sys |
|
19 | import sys | |
20 |
|
20 | |||
21 | from traitlets.config.configurable import Configurable |
|
21 | from traitlets.config.configurable import Configurable | |
22 | from traitlets import List |
|
22 | from traitlets import List | |
23 |
|
23 | |||
24 | # This used to be defined here - it is imported for backwards compatibility |
|
24 | # This used to be defined here - it is imported for backwards compatibility | |
25 | from .display_functions import publish_display_data |
|
25 | from .display_functions import publish_display_data | |
26 |
|
26 | |||
27 | #----------------------------------------------------------------------------- |
|
27 | #----------------------------------------------------------------------------- | |
28 | # Main payload class |
|
28 | # Main payload class | |
29 | #----------------------------------------------------------------------------- |
|
29 | #----------------------------------------------------------------------------- | |
30 |
|
30 | |||
31 |
|
31 | |||
32 | class DisplayPublisher(Configurable): |
|
32 | class DisplayPublisher(Configurable): | |
33 | """A traited class that publishes display data to frontends. |
|
33 | """A traited class that publishes display data to frontends. | |
34 |
|
34 | |||
35 | Instances of this class are created by the main IPython object and should |
|
35 | Instances of this class are created by the main IPython object and should | |
36 | be accessed there. |
|
36 | be accessed there. | |
37 | """ |
|
37 | """ | |
38 |
|
38 | |||
39 | def __init__(self, shell=None, *args, **kwargs): |
|
39 | def __init__(self, shell=None, *args, **kwargs): | |
40 | self.shell = shell |
|
40 | self.shell = shell | |
41 | super().__init__(*args, **kwargs) |
|
41 | super().__init__(*args, **kwargs) | |
42 |
|
42 | |||
43 | def _validate_data(self, data, metadata=None): |
|
43 | def _validate_data(self, data, metadata=None): | |
44 | """Validate the display data. |
|
44 | """Validate the display data. | |
45 |
|
45 | |||
46 | Parameters |
|
46 | Parameters | |
47 | ---------- |
|
47 | ---------- | |
48 | data : dict |
|
48 | data : dict | |
49 | The formata data dictionary. |
|
49 | The formata data dictionary. | |
50 | metadata : dict |
|
50 | metadata : dict | |
51 | Any metadata for the data. |
|
51 | Any metadata for the data. | |
52 | """ |
|
52 | """ | |
53 |
|
53 | |||
54 | if not isinstance(data, dict): |
|
54 | if not isinstance(data, dict): | |
55 | raise TypeError('data must be a dict, got: %r' % data) |
|
55 | raise TypeError('data must be a dict, got: %r' % data) | |
56 | if metadata is not None: |
|
56 | if metadata is not None: | |
57 | if not isinstance(metadata, dict): |
|
57 | if not isinstance(metadata, dict): | |
58 | raise TypeError('metadata must be a dict, got: %r' % data) |
|
58 | raise TypeError('metadata must be a dict, got: %r' % data) | |
59 |
|
59 | |||
60 | # use * to indicate transient, update are keyword-only |
|
60 | # use * to indicate transient, update are keyword-only | |
61 | def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None: |
|
61 | def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None: | |
62 | """Publish data and metadata to all frontends. |
|
62 | """Publish data and metadata to all frontends. | |
63 |
|
63 | |||
64 | See the ``display_data`` message in the messaging documentation for |
|
64 | See the ``display_data`` message in the messaging documentation for | |
65 | more details about this message type. |
|
65 | more details about this message type. | |
66 |
|
66 | |||
67 | The following MIME types are currently implemented: |
|
67 | The following MIME types are currently implemented: | |
68 |
|
68 | |||
69 | * text/plain |
|
69 | * text/plain | |
70 | * text/html |
|
70 | * text/html | |
71 | * text/markdown |
|
71 | * text/markdown | |
72 | * text/latex |
|
72 | * text/latex | |
73 | * application/json |
|
73 | * application/json | |
74 | * application/javascript |
|
74 | * application/javascript | |
75 | * image/png |
|
75 | * image/png | |
76 | * image/jpeg |
|
76 | * image/jpeg | |
77 | * image/svg+xml |
|
77 | * image/svg+xml | |
78 |
|
78 | |||
79 | Parameters |
|
79 | Parameters | |
80 | ---------- |
|
80 | ---------- | |
81 | data : dict |
|
81 | data : dict | |
82 | A dictionary having keys that are valid MIME types (like |
|
82 | A dictionary having keys that are valid MIME types (like | |
83 | 'text/plain' or 'image/svg+xml') and values that are the data for |
|
83 | 'text/plain' or 'image/svg+xml') and values that are the data for | |
84 | that MIME type. The data itself must be a JSON'able data |
|
84 | that MIME type. The data itself must be a JSON'able data | |
85 | structure. Minimally all data should have the 'text/plain' data, |
|
85 | structure. Minimally all data should have the 'text/plain' data, | |
86 | which can be displayed by all frontends. If more than the plain |
|
86 | which can be displayed by all frontends. If more than the plain | |
87 | text is given, it is up to the frontend to decide which |
|
87 | text is given, it is up to the frontend to decide which | |
88 | representation to use. |
|
88 | representation to use. | |
89 | metadata : dict |
|
89 | metadata : dict | |
90 | A dictionary for metadata related to the data. This can contain |
|
90 | A dictionary for metadata related to the data. This can contain | |
91 | arbitrary key, value pairs that frontends can use to interpret |
|
91 | arbitrary key, value pairs that frontends can use to interpret | |
92 | the data. Metadata specific to each mime-type can be specified |
|
92 | the data. Metadata specific to each mime-type can be specified | |
93 | in the metadata dict with the same mime-type keys as |
|
93 | in the metadata dict with the same mime-type keys as | |
94 | the data itself. |
|
94 | the data itself. | |
95 | source : str, deprecated |
|
95 | source : str, deprecated | |
96 | Unused. |
|
96 | Unused. | |
97 | transient: dict, keyword-only |
|
97 | transient : dict, keyword-only | |
98 | A dictionary for transient data. |
|
98 | A dictionary for transient data. | |
99 | Data in this dictionary should not be persisted as part of saving this output. |
|
99 | Data in this dictionary should not be persisted as part of saving this output. | |
100 | Examples include 'display_id'. |
|
100 | Examples include 'display_id'. | |
101 | update: bool, keyword-only, default: False |
|
101 | update : bool, keyword-only, default: False | |
102 | If True, only update existing outputs with the same display_id, |
|
102 | If True, only update existing outputs with the same display_id, | |
103 | rather than creating a new output. |
|
103 | rather than creating a new output. | |
104 | """ |
|
104 | """ | |
105 |
|
105 | |||
106 | handlers = {} |
|
106 | handlers = {} | |
107 | if self.shell is not None: |
|
107 | if self.shell is not None: | |
108 | handlers = getattr(self.shell, 'mime_renderers', {}) |
|
108 | handlers = getattr(self.shell, 'mime_renderers', {}) | |
109 |
|
109 | |||
110 | for mime, handler in handlers.items(): |
|
110 | for mime, handler in handlers.items(): | |
111 | if mime in data: |
|
111 | if mime in data: | |
112 | handler(data[mime], metadata.get(mime, None)) |
|
112 | handler(data[mime], metadata.get(mime, None)) | |
113 | return |
|
113 | return | |
114 |
|
114 | |||
115 | if 'text/plain' in data: |
|
115 | if 'text/plain' in data: | |
116 | print(data['text/plain']) |
|
116 | print(data['text/plain']) | |
117 |
|
117 | |||
118 | def clear_output(self, wait=False): |
|
118 | def clear_output(self, wait=False): | |
119 | """Clear the output of the cell receiving output.""" |
|
119 | """Clear the output of the cell receiving output.""" | |
120 | print('\033[2K\r', end='') |
|
120 | print('\033[2K\r', end='') | |
121 | sys.stdout.flush() |
|
121 | sys.stdout.flush() | |
122 | print('\033[2K\r', end='') |
|
122 | print('\033[2K\r', end='') | |
123 | sys.stderr.flush() |
|
123 | sys.stderr.flush() | |
124 |
|
124 | |||
125 |
|
125 | |||
126 | class CapturingDisplayPublisher(DisplayPublisher): |
|
126 | class CapturingDisplayPublisher(DisplayPublisher): | |
127 | """A DisplayPublisher that stores""" |
|
127 | """A DisplayPublisher that stores""" | |
128 | outputs = List() |
|
128 | outputs = List() | |
129 |
|
129 | |||
130 | def publish(self, data, metadata=None, source=None, *, transient=None, update=False): |
|
130 | def publish(self, data, metadata=None, source=None, *, transient=None, update=False): | |
131 | self.outputs.append({'data':data, 'metadata':metadata, |
|
131 | self.outputs.append({'data':data, 'metadata':metadata, | |
132 | 'transient':transient, 'update':update}) |
|
132 | 'transient':transient, 'update':update}) | |
133 |
|
133 | |||
134 | def clear_output(self, wait=False): |
|
134 | def clear_output(self, wait=False): | |
135 | super(CapturingDisplayPublisher, self).clear_output(wait) |
|
135 | super(CapturingDisplayPublisher, self).clear_output(wait) | |
136 |
|
136 | |||
137 | # empty the list, *do not* reassign a new list |
|
137 | # empty the list, *do not* reassign a new list | |
138 | self.outputs.clear() |
|
138 | self.outputs.clear() |
@@ -1,161 +1,161 b'' | |||||
1 | """Infrastructure for registering and firing callbacks on application events. |
|
1 | """Infrastructure for registering and firing callbacks on application events. | |
2 |
|
2 | |||
3 | Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to |
|
3 | Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to | |
4 | be called at specific times, or a collection of alternative methods to try, |
|
4 | be called at specific times, or a collection of alternative methods to try, | |
5 | callbacks are designed to be used by extension authors. A number of callbacks |
|
5 | callbacks are designed to be used by extension authors. A number of callbacks | |
6 | can be registered for the same event without needing to be aware of one another. |
|
6 | can be registered for the same event without needing to be aware of one another. | |
7 |
|
7 | |||
8 | The functions defined in this module are no-ops indicating the names of available |
|
8 | The functions defined in this module are no-ops indicating the names of available | |
9 | events and the arguments which will be passed to them. |
|
9 | events and the arguments which will be passed to them. | |
10 |
|
10 | |||
11 | .. note:: |
|
11 | .. note:: | |
12 |
|
12 | |||
13 | This API is experimental in IPython 2.0, and may be revised in future versions. |
|
13 | This API is experimental in IPython 2.0, and may be revised in future versions. | |
14 | """ |
|
14 | """ | |
15 |
|
15 | |||
16 | from backcall import callback_prototype |
|
16 | from backcall import callback_prototype | |
17 |
|
17 | |||
18 |
|
18 | |||
19 | class EventManager(object): |
|
19 | class EventManager(object): | |
20 | """Manage a collection of events and a sequence of callbacks for each. |
|
20 | """Manage a collection of events and a sequence of callbacks for each. | |
21 |
|
21 | |||
22 | This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell` |
|
22 | This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell` | |
23 | instances as an ``events`` attribute. |
|
23 | instances as an ``events`` attribute. | |
24 |
|
24 | |||
25 | .. note:: |
|
25 | .. note:: | |
26 |
|
26 | |||
27 | This API is experimental in IPython 2.0, and may be revised in future versions. |
|
27 | This API is experimental in IPython 2.0, and may be revised in future versions. | |
28 | """ |
|
28 | """ | |
29 | def __init__(self, shell, available_events): |
|
29 | def __init__(self, shell, available_events): | |
30 | """Initialise the :class:`CallbackManager`. |
|
30 | """Initialise the :class:`CallbackManager`. | |
31 |
|
31 | |||
32 | Parameters |
|
32 | Parameters | |
33 | ---------- |
|
33 | ---------- | |
34 | shell |
|
34 | shell | |
35 | The :class:`~IPython.core.interactiveshell.InteractiveShell` instance |
|
35 | The :class:`~IPython.core.interactiveshell.InteractiveShell` instance | |
36 |
available_ |
|
36 | available_events | |
37 | An iterable of names for callback events. |
|
37 | An iterable of names for callback events. | |
38 | """ |
|
38 | """ | |
39 | self.shell = shell |
|
39 | self.shell = shell | |
40 | self.callbacks = {n:[] for n in available_events} |
|
40 | self.callbacks = {n:[] for n in available_events} | |
41 |
|
41 | |||
42 | def register(self, event, function): |
|
42 | def register(self, event, function): | |
43 | """Register a new event callback. |
|
43 | """Register a new event callback. | |
44 |
|
44 | |||
45 | Parameters |
|
45 | Parameters | |
46 | ---------- |
|
46 | ---------- | |
47 | event : str |
|
47 | event : str | |
48 | The event for which to register this callback. |
|
48 | The event for which to register this callback. | |
49 | function : callable |
|
49 | function : callable | |
50 | A function to be called on the given event. It should take the same |
|
50 | A function to be called on the given event. It should take the same | |
51 | parameters as the appropriate callback prototype. |
|
51 | parameters as the appropriate callback prototype. | |
52 |
|
52 | |||
53 | Raises |
|
53 | Raises | |
54 | ------ |
|
54 | ------ | |
55 | TypeError |
|
55 | TypeError | |
56 | If ``function`` is not callable. |
|
56 | If ``function`` is not callable. | |
57 | KeyError |
|
57 | KeyError | |
58 | If ``event`` is not one of the known events. |
|
58 | If ``event`` is not one of the known events. | |
59 | """ |
|
59 | """ | |
60 | if not callable(function): |
|
60 | if not callable(function): | |
61 | raise TypeError('Need a callable, got %r' % function) |
|
61 | raise TypeError('Need a callable, got %r' % function) | |
62 | callback_proto = available_events.get(event) |
|
62 | callback_proto = available_events.get(event) | |
63 | if function not in self.callbacks[event]: |
|
63 | if function not in self.callbacks[event]: | |
64 | self.callbacks[event].append(callback_proto.adapt(function)) |
|
64 | self.callbacks[event].append(callback_proto.adapt(function)) | |
65 |
|
65 | |||
66 | def unregister(self, event, function): |
|
66 | def unregister(self, event, function): | |
67 | """Remove a callback from the given event.""" |
|
67 | """Remove a callback from the given event.""" | |
68 | if function in self.callbacks[event]: |
|
68 | if function in self.callbacks[event]: | |
69 | return self.callbacks[event].remove(function) |
|
69 | return self.callbacks[event].remove(function) | |
70 |
|
70 | |||
71 | # Remove callback in case ``function`` was adapted by `backcall`. |
|
71 | # Remove callback in case ``function`` was adapted by `backcall`. | |
72 | for callback in self.callbacks[event]: |
|
72 | for callback in self.callbacks[event]: | |
73 | try: |
|
73 | try: | |
74 | if callback.__wrapped__ is function: |
|
74 | if callback.__wrapped__ is function: | |
75 | return self.callbacks[event].remove(callback) |
|
75 | return self.callbacks[event].remove(callback) | |
76 | except AttributeError: |
|
76 | except AttributeError: | |
77 | pass |
|
77 | pass | |
78 |
|
78 | |||
79 | raise ValueError('Function {!r} is not registered as a {} callback'.format(function, event)) |
|
79 | raise ValueError('Function {!r} is not registered as a {} callback'.format(function, event)) | |
80 |
|
80 | |||
81 | def trigger(self, event, *args, **kwargs): |
|
81 | def trigger(self, event, *args, **kwargs): | |
82 | """Call callbacks for ``event``. |
|
82 | """Call callbacks for ``event``. | |
83 |
|
83 | |||
84 | Any additional arguments are passed to all callbacks registered for this |
|
84 | Any additional arguments are passed to all callbacks registered for this | |
85 | event. Exceptions raised by callbacks are caught, and a message printed. |
|
85 | event. Exceptions raised by callbacks are caught, and a message printed. | |
86 | """ |
|
86 | """ | |
87 | for func in self.callbacks[event][:]: |
|
87 | for func in self.callbacks[event][:]: | |
88 | try: |
|
88 | try: | |
89 | func(*args, **kwargs) |
|
89 | func(*args, **kwargs) | |
90 | except (Exception, KeyboardInterrupt): |
|
90 | except (Exception, KeyboardInterrupt): | |
91 | print("Error in callback {} (for {}):".format(func, event)) |
|
91 | print("Error in callback {} (for {}):".format(func, event)) | |
92 | self.shell.showtraceback() |
|
92 | self.shell.showtraceback() | |
93 |
|
93 | |||
94 | # event_name -> prototype mapping |
|
94 | # event_name -> prototype mapping | |
95 | available_events = {} |
|
95 | available_events = {} | |
96 |
|
96 | |||
97 | def _define_event(callback_function): |
|
97 | def _define_event(callback_function): | |
98 | callback_proto = callback_prototype(callback_function) |
|
98 | callback_proto = callback_prototype(callback_function) | |
99 | available_events[callback_function.__name__] = callback_proto |
|
99 | available_events[callback_function.__name__] = callback_proto | |
100 | return callback_proto |
|
100 | return callback_proto | |
101 |
|
101 | |||
102 | # ------------------------------------------------------------------------------ |
|
102 | # ------------------------------------------------------------------------------ | |
103 | # Callback prototypes |
|
103 | # Callback prototypes | |
104 | # |
|
104 | # | |
105 | # No-op functions which describe the names of available events and the |
|
105 | # No-op functions which describe the names of available events and the | |
106 | # signatures of callbacks for those events. |
|
106 | # signatures of callbacks for those events. | |
107 | # ------------------------------------------------------------------------------ |
|
107 | # ------------------------------------------------------------------------------ | |
108 |
|
108 | |||
109 | @_define_event |
|
109 | @_define_event | |
110 | def pre_execute(): |
|
110 | def pre_execute(): | |
111 | """Fires before code is executed in response to user/frontend action. |
|
111 | """Fires before code is executed in response to user/frontend action. | |
112 |
|
112 | |||
113 | This includes comm and widget messages and silent execution, as well as user |
|
113 | This includes comm and widget messages and silent execution, as well as user | |
114 | code cells. |
|
114 | code cells. | |
115 | """ |
|
115 | """ | |
116 | pass |
|
116 | pass | |
117 |
|
117 | |||
118 | @_define_event |
|
118 | @_define_event | |
119 | def pre_run_cell(info): |
|
119 | def pre_run_cell(info): | |
120 | """Fires before user-entered code runs. |
|
120 | """Fires before user-entered code runs. | |
121 |
|
121 | |||
122 | Parameters |
|
122 | Parameters | |
123 | ---------- |
|
123 | ---------- | |
124 | info : :class:`~IPython.core.interactiveshell.ExecutionInfo` |
|
124 | info : :class:`~IPython.core.interactiveshell.ExecutionInfo` | |
125 | An object containing information used for the code execution. |
|
125 | An object containing information used for the code execution. | |
126 | """ |
|
126 | """ | |
127 | pass |
|
127 | pass | |
128 |
|
128 | |||
129 | @_define_event |
|
129 | @_define_event | |
130 | def post_execute(): |
|
130 | def post_execute(): | |
131 | """Fires after code is executed in response to user/frontend action. |
|
131 | """Fires after code is executed in response to user/frontend action. | |
132 |
|
132 | |||
133 | This includes comm and widget messages and silent execution, as well as user |
|
133 | This includes comm and widget messages and silent execution, as well as user | |
134 | code cells. |
|
134 | code cells. | |
135 | """ |
|
135 | """ | |
136 | pass |
|
136 | pass | |
137 |
|
137 | |||
138 | @_define_event |
|
138 | @_define_event | |
139 | def post_run_cell(result): |
|
139 | def post_run_cell(result): | |
140 | """Fires after user-entered code runs. |
|
140 | """Fires after user-entered code runs. | |
141 |
|
141 | |||
142 | Parameters |
|
142 | Parameters | |
143 | ---------- |
|
143 | ---------- | |
144 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` |
|
144 | result : :class:`~IPython.core.interactiveshell.ExecutionResult` | |
145 | The object which will be returned as the execution result. |
|
145 | The object which will be returned as the execution result. | |
146 | """ |
|
146 | """ | |
147 | pass |
|
147 | pass | |
148 |
|
148 | |||
149 | @_define_event |
|
149 | @_define_event | |
150 | def shell_initialized(ip): |
|
150 | def shell_initialized(ip): | |
151 | """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`. |
|
151 | """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`. | |
152 |
|
152 | |||
153 | This is before extensions and startup scripts are loaded, so it can only be |
|
153 | This is before extensions and startup scripts are loaded, so it can only be | |
154 | set by subclassing. |
|
154 | set by subclassing. | |
155 |
|
155 | |||
156 | Parameters |
|
156 | Parameters | |
157 | ---------- |
|
157 | ---------- | |
158 | ip : :class:`~IPython.core.interactiveshell.InteractiveShell` |
|
158 | ip : :class:`~IPython.core.interactiveshell.InteractiveShell` | |
159 | The newly initialised shell. |
|
159 | The newly initialised shell. | |
160 | """ |
|
160 | """ | |
161 | pass |
|
161 | pass |
@@ -1,150 +1,150 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """A class for managing IPython extensions.""" |
|
2 | """A class for managing IPython extensions.""" | |
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 | import os |
|
7 | import os | |
8 | import os.path |
|
8 | import os.path | |
9 | import sys |
|
9 | import sys | |
10 | from importlib import import_module, reload |
|
10 | from importlib import import_module, reload | |
11 |
|
11 | |||
12 | from traitlets.config.configurable import Configurable |
|
12 | from traitlets.config.configurable import Configurable | |
13 | from IPython.utils.path import ensure_dir_exists, compress_user |
|
13 | from IPython.utils.path import ensure_dir_exists, compress_user | |
14 | from IPython.utils.decorators import undoc |
|
14 | from IPython.utils.decorators import undoc | |
15 | from traitlets import Instance |
|
15 | from traitlets import Instance | |
16 |
|
16 | |||
17 |
|
17 | |||
18 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
19 | # Main class |
|
19 | # Main class | |
20 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
21 |
|
21 | |||
22 | class ExtensionManager(Configurable): |
|
22 | class ExtensionManager(Configurable): | |
23 | """A class to manage IPython extensions. |
|
23 | """A class to manage IPython extensions. | |
24 |
|
24 | |||
25 | An IPython extension is an importable Python module that has |
|
25 | An IPython extension is an importable Python module that has | |
26 | a function with the signature:: |
|
26 | a function with the signature:: | |
27 |
|
27 | |||
28 | def load_ipython_extension(ipython): |
|
28 | def load_ipython_extension(ipython): | |
29 | # Do things with ipython |
|
29 | # Do things with ipython | |
30 |
|
30 | |||
31 | This function is called after your extension is imported and the |
|
31 | This function is called after your extension is imported and the | |
32 | currently active :class:`InteractiveShell` instance is passed as |
|
32 | currently active :class:`InteractiveShell` instance is passed as | |
33 | the only argument. You can do anything you want with IPython at |
|
33 | the only argument. You can do anything you want with IPython at | |
34 | that point, including defining new magic and aliases, adding new |
|
34 | that point, including defining new magic and aliases, adding new | |
35 | components, etc. |
|
35 | components, etc. | |
36 |
|
36 | |||
37 | You can also optionally define an :func:`unload_ipython_extension(ipython)` |
|
37 | You can also optionally define an :func:`unload_ipython_extension(ipython)` | |
38 | function, which will be called if the user unloads or reloads the extension. |
|
38 | function, which will be called if the user unloads or reloads the extension. | |
39 | The extension manager will only call :func:`load_ipython_extension` again |
|
39 | The extension manager will only call :func:`load_ipython_extension` again | |
40 | if the extension is reloaded. |
|
40 | if the extension is reloaded. | |
41 |
|
41 | |||
42 | You can put your extension modules anywhere you want, as long as |
|
42 | You can put your extension modules anywhere you want, as long as | |
43 | they can be imported by Python's standard import mechanism. However, |
|
43 | they can be imported by Python's standard import mechanism. However, | |
44 | to make it easy to write extensions, you can also put your extensions |
|
44 | to make it easy to write extensions, you can also put your extensions | |
45 | in ``os.path.join(self.ipython_dir, 'extensions')``. This directory |
|
45 | in ``os.path.join(self.ipython_dir, 'extensions')``. This directory | |
46 | is added to ``sys.path`` automatically. |
|
46 | is added to ``sys.path`` automatically. | |
47 | """ |
|
47 | """ | |
48 |
|
48 | |||
49 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) |
|
49 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) | |
50 |
|
50 | |||
51 | def __init__(self, shell=None, **kwargs): |
|
51 | def __init__(self, shell=None, **kwargs): | |
52 | super(ExtensionManager, self).__init__(shell=shell, **kwargs) |
|
52 | super(ExtensionManager, self).__init__(shell=shell, **kwargs) | |
53 | self.shell.observe( |
|
53 | self.shell.observe( | |
54 | self._on_ipython_dir_changed, names=('ipython_dir',) |
|
54 | self._on_ipython_dir_changed, names=('ipython_dir',) | |
55 | ) |
|
55 | ) | |
56 | self.loaded = set() |
|
56 | self.loaded = set() | |
57 |
|
57 | |||
58 | @property |
|
58 | @property | |
59 | def ipython_extension_dir(self): |
|
59 | def ipython_extension_dir(self): | |
60 | return os.path.join(self.shell.ipython_dir, u'extensions') |
|
60 | return os.path.join(self.shell.ipython_dir, u'extensions') | |
61 |
|
61 | |||
62 | def _on_ipython_dir_changed(self, change): |
|
62 | def _on_ipython_dir_changed(self, change): | |
63 | ensure_dir_exists(self.ipython_extension_dir) |
|
63 | ensure_dir_exists(self.ipython_extension_dir) | |
64 |
|
64 | |||
65 | def load_extension(self, module_str): |
|
65 | def load_extension(self, module_str): | |
66 | """Load an IPython extension by its module name. |
|
66 | """Load an IPython extension by its module name. | |
67 |
|
67 | |||
68 | Returns the string "already loaded" if the extension is already loaded, |
|
68 | Returns the string "already loaded" if the extension is already loaded, | |
69 | "no load function" if the module doesn't have a load_ipython_extension |
|
69 | "no load function" if the module doesn't have a load_ipython_extension | |
70 | function, or None if it succeeded. |
|
70 | function, or None if it succeeded. | |
71 | """ |
|
71 | """ | |
72 | if module_str in self.loaded: |
|
72 | if module_str in self.loaded: | |
73 | return "already loaded" |
|
73 | return "already loaded" | |
74 |
|
74 | |||
75 | from IPython.utils.syspathcontext import prepended_to_syspath |
|
75 | from IPython.utils.syspathcontext import prepended_to_syspath | |
76 |
|
76 | |||
77 | with self.shell.builtin_trap: |
|
77 | with self.shell.builtin_trap: | |
78 | if module_str not in sys.modules: |
|
78 | if module_str not in sys.modules: | |
79 | with prepended_to_syspath(self.ipython_extension_dir): |
|
79 | with prepended_to_syspath(self.ipython_extension_dir): | |
80 | mod = import_module(module_str) |
|
80 | mod = import_module(module_str) | |
81 | if mod.__file__.startswith(self.ipython_extension_dir): |
|
81 | if mod.__file__.startswith(self.ipython_extension_dir): | |
82 | print(("Loading extensions from {dir} is deprecated. " |
|
82 | print(("Loading extensions from {dir} is deprecated. " | |
83 | "We recommend managing extensions like any " |
|
83 | "We recommend managing extensions like any " | |
84 | "other Python packages, in site-packages.").format( |
|
84 | "other Python packages, in site-packages.").format( | |
85 | dir=compress_user(self.ipython_extension_dir))) |
|
85 | dir=compress_user(self.ipython_extension_dir))) | |
86 | mod = sys.modules[module_str] |
|
86 | mod = sys.modules[module_str] | |
87 | if self._call_load_ipython_extension(mod): |
|
87 | if self._call_load_ipython_extension(mod): | |
88 | self.loaded.add(module_str) |
|
88 | self.loaded.add(module_str) | |
89 | else: |
|
89 | else: | |
90 | return "no load function" |
|
90 | return "no load function" | |
91 |
|
91 | |||
92 | def unload_extension(self, module_str): |
|
92 | def unload_extension(self, module_str): | |
93 | """Unload an IPython extension by its module name. |
|
93 | """Unload an IPython extension by its module name. | |
94 |
|
94 | |||
95 | This function looks up the extension's name in ``sys.modules`` and |
|
95 | This function looks up the extension's name in ``sys.modules`` and | |
96 | simply calls ``mod.unload_ipython_extension(self)``. |
|
96 | simply calls ``mod.unload_ipython_extension(self)``. | |
97 |
|
97 | |||
98 | Returns the string "no unload function" if the extension doesn't define |
|
98 | Returns the string "no unload function" if the extension doesn't define | |
99 | a function to unload itself, "not loaded" if the extension isn't loaded, |
|
99 | a function to unload itself, "not loaded" if the extension isn't loaded, | |
100 | otherwise None. |
|
100 | otherwise None. | |
101 | """ |
|
101 | """ | |
102 | if module_str not in self.loaded: |
|
102 | if module_str not in self.loaded: | |
103 | return "not loaded" |
|
103 | return "not loaded" | |
104 |
|
104 | |||
105 | if module_str in sys.modules: |
|
105 | if module_str in sys.modules: | |
106 | mod = sys.modules[module_str] |
|
106 | mod = sys.modules[module_str] | |
107 | if self._call_unload_ipython_extension(mod): |
|
107 | if self._call_unload_ipython_extension(mod): | |
108 | self.loaded.discard(module_str) |
|
108 | self.loaded.discard(module_str) | |
109 | else: |
|
109 | else: | |
110 | return "no unload function" |
|
110 | return "no unload function" | |
111 |
|
111 | |||
112 | def reload_extension(self, module_str): |
|
112 | def reload_extension(self, module_str): | |
113 | """Reload an IPython extension by calling reload. |
|
113 | """Reload an IPython extension by calling reload. | |
114 |
|
114 | |||
115 | If the module has not been loaded before, |
|
115 | If the module has not been loaded before, | |
116 | :meth:`InteractiveShell.load_extension` is called. Otherwise |
|
116 | :meth:`InteractiveShell.load_extension` is called. Otherwise | |
117 | :func:`reload` is called and then the :func:`load_ipython_extension` |
|
117 | :func:`reload` is called and then the :func:`load_ipython_extension` | |
118 | function of the module, if it exists is called. |
|
118 | function of the module, if it exists is called. | |
119 | """ |
|
119 | """ | |
120 | from IPython.utils.syspathcontext import prepended_to_syspath |
|
120 | from IPython.utils.syspathcontext import prepended_to_syspath | |
121 |
|
121 | |||
122 | if (module_str in self.loaded) and (module_str in sys.modules): |
|
122 | if (module_str in self.loaded) and (module_str in sys.modules): | |
123 | self.unload_extension(module_str) |
|
123 | self.unload_extension(module_str) | |
124 | mod = sys.modules[module_str] |
|
124 | mod = sys.modules[module_str] | |
125 | with prepended_to_syspath(self.ipython_extension_dir): |
|
125 | with prepended_to_syspath(self.ipython_extension_dir): | |
126 | reload(mod) |
|
126 | reload(mod) | |
127 | if self._call_load_ipython_extension(mod): |
|
127 | if self._call_load_ipython_extension(mod): | |
128 | self.loaded.add(module_str) |
|
128 | self.loaded.add(module_str) | |
129 | else: |
|
129 | else: | |
130 | self.load_extension(module_str) |
|
130 | self.load_extension(module_str) | |
131 |
|
131 | |||
132 | def _call_load_ipython_extension(self, mod): |
|
132 | def _call_load_ipython_extension(self, mod): | |
133 | if hasattr(mod, 'load_ipython_extension'): |
|
133 | if hasattr(mod, 'load_ipython_extension'): | |
134 | mod.load_ipython_extension(self.shell) |
|
134 | mod.load_ipython_extension(self.shell) | |
135 | return True |
|
135 | return True | |
136 |
|
136 | |||
137 | def _call_unload_ipython_extension(self, mod): |
|
137 | def _call_unload_ipython_extension(self, mod): | |
138 | if hasattr(mod, 'unload_ipython_extension'): |
|
138 | if hasattr(mod, 'unload_ipython_extension'): | |
139 | mod.unload_ipython_extension(self.shell) |
|
139 | mod.unload_ipython_extension(self.shell) | |
140 | return True |
|
140 | return True | |
141 |
|
141 | |||
142 | @undoc |
|
142 | @undoc | |
143 | def install_extension(self, url, filename=None): |
|
143 | def install_extension(self, url, filename=None): | |
144 | """ |
|
144 | """ | |
145 | Deprecated. |
|
145 | Deprecated. | |
146 | """ |
|
146 | """ | |
147 | # Ensure the extension directory exists |
|
147 | # Ensure the extension directory exists | |
148 | raise DeprecationWarning( |
|
148 | raise DeprecationWarning( | |
149 | '`install_extension` and the `install_ext` magic have been deprecated since IPython 4.0' |
|
149 | '`install_extension` and the `install_ext` magic have been deprecated since IPython 4.0' | |
150 | 'Use pip or other package managers to manage ipython extensions.') |
|
150 | 'Use pip or other package managers to manage ipython extensions.') |
@@ -1,1024 +1,1026 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 | """Display formatters. |
|
2 | """Display formatters. | |
3 |
|
3 | |||
4 | Inheritance diagram: |
|
4 | Inheritance diagram: | |
5 |
|
5 | |||
6 | .. inheritance-diagram:: IPython.core.formatters |
|
6 | .. inheritance-diagram:: IPython.core.formatters | |
7 | :parts: 3 |
|
7 | :parts: 3 | |
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 | import abc |
|
13 | import abc | |
14 | import json |
|
14 | import json | |
15 | import sys |
|
15 | import sys | |
16 | import traceback |
|
16 | import traceback | |
17 | import warnings |
|
17 | import warnings | |
18 | from io import StringIO |
|
18 | from io import StringIO | |
19 |
|
19 | |||
20 | from decorator import decorator |
|
20 | from decorator import decorator | |
21 |
|
21 | |||
22 | from traitlets.config.configurable import Configurable |
|
22 | from traitlets.config.configurable import Configurable | |
23 | from .getipython import get_ipython |
|
23 | from .getipython import get_ipython | |
24 | from ..utils.sentinel import Sentinel |
|
24 | from ..utils.sentinel import Sentinel | |
25 | from ..utils.dir2 import get_real_method |
|
25 | from ..utils.dir2 import get_real_method | |
26 | from ..lib import pretty |
|
26 | from ..lib import pretty | |
27 | from traitlets import ( |
|
27 | from traitlets import ( | |
28 | Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List, |
|
28 | Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List, | |
29 | ForwardDeclaredInstance, |
|
29 | ForwardDeclaredInstance, | |
30 | default, observe, |
|
30 | default, observe, | |
31 | ) |
|
31 | ) | |
32 |
|
32 | |||
33 |
|
33 | |||
34 | class DisplayFormatter(Configurable): |
|
34 | class DisplayFormatter(Configurable): | |
35 |
|
35 | |||
36 | active_types = List(Unicode(), |
|
36 | active_types = List(Unicode(), | |
37 | help="""List of currently active mime-types to display. |
|
37 | help="""List of currently active mime-types to display. | |
38 | You can use this to set a white-list for formats to display. |
|
38 | You can use this to set a white-list for formats to display. | |
39 |
|
39 | |||
40 | Most users will not need to change this value. |
|
40 | Most users will not need to change this value. | |
41 | """).tag(config=True) |
|
41 | """).tag(config=True) | |
42 |
|
42 | |||
43 | @default('active_types') |
|
43 | @default('active_types') | |
44 | def _active_types_default(self): |
|
44 | def _active_types_default(self): | |
45 | return self.format_types |
|
45 | return self.format_types | |
46 |
|
46 | |||
47 | @observe('active_types') |
|
47 | @observe('active_types') | |
48 | def _active_types_changed(self, change): |
|
48 | def _active_types_changed(self, change): | |
49 | for key, formatter in self.formatters.items(): |
|
49 | for key, formatter in self.formatters.items(): | |
50 | if key in change['new']: |
|
50 | if key in change['new']: | |
51 | formatter.enabled = True |
|
51 | formatter.enabled = True | |
52 | else: |
|
52 | else: | |
53 | formatter.enabled = False |
|
53 | formatter.enabled = False | |
54 |
|
54 | |||
55 | ipython_display_formatter = ForwardDeclaredInstance('FormatterABC') |
|
55 | ipython_display_formatter = ForwardDeclaredInstance('FormatterABC') | |
56 | @default('ipython_display_formatter') |
|
56 | @default('ipython_display_formatter') | |
57 | def _default_formatter(self): |
|
57 | def _default_formatter(self): | |
58 | return IPythonDisplayFormatter(parent=self) |
|
58 | return IPythonDisplayFormatter(parent=self) | |
59 |
|
59 | |||
60 | mimebundle_formatter = ForwardDeclaredInstance('FormatterABC') |
|
60 | mimebundle_formatter = ForwardDeclaredInstance('FormatterABC') | |
61 | @default('mimebundle_formatter') |
|
61 | @default('mimebundle_formatter') | |
62 | def _default_mime_formatter(self): |
|
62 | def _default_mime_formatter(self): | |
63 | return MimeBundleFormatter(parent=self) |
|
63 | return MimeBundleFormatter(parent=self) | |
64 |
|
64 | |||
65 | # A dict of formatter whose keys are format types (MIME types) and whose |
|
65 | # A dict of formatter whose keys are format types (MIME types) and whose | |
66 | # values are subclasses of BaseFormatter. |
|
66 | # values are subclasses of BaseFormatter. | |
67 | formatters = Dict() |
|
67 | formatters = Dict() | |
68 | @default('formatters') |
|
68 | @default('formatters') | |
69 | def _formatters_default(self): |
|
69 | def _formatters_default(self): | |
70 | """Activate the default formatters.""" |
|
70 | """Activate the default formatters.""" | |
71 | formatter_classes = [ |
|
71 | formatter_classes = [ | |
72 | PlainTextFormatter, |
|
72 | PlainTextFormatter, | |
73 | HTMLFormatter, |
|
73 | HTMLFormatter, | |
74 | MarkdownFormatter, |
|
74 | MarkdownFormatter, | |
75 | SVGFormatter, |
|
75 | SVGFormatter, | |
76 | PNGFormatter, |
|
76 | PNGFormatter, | |
77 | PDFFormatter, |
|
77 | PDFFormatter, | |
78 | JPEGFormatter, |
|
78 | JPEGFormatter, | |
79 | LatexFormatter, |
|
79 | LatexFormatter, | |
80 | JSONFormatter, |
|
80 | JSONFormatter, | |
81 | JavascriptFormatter |
|
81 | JavascriptFormatter | |
82 | ] |
|
82 | ] | |
83 | d = {} |
|
83 | d = {} | |
84 | for cls in formatter_classes: |
|
84 | for cls in formatter_classes: | |
85 | f = cls(parent=self) |
|
85 | f = cls(parent=self) | |
86 | d[f.format_type] = f |
|
86 | d[f.format_type] = f | |
87 | return d |
|
87 | return d | |
88 |
|
88 | |||
89 | def format(self, obj, include=None, exclude=None): |
|
89 | def format(self, obj, include=None, exclude=None): | |
90 | """Return a format data dict for an object. |
|
90 | """Return a format data dict for an object. | |
91 |
|
91 | |||
92 | By default all format types will be computed. |
|
92 | By default all format types will be computed. | |
93 |
|
93 | |||
94 | The following MIME types are usually implemented: |
|
94 | The following MIME types are usually implemented: | |
95 |
|
95 | |||
96 | * text/plain |
|
96 | * text/plain | |
97 | * text/html |
|
97 | * text/html | |
98 | * text/markdown |
|
98 | * text/markdown | |
99 | * text/latex |
|
99 | * text/latex | |
100 | * application/json |
|
100 | * application/json | |
101 | * application/javascript |
|
101 | * application/javascript | |
102 | * application/pdf |
|
102 | * application/pdf | |
103 | * image/png |
|
103 | * image/png | |
104 | * image/jpeg |
|
104 | * image/jpeg | |
105 | * image/svg+xml |
|
105 | * image/svg+xml | |
106 |
|
106 | |||
107 | Parameters |
|
107 | Parameters | |
108 | ---------- |
|
108 | ---------- | |
109 | obj : object |
|
109 | obj : object | |
110 | The Python object whose format data will be computed. |
|
110 | The Python object whose format data will be computed. | |
111 | include : list, tuple or set; optional |
|
111 | include : list, tuple or set; optional | |
112 | A list of format type strings (MIME types) to include in the |
|
112 | A list of format type strings (MIME types) to include in the | |
113 | format data dict. If this is set *only* the format types included |
|
113 | format data dict. If this is set *only* the format types included | |
114 | in this list will be computed. |
|
114 | in this list will be computed. | |
115 | exclude : list, tuple or set; optional |
|
115 | exclude : list, tuple or set; optional | |
116 | A list of format type string (MIME types) to exclude in the format |
|
116 | A list of format type string (MIME types) to exclude in the format | |
117 | data dict. If this is set all format types will be computed, |
|
117 | data dict. If this is set all format types will be computed, | |
118 | except for those included in this argument. |
|
118 | except for those included in this argument. | |
119 | Mimetypes present in exclude will take precedence over the ones in include |
|
119 | Mimetypes present in exclude will take precedence over the ones in include | |
120 |
|
120 | |||
121 | Returns |
|
121 | Returns | |
122 | ------- |
|
122 | ------- | |
123 | (format_dict, metadata_dict) : tuple of two dicts |
|
123 | (format_dict, metadata_dict) : tuple of two dicts | |
124 |
|
||||
125 | format_dict is a dictionary of key/value pairs, one of each format that was |
|
124 | format_dict is a dictionary of key/value pairs, one of each format that was | |
126 | generated for the object. The keys are the format types, which |
|
125 | generated for the object. The keys are the format types, which | |
127 | will usually be MIME type strings and the values and JSON'able |
|
126 | will usually be MIME type strings and the values and JSON'able | |
128 | data structure containing the raw data for the representation in |
|
127 | data structure containing the raw data for the representation in | |
129 | that format. |
|
128 | that format. | |
130 |
|
129 | |||
131 | metadata_dict is a dictionary of metadata about each mime-type output. |
|
130 | metadata_dict is a dictionary of metadata about each mime-type output. | |
132 | Its keys will be a strict subset of the keys in format_dict. |
|
131 | Its keys will be a strict subset of the keys in format_dict. | |
133 |
|
132 | |||
134 | Notes |
|
133 | Notes | |
135 | ----- |
|
134 | ----- | |
136 |
|
||||
137 | If an object implement `_repr_mimebundle_` as well as various |
|
135 | If an object implement `_repr_mimebundle_` as well as various | |
138 | `_repr_*_`, the data returned by `_repr_mimebundle_` will take |
|
136 | `_repr_*_`, the data returned by `_repr_mimebundle_` will take | |
139 | precedence and the corresponding `_repr_*_` for this mimetype will |
|
137 | precedence and the corresponding `_repr_*_` for this mimetype will | |
140 | not be called. |
|
138 | not be called. | |
141 |
|
139 | |||
142 | """ |
|
140 | """ | |
143 | format_dict = {} |
|
141 | format_dict = {} | |
144 | md_dict = {} |
|
142 | md_dict = {} | |
145 |
|
143 | |||
146 | if self.ipython_display_formatter(obj): |
|
144 | if self.ipython_display_formatter(obj): | |
147 | # object handled itself, don't proceed |
|
145 | # object handled itself, don't proceed | |
148 | return {}, {} |
|
146 | return {}, {} | |
149 |
|
147 | |||
150 | format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude) |
|
148 | format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude) | |
151 |
|
149 | |||
152 | if format_dict or md_dict: |
|
150 | if format_dict or md_dict: | |
153 | if include: |
|
151 | if include: | |
154 | format_dict = {k:v for k,v in format_dict.items() if k in include} |
|
152 | format_dict = {k:v for k,v in format_dict.items() if k in include} | |
155 | md_dict = {k:v for k,v in md_dict.items() if k in include} |
|
153 | md_dict = {k:v for k,v in md_dict.items() if k in include} | |
156 | if exclude: |
|
154 | if exclude: | |
157 | format_dict = {k:v for k,v in format_dict.items() if k not in exclude} |
|
155 | format_dict = {k:v for k,v in format_dict.items() if k not in exclude} | |
158 | md_dict = {k:v for k,v in md_dict.items() if k not in exclude} |
|
156 | md_dict = {k:v for k,v in md_dict.items() if k not in exclude} | |
159 |
|
157 | |||
160 | for format_type, formatter in self.formatters.items(): |
|
158 | for format_type, formatter in self.formatters.items(): | |
161 | if format_type in format_dict: |
|
159 | if format_type in format_dict: | |
162 | # already got it from mimebundle, maybe don't render again. |
|
160 | # already got it from mimebundle, maybe don't render again. | |
163 | # exception: manually registered per-mime renderer |
|
161 | # exception: manually registered per-mime renderer | |
164 | # check priority: |
|
162 | # check priority: | |
165 | # 1. user-registered per-mime formatter |
|
163 | # 1. user-registered per-mime formatter | |
166 | # 2. mime-bundle (user-registered or repr method) |
|
164 | # 2. mime-bundle (user-registered or repr method) | |
167 | # 3. default per-mime formatter (e.g. repr method) |
|
165 | # 3. default per-mime formatter (e.g. repr method) | |
168 | try: |
|
166 | try: | |
169 | formatter.lookup(obj) |
|
167 | formatter.lookup(obj) | |
170 | except KeyError: |
|
168 | except KeyError: | |
171 | # no special formatter, use mime-bundle-provided value |
|
169 | # no special formatter, use mime-bundle-provided value | |
172 | continue |
|
170 | continue | |
173 | if include and format_type not in include: |
|
171 | if include and format_type not in include: | |
174 | continue |
|
172 | continue | |
175 | if exclude and format_type in exclude: |
|
173 | if exclude and format_type in exclude: | |
176 | continue |
|
174 | continue | |
177 |
|
175 | |||
178 | md = None |
|
176 | md = None | |
179 | try: |
|
177 | try: | |
180 | data = formatter(obj) |
|
178 | data = formatter(obj) | |
181 | except: |
|
179 | except: | |
182 | # FIXME: log the exception |
|
180 | # FIXME: log the exception | |
183 | raise |
|
181 | raise | |
184 |
|
182 | |||
185 | # formatters can return raw data or (data, metadata) |
|
183 | # formatters can return raw data or (data, metadata) | |
186 | if isinstance(data, tuple) and len(data) == 2: |
|
184 | if isinstance(data, tuple) and len(data) == 2: | |
187 | data, md = data |
|
185 | data, md = data | |
188 |
|
186 | |||
189 | if data is not None: |
|
187 | if data is not None: | |
190 | format_dict[format_type] = data |
|
188 | format_dict[format_type] = data | |
191 | if md is not None: |
|
189 | if md is not None: | |
192 | md_dict[format_type] = md |
|
190 | md_dict[format_type] = md | |
193 | return format_dict, md_dict |
|
191 | return format_dict, md_dict | |
194 |
|
192 | |||
195 | @property |
|
193 | @property | |
196 | def format_types(self): |
|
194 | def format_types(self): | |
197 | """Return the format types (MIME types) of the active formatters.""" |
|
195 | """Return the format types (MIME types) of the active formatters.""" | |
198 | return list(self.formatters.keys()) |
|
196 | return list(self.formatters.keys()) | |
199 |
|
197 | |||
200 |
|
198 | |||
201 | #----------------------------------------------------------------------------- |
|
199 | #----------------------------------------------------------------------------- | |
202 | # Formatters for specific format types (text, html, svg, etc.) |
|
200 | # Formatters for specific format types (text, html, svg, etc.) | |
203 | #----------------------------------------------------------------------------- |
|
201 | #----------------------------------------------------------------------------- | |
204 |
|
202 | |||
205 |
|
203 | |||
206 | def _safe_repr(obj): |
|
204 | def _safe_repr(obj): | |
207 | """Try to return a repr of an object |
|
205 | """Try to return a repr of an object | |
208 |
|
206 | |||
209 | always returns a string, at least. |
|
207 | always returns a string, at least. | |
210 | """ |
|
208 | """ | |
211 | try: |
|
209 | try: | |
212 | return repr(obj) |
|
210 | return repr(obj) | |
213 | except Exception as e: |
|
211 | except Exception as e: | |
214 | return "un-repr-able object (%r)" % e |
|
212 | return "un-repr-able object (%r)" % e | |
215 |
|
213 | |||
216 |
|
214 | |||
217 | class FormatterWarning(UserWarning): |
|
215 | class FormatterWarning(UserWarning): | |
218 | """Warning class for errors in formatters""" |
|
216 | """Warning class for errors in formatters""" | |
219 |
|
217 | |||
220 | @decorator |
|
218 | @decorator | |
221 | def catch_format_error(method, self, *args, **kwargs): |
|
219 | def catch_format_error(method, self, *args, **kwargs): | |
222 | """show traceback on failed format call""" |
|
220 | """show traceback on failed format call""" | |
223 | try: |
|
221 | try: | |
224 | r = method(self, *args, **kwargs) |
|
222 | r = method(self, *args, **kwargs) | |
225 | except NotImplementedError: |
|
223 | except NotImplementedError: | |
226 | # don't warn on NotImplementedErrors |
|
224 | # don't warn on NotImplementedErrors | |
227 | return self._check_return(None, args[0]) |
|
225 | return self._check_return(None, args[0]) | |
228 | except Exception: |
|
226 | except Exception: | |
229 | exc_info = sys.exc_info() |
|
227 | exc_info = sys.exc_info() | |
230 | ip = get_ipython() |
|
228 | ip = get_ipython() | |
231 | if ip is not None: |
|
229 | if ip is not None: | |
232 | ip.showtraceback(exc_info) |
|
230 | ip.showtraceback(exc_info) | |
233 | else: |
|
231 | else: | |
234 | traceback.print_exception(*exc_info) |
|
232 | traceback.print_exception(*exc_info) | |
235 | return self._check_return(None, args[0]) |
|
233 | return self._check_return(None, args[0]) | |
236 | return self._check_return(r, args[0]) |
|
234 | return self._check_return(r, args[0]) | |
237 |
|
235 | |||
238 |
|
236 | |||
239 | class FormatterABC(metaclass=abc.ABCMeta): |
|
237 | class FormatterABC(metaclass=abc.ABCMeta): | |
240 | """ Abstract base class for Formatters. |
|
238 | """ Abstract base class for Formatters. | |
241 |
|
239 | |||
242 | A formatter is a callable class that is responsible for computing the |
|
240 | A formatter is a callable class that is responsible for computing the | |
243 | raw format data for a particular format type (MIME type). For example, |
|
241 | raw format data for a particular format type (MIME type). For example, | |
244 | an HTML formatter would have a format type of `text/html` and would return |
|
242 | an HTML formatter would have a format type of `text/html` and would return | |
245 | the HTML representation of the object when called. |
|
243 | the HTML representation of the object when called. | |
246 | """ |
|
244 | """ | |
247 |
|
245 | |||
248 | # The format type of the data returned, usually a MIME type. |
|
246 | # The format type of the data returned, usually a MIME type. | |
249 | format_type = 'text/plain' |
|
247 | format_type = 'text/plain' | |
250 |
|
248 | |||
251 | # Is the formatter enabled... |
|
249 | # Is the formatter enabled... | |
252 | enabled = True |
|
250 | enabled = True | |
253 |
|
251 | |||
254 | @abc.abstractmethod |
|
252 | @abc.abstractmethod | |
255 | def __call__(self, obj): |
|
253 | def __call__(self, obj): | |
256 | """Return a JSON'able representation of the object. |
|
254 | """Return a JSON'able representation of the object. | |
257 |
|
255 | |||
258 | If the object cannot be formatted by this formatter, |
|
256 | If the object cannot be formatted by this formatter, | |
259 | warn and return None. |
|
257 | warn and return None. | |
260 | """ |
|
258 | """ | |
261 | return repr(obj) |
|
259 | return repr(obj) | |
262 |
|
260 | |||
263 |
|
261 | |||
264 | def _mod_name_key(typ): |
|
262 | def _mod_name_key(typ): | |
265 | """Return a (__module__, __name__) tuple for a type. |
|
263 | """Return a (__module__, __name__) tuple for a type. | |
266 |
|
264 | |||
267 | Used as key in Formatter.deferred_printers. |
|
265 | Used as key in Formatter.deferred_printers. | |
268 | """ |
|
266 | """ | |
269 | module = getattr(typ, '__module__', None) |
|
267 | module = getattr(typ, '__module__', None) | |
270 | name = getattr(typ, '__name__', None) |
|
268 | name = getattr(typ, '__name__', None) | |
271 | return (module, name) |
|
269 | return (module, name) | |
272 |
|
270 | |||
273 |
|
271 | |||
274 | def _get_type(obj): |
|
272 | def _get_type(obj): | |
275 | """Return the type of an instance (old and new-style)""" |
|
273 | """Return the type of an instance (old and new-style)""" | |
276 | return getattr(obj, '__class__', None) or type(obj) |
|
274 | return getattr(obj, '__class__', None) or type(obj) | |
277 |
|
275 | |||
278 |
|
276 | |||
279 | _raise_key_error = Sentinel('_raise_key_error', __name__, |
|
277 | _raise_key_error = Sentinel('_raise_key_error', __name__, | |
280 | """ |
|
278 | """ | |
281 | Special value to raise a KeyError |
|
279 | Special value to raise a KeyError | |
282 |
|
280 | |||
283 | Raise KeyError in `BaseFormatter.pop` if passed as the default value to `pop` |
|
281 | Raise KeyError in `BaseFormatter.pop` if passed as the default value to `pop` | |
284 | """) |
|
282 | """) | |
285 |
|
283 | |||
286 |
|
284 | |||
287 | class BaseFormatter(Configurable): |
|
285 | class BaseFormatter(Configurable): | |
288 | """A base formatter class that is configurable. |
|
286 | """A base formatter class that is configurable. | |
289 |
|
287 | |||
290 | This formatter should usually be used as the base class of all formatters. |
|
288 | This formatter should usually be used as the base class of all formatters. | |
291 | It is a traited :class:`Configurable` class and includes an extensible |
|
289 | It is a traited :class:`Configurable` class and includes an extensible | |
292 | API for users to determine how their objects are formatted. The following |
|
290 | API for users to determine how their objects are formatted. The following | |
293 | logic is used to find a function to format an given object. |
|
291 | logic is used to find a function to format an given object. | |
294 |
|
292 | |||
295 | 1. The object is introspected to see if it has a method with the name |
|
293 | 1. The object is introspected to see if it has a method with the name | |
296 | :attr:`print_method`. If is does, that object is passed to that method |
|
294 | :attr:`print_method`. If is does, that object is passed to that method | |
297 | for formatting. |
|
295 | for formatting. | |
298 | 2. If no print method is found, three internal dictionaries are consulted |
|
296 | 2. If no print method is found, three internal dictionaries are consulted | |
299 | to find print method: :attr:`singleton_printers`, :attr:`type_printers` |
|
297 | to find print method: :attr:`singleton_printers`, :attr:`type_printers` | |
300 | and :attr:`deferred_printers`. |
|
298 | and :attr:`deferred_printers`. | |
301 |
|
299 | |||
302 | Users should use these dictionaries to register functions that will be |
|
300 | Users should use these dictionaries to register functions that will be | |
303 | used to compute the format data for their objects (if those objects don't |
|
301 | used to compute the format data for their objects (if those objects don't | |
304 | have the special print methods). The easiest way of using these |
|
302 | have the special print methods). The easiest way of using these | |
305 | dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name` |
|
303 | dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name` | |
306 | methods. |
|
304 | methods. | |
307 |
|
305 | |||
308 | If no function/callable is found to compute the format data, ``None`` is |
|
306 | If no function/callable is found to compute the format data, ``None`` is | |
309 | returned and this format type is not used. |
|
307 | returned and this format type is not used. | |
310 | """ |
|
308 | """ | |
311 |
|
309 | |||
312 | format_type = Unicode('text/plain') |
|
310 | format_type = Unicode('text/plain') | |
313 | _return_type = str |
|
311 | _return_type = str | |
314 |
|
312 | |||
315 | enabled = Bool(True).tag(config=True) |
|
313 | enabled = Bool(True).tag(config=True) | |
316 |
|
314 | |||
317 | print_method = ObjectName('__repr__') |
|
315 | print_method = ObjectName('__repr__') | |
318 |
|
316 | |||
319 | # The singleton printers. |
|
317 | # The singleton printers. | |
320 | # Maps the IDs of the builtin singleton objects to the format functions. |
|
318 | # Maps the IDs of the builtin singleton objects to the format functions. | |
321 | singleton_printers = Dict().tag(config=True) |
|
319 | singleton_printers = Dict().tag(config=True) | |
322 |
|
320 | |||
323 | # The type-specific printers. |
|
321 | # The type-specific printers. | |
324 | # Map type objects to the format functions. |
|
322 | # Map type objects to the format functions. | |
325 | type_printers = Dict().tag(config=True) |
|
323 | type_printers = Dict().tag(config=True) | |
326 |
|
324 | |||
327 | # The deferred-import type-specific printers. |
|
325 | # The deferred-import type-specific printers. | |
328 | # Map (modulename, classname) pairs to the format functions. |
|
326 | # Map (modulename, classname) pairs to the format functions. | |
329 | deferred_printers = Dict().tag(config=True) |
|
327 | deferred_printers = Dict().tag(config=True) | |
330 |
|
328 | |||
331 | @catch_format_error |
|
329 | @catch_format_error | |
332 | def __call__(self, obj): |
|
330 | def __call__(self, obj): | |
333 | """Compute the format for an object.""" |
|
331 | """Compute the format for an object.""" | |
334 | if self.enabled: |
|
332 | if self.enabled: | |
335 | # lookup registered printer |
|
333 | # lookup registered printer | |
336 | try: |
|
334 | try: | |
337 | printer = self.lookup(obj) |
|
335 | printer = self.lookup(obj) | |
338 | except KeyError: |
|
336 | except KeyError: | |
339 | pass |
|
337 | pass | |
340 | else: |
|
338 | else: | |
341 | return printer(obj) |
|
339 | return printer(obj) | |
342 | # Finally look for special method names |
|
340 | # Finally look for special method names | |
343 | method = get_real_method(obj, self.print_method) |
|
341 | method = get_real_method(obj, self.print_method) | |
344 | if method is not None: |
|
342 | if method is not None: | |
345 | return method() |
|
343 | return method() | |
346 | return None |
|
344 | return None | |
347 | else: |
|
345 | else: | |
348 | return None |
|
346 | return None | |
349 |
|
347 | |||
350 | def __contains__(self, typ): |
|
348 | def __contains__(self, typ): | |
351 | """map in to lookup_by_type""" |
|
349 | """map in to lookup_by_type""" | |
352 | try: |
|
350 | try: | |
353 | self.lookup_by_type(typ) |
|
351 | self.lookup_by_type(typ) | |
354 | except KeyError: |
|
352 | except KeyError: | |
355 | return False |
|
353 | return False | |
356 | else: |
|
354 | else: | |
357 | return True |
|
355 | return True | |
358 |
|
356 | |||
359 | def _check_return(self, r, obj): |
|
357 | def _check_return(self, r, obj): | |
360 | """Check that a return value is appropriate |
|
358 | """Check that a return value is appropriate | |
361 |
|
359 | |||
362 | Return the value if so, None otherwise, warning if invalid. |
|
360 | Return the value if so, None otherwise, warning if invalid. | |
363 | """ |
|
361 | """ | |
364 | if r is None or isinstance(r, self._return_type) or \ |
|
362 | if r is None or isinstance(r, self._return_type) or \ | |
365 | (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)): |
|
363 | (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)): | |
366 | return r |
|
364 | return r | |
367 | else: |
|
365 | else: | |
368 | warnings.warn( |
|
366 | warnings.warn( | |
369 | "%s formatter returned invalid type %s (expected %s) for object: %s" % \ |
|
367 | "%s formatter returned invalid type %s (expected %s) for object: %s" % \ | |
370 | (self.format_type, type(r), self._return_type, _safe_repr(obj)), |
|
368 | (self.format_type, type(r), self._return_type, _safe_repr(obj)), | |
371 | FormatterWarning |
|
369 | FormatterWarning | |
372 | ) |
|
370 | ) | |
373 |
|
371 | |||
374 | def lookup(self, obj): |
|
372 | def lookup(self, obj): | |
375 | """Look up the formatter for a given instance. |
|
373 | """Look up the formatter for a given instance. | |
376 |
|
374 | |||
377 | Parameters |
|
375 | Parameters | |
378 | ---------- |
|
376 | ---------- | |
379 |
obj |
|
377 | obj : object instance | |
380 |
|
378 | |||
381 | Returns |
|
379 | Returns | |
382 | ------- |
|
380 | ------- | |
383 | f : callable |
|
381 | f : callable | |
384 | The registered formatting callable for the type. |
|
382 | The registered formatting callable for the type. | |
385 |
|
383 | |||
386 | Raises |
|
384 | Raises | |
387 | ------ |
|
385 | ------ | |
388 | KeyError if the type has not been registered. |
|
386 | KeyError if the type has not been registered. | |
389 | """ |
|
387 | """ | |
390 | # look for singleton first |
|
388 | # look for singleton first | |
391 | obj_id = id(obj) |
|
389 | obj_id = id(obj) | |
392 | if obj_id in self.singleton_printers: |
|
390 | if obj_id in self.singleton_printers: | |
393 | return self.singleton_printers[obj_id] |
|
391 | return self.singleton_printers[obj_id] | |
394 | # then lookup by type |
|
392 | # then lookup by type | |
395 | return self.lookup_by_type(_get_type(obj)) |
|
393 | return self.lookup_by_type(_get_type(obj)) | |
396 |
|
394 | |||
397 | def lookup_by_type(self, typ): |
|
395 | def lookup_by_type(self, typ): | |
398 | """Look up the registered formatter for a type. |
|
396 | """Look up the registered formatter for a type. | |
399 |
|
397 | |||
400 | Parameters |
|
398 | Parameters | |
401 | ---------- |
|
399 | ---------- | |
402 |
typ |
|
400 | typ : type or '__module__.__name__' string for a type | |
403 |
|
401 | |||
404 | Returns |
|
402 | Returns | |
405 | ------- |
|
403 | ------- | |
406 | f : callable |
|
404 | f : callable | |
407 | The registered formatting callable for the type. |
|
405 | The registered formatting callable for the type. | |
408 |
|
406 | |||
409 | Raises |
|
407 | Raises | |
410 | ------ |
|
408 | ------ | |
411 | KeyError if the type has not been registered. |
|
409 | KeyError if the type has not been registered. | |
412 | """ |
|
410 | """ | |
413 | if isinstance(typ, str): |
|
411 | if isinstance(typ, str): | |
414 | typ_key = tuple(typ.rsplit('.',1)) |
|
412 | typ_key = tuple(typ.rsplit('.',1)) | |
415 | if typ_key not in self.deferred_printers: |
|
413 | if typ_key not in self.deferred_printers: | |
416 | # We may have it cached in the type map. We will have to |
|
414 | # We may have it cached in the type map. We will have to | |
417 | # iterate over all of the types to check. |
|
415 | # iterate over all of the types to check. | |
418 | for cls in self.type_printers: |
|
416 | for cls in self.type_printers: | |
419 | if _mod_name_key(cls) == typ_key: |
|
417 | if _mod_name_key(cls) == typ_key: | |
420 | return self.type_printers[cls] |
|
418 | return self.type_printers[cls] | |
421 | else: |
|
419 | else: | |
422 | return self.deferred_printers[typ_key] |
|
420 | return self.deferred_printers[typ_key] | |
423 | else: |
|
421 | else: | |
424 | for cls in pretty._get_mro(typ): |
|
422 | for cls in pretty._get_mro(typ): | |
425 | if cls in self.type_printers or self._in_deferred_types(cls): |
|
423 | if cls in self.type_printers or self._in_deferred_types(cls): | |
426 | return self.type_printers[cls] |
|
424 | return self.type_printers[cls] | |
427 |
|
425 | |||
428 | # If we have reached here, the lookup failed. |
|
426 | # If we have reached here, the lookup failed. | |
429 | raise KeyError("No registered printer for {0!r}".format(typ)) |
|
427 | raise KeyError("No registered printer for {0!r}".format(typ)) | |
430 |
|
428 | |||
431 | def for_type(self, typ, func=None): |
|
429 | def for_type(self, typ, func=None): | |
432 | """Add a format function for a given type. |
|
430 | """Add a format function for a given type. | |
433 |
|
431 | |||
434 | Parameters |
|
432 | Parameters | |
435 | ---------- |
|
433 | ---------- | |
436 | typ : type or '__module__.__name__' string for a type |
|
434 | typ : type or '__module__.__name__' string for a type | |
437 | The class of the object that will be formatted using `func`. |
|
435 | The class of the object that will be formatted using `func`. | |
438 | func : callable |
|
436 | func : callable | |
439 | A callable for computing the format data. |
|
437 | A callable for computing the format data. | |
440 | `func` will be called with the object to be formatted, |
|
438 | `func` will be called with the object to be formatted, | |
441 | and will return the raw data in this formatter's format. |
|
439 | and will return the raw data in this formatter's format. | |
442 | Subclasses may use a different call signature for the |
|
440 | Subclasses may use a different call signature for the | |
443 | `func` argument. |
|
441 | `func` argument. | |
444 |
|
442 | |||
445 | If `func` is None or not specified, there will be no change, |
|
443 | If `func` is None or not specified, there will be no change, | |
446 | only returning the current value. |
|
444 | only returning the current value. | |
447 |
|
445 | |||
448 | Returns |
|
446 | Returns | |
449 | ------- |
|
447 | ------- | |
450 | oldfunc : callable |
|
448 | oldfunc : callable | |
451 | The currently registered callable. |
|
449 | The currently registered callable. | |
452 | If you are registering a new formatter, |
|
450 | If you are registering a new formatter, | |
453 | this will be the previous value (to enable restoring later). |
|
451 | this will be the previous value (to enable restoring later). | |
454 | """ |
|
452 | """ | |
455 | # if string given, interpret as 'pkg.module.class_name' |
|
453 | # if string given, interpret as 'pkg.module.class_name' | |
456 | if isinstance(typ, str): |
|
454 | if isinstance(typ, str): | |
457 | type_module, type_name = typ.rsplit('.', 1) |
|
455 | type_module, type_name = typ.rsplit('.', 1) | |
458 | return self.for_type_by_name(type_module, type_name, func) |
|
456 | return self.for_type_by_name(type_module, type_name, func) | |
459 |
|
457 | |||
460 | try: |
|
458 | try: | |
461 | oldfunc = self.lookup_by_type(typ) |
|
459 | oldfunc = self.lookup_by_type(typ) | |
462 | except KeyError: |
|
460 | except KeyError: | |
463 | oldfunc = None |
|
461 | oldfunc = None | |
464 |
|
462 | |||
465 | if func is not None: |
|
463 | if func is not None: | |
466 | self.type_printers[typ] = func |
|
464 | self.type_printers[typ] = func | |
467 |
|
465 | |||
468 | return oldfunc |
|
466 | return oldfunc | |
469 |
|
467 | |||
470 | def for_type_by_name(self, type_module, type_name, func=None): |
|
468 | def for_type_by_name(self, type_module, type_name, func=None): | |
471 | """Add a format function for a type specified by the full dotted |
|
469 | """Add a format function for a type specified by the full dotted | |
472 | module and name of the type, rather than the type of the object. |
|
470 | module and name of the type, rather than the type of the object. | |
473 |
|
471 | |||
474 | Parameters |
|
472 | Parameters | |
475 | ---------- |
|
473 | ---------- | |
476 | type_module : str |
|
474 | type_module : str | |
477 | The full dotted name of the module the type is defined in, like |
|
475 | The full dotted name of the module the type is defined in, like | |
478 | ``numpy``. |
|
476 | ``numpy``. | |
479 | type_name : str |
|
477 | type_name : str | |
480 | The name of the type (the class name), like ``dtype`` |
|
478 | The name of the type (the class name), like ``dtype`` | |
481 | func : callable |
|
479 | func : callable | |
482 | A callable for computing the format data. |
|
480 | A callable for computing the format data. | |
483 | `func` will be called with the object to be formatted, |
|
481 | `func` will be called with the object to be formatted, | |
484 | and will return the raw data in this formatter's format. |
|
482 | and will return the raw data in this formatter's format. | |
485 | Subclasses may use a different call signature for the |
|
483 | Subclasses may use a different call signature for the | |
486 | `func` argument. |
|
484 | `func` argument. | |
487 |
|
485 | |||
488 | If `func` is None or unspecified, there will be no change, |
|
486 | If `func` is None or unspecified, there will be no change, | |
489 | only returning the current value. |
|
487 | only returning the current value. | |
490 |
|
488 | |||
491 | Returns |
|
489 | Returns | |
492 | ------- |
|
490 | ------- | |
493 | oldfunc : callable |
|
491 | oldfunc : callable | |
494 | The currently registered callable. |
|
492 | The currently registered callable. | |
495 | If you are registering a new formatter, |
|
493 | If you are registering a new formatter, | |
496 | this will be the previous value (to enable restoring later). |
|
494 | this will be the previous value (to enable restoring later). | |
497 | """ |
|
495 | """ | |
498 | key = (type_module, type_name) |
|
496 | key = (type_module, type_name) | |
499 |
|
497 | |||
500 | try: |
|
498 | try: | |
501 | oldfunc = self.lookup_by_type("%s.%s" % key) |
|
499 | oldfunc = self.lookup_by_type("%s.%s" % key) | |
502 | except KeyError: |
|
500 | except KeyError: | |
503 | oldfunc = None |
|
501 | oldfunc = None | |
504 |
|
502 | |||
505 | if func is not None: |
|
503 | if func is not None: | |
506 | self.deferred_printers[key] = func |
|
504 | self.deferred_printers[key] = func | |
507 | return oldfunc |
|
505 | return oldfunc | |
508 |
|
506 | |||
509 | def pop(self, typ, default=_raise_key_error): |
|
507 | def pop(self, typ, default=_raise_key_error): | |
510 | """Pop a formatter for the given type. |
|
508 | """Pop a formatter for the given type. | |
511 |
|
509 | |||
512 | Parameters |
|
510 | Parameters | |
513 | ---------- |
|
511 | ---------- | |
514 | typ : type or '__module__.__name__' string for a type |
|
512 | typ : type or '__module__.__name__' string for a type | |
515 | default : object |
|
513 | default : object | |
516 | value to be returned if no formatter is registered for typ. |
|
514 | value to be returned if no formatter is registered for typ. | |
517 |
|
515 | |||
518 | Returns |
|
516 | Returns | |
519 | ------- |
|
517 | ------- | |
520 | obj : object |
|
518 | obj : object | |
521 | The last registered object for the type. |
|
519 | The last registered object for the type. | |
522 |
|
520 | |||
523 | Raises |
|
521 | Raises | |
524 | ------ |
|
522 | ------ | |
525 | KeyError if the type is not registered and default is not specified. |
|
523 | KeyError if the type is not registered and default is not specified. | |
526 | """ |
|
524 | """ | |
527 |
|
525 | |||
528 | if isinstance(typ, str): |
|
526 | if isinstance(typ, str): | |
529 | typ_key = tuple(typ.rsplit('.',1)) |
|
527 | typ_key = tuple(typ.rsplit('.',1)) | |
530 | if typ_key not in self.deferred_printers: |
|
528 | if typ_key not in self.deferred_printers: | |
531 | # We may have it cached in the type map. We will have to |
|
529 | # We may have it cached in the type map. We will have to | |
532 | # iterate over all of the types to check. |
|
530 | # iterate over all of the types to check. | |
533 | for cls in self.type_printers: |
|
531 | for cls in self.type_printers: | |
534 | if _mod_name_key(cls) == typ_key: |
|
532 | if _mod_name_key(cls) == typ_key: | |
535 | old = self.type_printers.pop(cls) |
|
533 | old = self.type_printers.pop(cls) | |
536 | break |
|
534 | break | |
537 | else: |
|
535 | else: | |
538 | old = default |
|
536 | old = default | |
539 | else: |
|
537 | else: | |
540 | old = self.deferred_printers.pop(typ_key) |
|
538 | old = self.deferred_printers.pop(typ_key) | |
541 | else: |
|
539 | else: | |
542 | if typ in self.type_printers: |
|
540 | if typ in self.type_printers: | |
543 | old = self.type_printers.pop(typ) |
|
541 | old = self.type_printers.pop(typ) | |
544 | else: |
|
542 | else: | |
545 | old = self.deferred_printers.pop(_mod_name_key(typ), default) |
|
543 | old = self.deferred_printers.pop(_mod_name_key(typ), default) | |
546 | if old is _raise_key_error: |
|
544 | if old is _raise_key_error: | |
547 | raise KeyError("No registered value for {0!r}".format(typ)) |
|
545 | raise KeyError("No registered value for {0!r}".format(typ)) | |
548 | return old |
|
546 | return old | |
549 |
|
547 | |||
550 | def _in_deferred_types(self, cls): |
|
548 | def _in_deferred_types(self, cls): | |
551 | """ |
|
549 | """ | |
552 | Check if the given class is specified in the deferred type registry. |
|
550 | Check if the given class is specified in the deferred type registry. | |
553 |
|
551 | |||
554 | Successful matches will be moved to the regular type registry for future use. |
|
552 | Successful matches will be moved to the regular type registry for future use. | |
555 | """ |
|
553 | """ | |
556 | mod = getattr(cls, '__module__', None) |
|
554 | mod = getattr(cls, '__module__', None) | |
557 | name = getattr(cls, '__name__', None) |
|
555 | name = getattr(cls, '__name__', None) | |
558 | key = (mod, name) |
|
556 | key = (mod, name) | |
559 | if key in self.deferred_printers: |
|
557 | if key in self.deferred_printers: | |
560 | # Move the printer over to the regular registry. |
|
558 | # Move the printer over to the regular registry. | |
561 | printer = self.deferred_printers.pop(key) |
|
559 | printer = self.deferred_printers.pop(key) | |
562 | self.type_printers[cls] = printer |
|
560 | self.type_printers[cls] = printer | |
563 | return True |
|
561 | return True | |
564 | return False |
|
562 | return False | |
565 |
|
563 | |||
566 |
|
564 | |||
567 | class PlainTextFormatter(BaseFormatter): |
|
565 | class PlainTextFormatter(BaseFormatter): | |
568 | """The default pretty-printer. |
|
566 | """The default pretty-printer. | |
569 |
|
567 | |||
570 | This uses :mod:`IPython.lib.pretty` to compute the format data of |
|
568 | This uses :mod:`IPython.lib.pretty` to compute the format data of | |
571 | the object. If the object cannot be pretty printed, :func:`repr` is used. |
|
569 | the object. If the object cannot be pretty printed, :func:`repr` is used. | |
572 | See the documentation of :mod:`IPython.lib.pretty` for details on |
|
570 | See the documentation of :mod:`IPython.lib.pretty` for details on | |
573 | how to write pretty printers. Here is a simple example:: |
|
571 | how to write pretty printers. Here is a simple example:: | |
574 |
|
572 | |||
575 | def dtype_pprinter(obj, p, cycle): |
|
573 | def dtype_pprinter(obj, p, cycle): | |
576 | if cycle: |
|
574 | if cycle: | |
577 | return p.text('dtype(...)') |
|
575 | return p.text('dtype(...)') | |
578 | if hasattr(obj, 'fields'): |
|
576 | if hasattr(obj, 'fields'): | |
579 | if obj.fields is None: |
|
577 | if obj.fields is None: | |
580 | p.text(repr(obj)) |
|
578 | p.text(repr(obj)) | |
581 | else: |
|
579 | else: | |
582 | p.begin_group(7, 'dtype([') |
|
580 | p.begin_group(7, 'dtype([') | |
583 | for i, field in enumerate(obj.descr): |
|
581 | for i, field in enumerate(obj.descr): | |
584 | if i > 0: |
|
582 | if i > 0: | |
585 | p.text(',') |
|
583 | p.text(',') | |
586 | p.breakable() |
|
584 | p.breakable() | |
587 | p.pretty(field) |
|
585 | p.pretty(field) | |
588 | p.end_group(7, '])') |
|
586 | p.end_group(7, '])') | |
589 | """ |
|
587 | """ | |
590 |
|
588 | |||
591 | # The format type of data returned. |
|
589 | # The format type of data returned. | |
592 | format_type = Unicode('text/plain') |
|
590 | format_type = Unicode('text/plain') | |
593 |
|
591 | |||
594 | # This subclass ignores this attribute as it always need to return |
|
592 | # This subclass ignores this attribute as it always need to return | |
595 | # something. |
|
593 | # something. | |
596 | enabled = Bool(True).tag(config=False) |
|
594 | enabled = Bool(True).tag(config=False) | |
597 |
|
595 | |||
598 | max_seq_length = Integer(pretty.MAX_SEQ_LENGTH, |
|
596 | max_seq_length = Integer(pretty.MAX_SEQ_LENGTH, | |
599 | help="""Truncate large collections (lists, dicts, tuples, sets) to this size. |
|
597 | help="""Truncate large collections (lists, dicts, tuples, sets) to this size. | |
600 |
|
598 | |||
601 | Set to 0 to disable truncation. |
|
599 | Set to 0 to disable truncation. | |
602 | """ |
|
600 | """ | |
603 | ).tag(config=True) |
|
601 | ).tag(config=True) | |
604 |
|
602 | |||
605 | # Look for a _repr_pretty_ methods to use for pretty printing. |
|
603 | # Look for a _repr_pretty_ methods to use for pretty printing. | |
606 | print_method = ObjectName('_repr_pretty_') |
|
604 | print_method = ObjectName('_repr_pretty_') | |
607 |
|
605 | |||
608 | # Whether to pretty-print or not. |
|
606 | # Whether to pretty-print or not. | |
609 | pprint = Bool(True).tag(config=True) |
|
607 | pprint = Bool(True).tag(config=True) | |
610 |
|
608 | |||
611 | # Whether to be verbose or not. |
|
609 | # Whether to be verbose or not. | |
612 | verbose = Bool(False).tag(config=True) |
|
610 | verbose = Bool(False).tag(config=True) | |
613 |
|
611 | |||
614 | # The maximum width. |
|
612 | # The maximum width. | |
615 | max_width = Integer(79).tag(config=True) |
|
613 | max_width = Integer(79).tag(config=True) | |
616 |
|
614 | |||
617 | # The newline character. |
|
615 | # The newline character. | |
618 | newline = Unicode('\n').tag(config=True) |
|
616 | newline = Unicode('\n').tag(config=True) | |
619 |
|
617 | |||
620 | # format-string for pprinting floats |
|
618 | # format-string for pprinting floats | |
621 | float_format = Unicode('%r') |
|
619 | float_format = Unicode('%r') | |
622 | # setter for float precision, either int or direct format-string |
|
620 | # setter for float precision, either int or direct format-string | |
623 | float_precision = CUnicode('').tag(config=True) |
|
621 | float_precision = CUnicode('').tag(config=True) | |
624 |
|
622 | |||
625 | @observe('float_precision') |
|
623 | @observe('float_precision') | |
626 | def _float_precision_changed(self, change): |
|
624 | def _float_precision_changed(self, change): | |
627 | """float_precision changed, set float_format accordingly. |
|
625 | """float_precision changed, set float_format accordingly. | |
628 |
|
626 | |||
629 | float_precision can be set by int or str. |
|
627 | float_precision can be set by int or str. | |
630 | This will set float_format, after interpreting input. |
|
628 | This will set float_format, after interpreting input. | |
631 | If numpy has been imported, numpy print precision will also be set. |
|
629 | If numpy has been imported, numpy print precision will also be set. | |
632 |
|
630 | |||
633 | integer `n` sets format to '%.nf', otherwise, format set directly. |
|
631 | integer `n` sets format to '%.nf', otherwise, format set directly. | |
634 |
|
632 | |||
635 | An empty string returns to defaults (repr for float, 8 for numpy). |
|
633 | An empty string returns to defaults (repr for float, 8 for numpy). | |
636 |
|
634 | |||
637 | This parameter can be set via the '%precision' magic. |
|
635 | This parameter can be set via the '%precision' magic. | |
638 | """ |
|
636 | """ | |
639 |
|
||||
640 | new = change['new'] |
|
637 | new = change['new'] | |
641 | if '%' in new: |
|
638 | if '%' in new: | |
642 | # got explicit format string |
|
639 | # got explicit format string | |
643 | fmt = new |
|
640 | fmt = new | |
644 | try: |
|
641 | try: | |
645 | fmt%3.14159 |
|
642 | fmt%3.14159 | |
646 | except Exception as e: |
|
643 | except Exception as e: | |
647 | raise ValueError("Precision must be int or format string, not %r"%new) from e |
|
644 | raise ValueError("Precision must be int or format string, not %r"%new) from e | |
648 | elif new: |
|
645 | elif new: | |
649 | # otherwise, should be an int |
|
646 | # otherwise, should be an int | |
650 | try: |
|
647 | try: | |
651 | i = int(new) |
|
648 | i = int(new) | |
652 | assert i >= 0 |
|
649 | assert i >= 0 | |
653 | except ValueError as e: |
|
650 | except ValueError as e: | |
654 | raise ValueError("Precision must be int or format string, not %r"%new) from e |
|
651 | raise ValueError("Precision must be int or format string, not %r"%new) from e | |
655 | except AssertionError as e: |
|
652 | except AssertionError as e: | |
656 | raise ValueError("int precision must be non-negative, not %r"%i) from e |
|
653 | raise ValueError("int precision must be non-negative, not %r"%i) from e | |
657 |
|
654 | |||
658 | fmt = '%%.%if'%i |
|
655 | fmt = '%%.%if'%i | |
659 | if 'numpy' in sys.modules: |
|
656 | if 'numpy' in sys.modules: | |
660 | # set numpy precision if it has been imported |
|
657 | # set numpy precision if it has been imported | |
661 | import numpy |
|
658 | import numpy | |
662 | numpy.set_printoptions(precision=i) |
|
659 | numpy.set_printoptions(precision=i) | |
663 | else: |
|
660 | else: | |
664 | # default back to repr |
|
661 | # default back to repr | |
665 | fmt = '%r' |
|
662 | fmt = '%r' | |
666 | if 'numpy' in sys.modules: |
|
663 | if 'numpy' in sys.modules: | |
667 | import numpy |
|
664 | import numpy | |
668 | # numpy default is 8 |
|
665 | # numpy default is 8 | |
669 | numpy.set_printoptions(precision=8) |
|
666 | numpy.set_printoptions(precision=8) | |
670 | self.float_format = fmt |
|
667 | self.float_format = fmt | |
671 |
|
668 | |||
672 | # Use the default pretty printers from IPython.lib.pretty. |
|
669 | # Use the default pretty printers from IPython.lib.pretty. | |
673 | @default('singleton_printers') |
|
670 | @default('singleton_printers') | |
674 | def _singleton_printers_default(self): |
|
671 | def _singleton_printers_default(self): | |
675 | return pretty._singleton_pprinters.copy() |
|
672 | return pretty._singleton_pprinters.copy() | |
676 |
|
673 | |||
677 | @default('type_printers') |
|
674 | @default('type_printers') | |
678 | def _type_printers_default(self): |
|
675 | def _type_printers_default(self): | |
679 | d = pretty._type_pprinters.copy() |
|
676 | d = pretty._type_pprinters.copy() | |
680 | d[float] = lambda obj,p,cycle: p.text(self.float_format%obj) |
|
677 | d[float] = lambda obj,p,cycle: p.text(self.float_format%obj) | |
|
678 | # if NumPy is used, set precision for its float64 type | |||
|
679 | if "numpy" in sys.modules: | |||
|
680 | import numpy | |||
|
681 | ||||
|
682 | d[numpy.float64] = lambda obj, p, cycle: p.text(self.float_format % obj) | |||
681 | return d |
|
683 | return d | |
682 |
|
684 | |||
683 | @default('deferred_printers') |
|
685 | @default('deferred_printers') | |
684 | def _deferred_printers_default(self): |
|
686 | def _deferred_printers_default(self): | |
685 | return pretty._deferred_type_pprinters.copy() |
|
687 | return pretty._deferred_type_pprinters.copy() | |
686 |
|
688 | |||
687 | #### FormatterABC interface #### |
|
689 | #### FormatterABC interface #### | |
688 |
|
690 | |||
689 | @catch_format_error |
|
691 | @catch_format_error | |
690 | def __call__(self, obj): |
|
692 | def __call__(self, obj): | |
691 | """Compute the pretty representation of the object.""" |
|
693 | """Compute the pretty representation of the object.""" | |
692 | if not self.pprint: |
|
694 | if not self.pprint: | |
693 | return repr(obj) |
|
695 | return repr(obj) | |
694 | else: |
|
696 | else: | |
695 | stream = StringIO() |
|
697 | stream = StringIO() | |
696 | printer = pretty.RepresentationPrinter(stream, self.verbose, |
|
698 | printer = pretty.RepresentationPrinter(stream, self.verbose, | |
697 | self.max_width, self.newline, |
|
699 | self.max_width, self.newline, | |
698 | max_seq_length=self.max_seq_length, |
|
700 | max_seq_length=self.max_seq_length, | |
699 | singleton_pprinters=self.singleton_printers, |
|
701 | singleton_pprinters=self.singleton_printers, | |
700 | type_pprinters=self.type_printers, |
|
702 | type_pprinters=self.type_printers, | |
701 | deferred_pprinters=self.deferred_printers) |
|
703 | deferred_pprinters=self.deferred_printers) | |
702 | printer.pretty(obj) |
|
704 | printer.pretty(obj) | |
703 | printer.flush() |
|
705 | printer.flush() | |
704 | return stream.getvalue() |
|
706 | return stream.getvalue() | |
705 |
|
707 | |||
706 |
|
708 | |||
707 | class HTMLFormatter(BaseFormatter): |
|
709 | class HTMLFormatter(BaseFormatter): | |
708 | """An HTML formatter. |
|
710 | """An HTML formatter. | |
709 |
|
711 | |||
710 | To define the callables that compute the HTML representation of your |
|
712 | To define the callables that compute the HTML representation of your | |
711 | objects, define a :meth:`_repr_html_` method or use the :meth:`for_type` |
|
713 | objects, define a :meth:`_repr_html_` method or use the :meth:`for_type` | |
712 | or :meth:`for_type_by_name` methods to register functions that handle |
|
714 | or :meth:`for_type_by_name` methods to register functions that handle | |
713 | this. |
|
715 | this. | |
714 |
|
716 | |||
715 | The return value of this formatter should be a valid HTML snippet that |
|
717 | The return value of this formatter should be a valid HTML snippet that | |
716 | could be injected into an existing DOM. It should *not* include the |
|
718 | could be injected into an existing DOM. It should *not* include the | |
717 | ```<html>`` or ```<body>`` tags. |
|
719 | ```<html>`` or ```<body>`` tags. | |
718 | """ |
|
720 | """ | |
719 | format_type = Unicode('text/html') |
|
721 | format_type = Unicode('text/html') | |
720 |
|
722 | |||
721 | print_method = ObjectName('_repr_html_') |
|
723 | print_method = ObjectName('_repr_html_') | |
722 |
|
724 | |||
723 |
|
725 | |||
724 | class MarkdownFormatter(BaseFormatter): |
|
726 | class MarkdownFormatter(BaseFormatter): | |
725 | """A Markdown formatter. |
|
727 | """A Markdown formatter. | |
726 |
|
728 | |||
727 | To define the callables that compute the Markdown representation of your |
|
729 | To define the callables that compute the Markdown representation of your | |
728 | objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type` |
|
730 | objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type` | |
729 | or :meth:`for_type_by_name` methods to register functions that handle |
|
731 | or :meth:`for_type_by_name` methods to register functions that handle | |
730 | this. |
|
732 | this. | |
731 |
|
733 | |||
732 | The return value of this formatter should be a valid Markdown. |
|
734 | The return value of this formatter should be a valid Markdown. | |
733 | """ |
|
735 | """ | |
734 | format_type = Unicode('text/markdown') |
|
736 | format_type = Unicode('text/markdown') | |
735 |
|
737 | |||
736 | print_method = ObjectName('_repr_markdown_') |
|
738 | print_method = ObjectName('_repr_markdown_') | |
737 |
|
739 | |||
738 | class SVGFormatter(BaseFormatter): |
|
740 | class SVGFormatter(BaseFormatter): | |
739 | """An SVG formatter. |
|
741 | """An SVG formatter. | |
740 |
|
742 | |||
741 | To define the callables that compute the SVG representation of your |
|
743 | To define the callables that compute the SVG representation of your | |
742 | objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type` |
|
744 | objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type` | |
743 | or :meth:`for_type_by_name` methods to register functions that handle |
|
745 | or :meth:`for_type_by_name` methods to register functions that handle | |
744 | this. |
|
746 | this. | |
745 |
|
747 | |||
746 | The return value of this formatter should be valid SVG enclosed in |
|
748 | The return value of this formatter should be valid SVG enclosed in | |
747 | ```<svg>``` tags, that could be injected into an existing DOM. It should |
|
749 | ```<svg>``` tags, that could be injected into an existing DOM. It should | |
748 | *not* include the ```<html>`` or ```<body>`` tags. |
|
750 | *not* include the ```<html>`` or ```<body>`` tags. | |
749 | """ |
|
751 | """ | |
750 | format_type = Unicode('image/svg+xml') |
|
752 | format_type = Unicode('image/svg+xml') | |
751 |
|
753 | |||
752 | print_method = ObjectName('_repr_svg_') |
|
754 | print_method = ObjectName('_repr_svg_') | |
753 |
|
755 | |||
754 |
|
756 | |||
755 | class PNGFormatter(BaseFormatter): |
|
757 | class PNGFormatter(BaseFormatter): | |
756 | """A PNG formatter. |
|
758 | """A PNG formatter. | |
757 |
|
759 | |||
758 | To define the callables that compute the PNG representation of your |
|
760 | To define the callables that compute the PNG representation of your | |
759 | objects, define a :meth:`_repr_png_` method or use the :meth:`for_type` |
|
761 | objects, define a :meth:`_repr_png_` method or use the :meth:`for_type` | |
760 | or :meth:`for_type_by_name` methods to register functions that handle |
|
762 | or :meth:`for_type_by_name` methods to register functions that handle | |
761 | this. |
|
763 | this. | |
762 |
|
764 | |||
763 | The return value of this formatter should be raw PNG data, *not* |
|
765 | The return value of this formatter should be raw PNG data, *not* | |
764 | base64 encoded. |
|
766 | base64 encoded. | |
765 | """ |
|
767 | """ | |
766 | format_type = Unicode('image/png') |
|
768 | format_type = Unicode('image/png') | |
767 |
|
769 | |||
768 | print_method = ObjectName('_repr_png_') |
|
770 | print_method = ObjectName('_repr_png_') | |
769 |
|
771 | |||
770 | _return_type = (bytes, str) |
|
772 | _return_type = (bytes, str) | |
771 |
|
773 | |||
772 |
|
774 | |||
773 | class JPEGFormatter(BaseFormatter): |
|
775 | class JPEGFormatter(BaseFormatter): | |
774 | """A JPEG formatter. |
|
776 | """A JPEG formatter. | |
775 |
|
777 | |||
776 | To define the callables that compute the JPEG representation of your |
|
778 | To define the callables that compute the JPEG representation of your | |
777 | objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type` |
|
779 | objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type` | |
778 | or :meth:`for_type_by_name` methods to register functions that handle |
|
780 | or :meth:`for_type_by_name` methods to register functions that handle | |
779 | this. |
|
781 | this. | |
780 |
|
782 | |||
781 | The return value of this formatter should be raw JPEG data, *not* |
|
783 | The return value of this formatter should be raw JPEG data, *not* | |
782 | base64 encoded. |
|
784 | base64 encoded. | |
783 | """ |
|
785 | """ | |
784 | format_type = Unicode('image/jpeg') |
|
786 | format_type = Unicode('image/jpeg') | |
785 |
|
787 | |||
786 | print_method = ObjectName('_repr_jpeg_') |
|
788 | print_method = ObjectName('_repr_jpeg_') | |
787 |
|
789 | |||
788 | _return_type = (bytes, str) |
|
790 | _return_type = (bytes, str) | |
789 |
|
791 | |||
790 |
|
792 | |||
791 | class LatexFormatter(BaseFormatter): |
|
793 | class LatexFormatter(BaseFormatter): | |
792 | """A LaTeX formatter. |
|
794 | """A LaTeX formatter. | |
793 |
|
795 | |||
794 | To define the callables that compute the LaTeX representation of your |
|
796 | To define the callables that compute the LaTeX representation of your | |
795 | objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type` |
|
797 | objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type` | |
796 | or :meth:`for_type_by_name` methods to register functions that handle |
|
798 | or :meth:`for_type_by_name` methods to register functions that handle | |
797 | this. |
|
799 | this. | |
798 |
|
800 | |||
799 | The return value of this formatter should be a valid LaTeX equation, |
|
801 | The return value of this formatter should be a valid LaTeX equation, | |
800 | enclosed in either ```$```, ```$$``` or another LaTeX equation |
|
802 | enclosed in either ```$```, ```$$``` or another LaTeX equation | |
801 | environment. |
|
803 | environment. | |
802 | """ |
|
804 | """ | |
803 | format_type = Unicode('text/latex') |
|
805 | format_type = Unicode('text/latex') | |
804 |
|
806 | |||
805 | print_method = ObjectName('_repr_latex_') |
|
807 | print_method = ObjectName('_repr_latex_') | |
806 |
|
808 | |||
807 |
|
809 | |||
808 | class JSONFormatter(BaseFormatter): |
|
810 | class JSONFormatter(BaseFormatter): | |
809 | """A JSON string formatter. |
|
811 | """A JSON string formatter. | |
810 |
|
812 | |||
811 | To define the callables that compute the JSONable representation of |
|
813 | To define the callables that compute the JSONable representation of | |
812 | your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type` |
|
814 | your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type` | |
813 | or :meth:`for_type_by_name` methods to register functions that handle |
|
815 | or :meth:`for_type_by_name` methods to register functions that handle | |
814 | this. |
|
816 | this. | |
815 |
|
817 | |||
816 | The return value of this formatter should be a JSONable list or dict. |
|
818 | The return value of this formatter should be a JSONable list or dict. | |
817 | JSON scalars (None, number, string) are not allowed, only dict or list containers. |
|
819 | JSON scalars (None, number, string) are not allowed, only dict or list containers. | |
818 | """ |
|
820 | """ | |
819 | format_type = Unicode('application/json') |
|
821 | format_type = Unicode('application/json') | |
820 | _return_type = (list, dict) |
|
822 | _return_type = (list, dict) | |
821 |
|
823 | |||
822 | print_method = ObjectName('_repr_json_') |
|
824 | print_method = ObjectName('_repr_json_') | |
823 |
|
825 | |||
824 | def _check_return(self, r, obj): |
|
826 | def _check_return(self, r, obj): | |
825 | """Check that a return value is appropriate |
|
827 | """Check that a return value is appropriate | |
826 |
|
828 | |||
827 | Return the value if so, None otherwise, warning if invalid. |
|
829 | Return the value if so, None otherwise, warning if invalid. | |
828 | """ |
|
830 | """ | |
829 | if r is None: |
|
831 | if r is None: | |
830 | return |
|
832 | return | |
831 | md = None |
|
833 | md = None | |
832 | if isinstance(r, tuple): |
|
834 | if isinstance(r, tuple): | |
833 | # unpack data, metadata tuple for type checking on first element |
|
835 | # unpack data, metadata tuple for type checking on first element | |
834 | r, md = r |
|
836 | r, md = r | |
835 |
|
837 | |||
836 | # handle deprecated JSON-as-string form from IPython < 3 |
|
838 | # handle deprecated JSON-as-string form from IPython < 3 | |
837 | if isinstance(r, str): |
|
839 | if isinstance(r, str): | |
838 | warnings.warn("JSON expects JSONable list/dict containers, not JSON strings", |
|
840 | warnings.warn("JSON expects JSONable list/dict containers, not JSON strings", | |
839 | FormatterWarning) |
|
841 | FormatterWarning) | |
840 | r = json.loads(r) |
|
842 | r = json.loads(r) | |
841 |
|
843 | |||
842 | if md is not None: |
|
844 | if md is not None: | |
843 | # put the tuple back together |
|
845 | # put the tuple back together | |
844 | r = (r, md) |
|
846 | r = (r, md) | |
845 | return super(JSONFormatter, self)._check_return(r, obj) |
|
847 | return super(JSONFormatter, self)._check_return(r, obj) | |
846 |
|
848 | |||
847 |
|
849 | |||
848 | class JavascriptFormatter(BaseFormatter): |
|
850 | class JavascriptFormatter(BaseFormatter): | |
849 | """A Javascript formatter. |
|
851 | """A Javascript formatter. | |
850 |
|
852 | |||
851 | To define the callables that compute the Javascript representation of |
|
853 | To define the callables that compute the Javascript representation of | |
852 | your objects, define a :meth:`_repr_javascript_` method or use the |
|
854 | your objects, define a :meth:`_repr_javascript_` method or use the | |
853 | :meth:`for_type` or :meth:`for_type_by_name` methods to register functions |
|
855 | :meth:`for_type` or :meth:`for_type_by_name` methods to register functions | |
854 | that handle this. |
|
856 | that handle this. | |
855 |
|
857 | |||
856 | The return value of this formatter should be valid Javascript code and |
|
858 | The return value of this formatter should be valid Javascript code and | |
857 | should *not* be enclosed in ```<script>``` tags. |
|
859 | should *not* be enclosed in ```<script>``` tags. | |
858 | """ |
|
860 | """ | |
859 | format_type = Unicode('application/javascript') |
|
861 | format_type = Unicode('application/javascript') | |
860 |
|
862 | |||
861 | print_method = ObjectName('_repr_javascript_') |
|
863 | print_method = ObjectName('_repr_javascript_') | |
862 |
|
864 | |||
863 |
|
865 | |||
864 | class PDFFormatter(BaseFormatter): |
|
866 | class PDFFormatter(BaseFormatter): | |
865 | """A PDF formatter. |
|
867 | """A PDF formatter. | |
866 |
|
868 | |||
867 | To define the callables that compute the PDF representation of your |
|
869 | To define the callables that compute the PDF representation of your | |
868 | objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type` |
|
870 | objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type` | |
869 | or :meth:`for_type_by_name` methods to register functions that handle |
|
871 | or :meth:`for_type_by_name` methods to register functions that handle | |
870 | this. |
|
872 | this. | |
871 |
|
873 | |||
872 | The return value of this formatter should be raw PDF data, *not* |
|
874 | The return value of this formatter should be raw PDF data, *not* | |
873 | base64 encoded. |
|
875 | base64 encoded. | |
874 | """ |
|
876 | """ | |
875 | format_type = Unicode('application/pdf') |
|
877 | format_type = Unicode('application/pdf') | |
876 |
|
878 | |||
877 | print_method = ObjectName('_repr_pdf_') |
|
879 | print_method = ObjectName('_repr_pdf_') | |
878 |
|
880 | |||
879 | _return_type = (bytes, str) |
|
881 | _return_type = (bytes, str) | |
880 |
|
882 | |||
881 | class IPythonDisplayFormatter(BaseFormatter): |
|
883 | class IPythonDisplayFormatter(BaseFormatter): | |
882 | """An escape-hatch Formatter for objects that know how to display themselves. |
|
884 | """An escape-hatch Formatter for objects that know how to display themselves. | |
883 |
|
885 | |||
884 | To define the callables that compute the representation of your |
|
886 | To define the callables that compute the representation of your | |
885 | objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type` |
|
887 | objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type` | |
886 | or :meth:`for_type_by_name` methods to register functions that handle |
|
888 | or :meth:`for_type_by_name` methods to register functions that handle | |
887 | this. Unlike mime-type displays, this method should not return anything, |
|
889 | this. Unlike mime-type displays, this method should not return anything, | |
888 | instead calling any appropriate display methods itself. |
|
890 | instead calling any appropriate display methods itself. | |
889 |
|
891 | |||
890 | This display formatter has highest priority. |
|
892 | This display formatter has highest priority. | |
891 | If it fires, no other display formatter will be called. |
|
893 | If it fires, no other display formatter will be called. | |
892 |
|
894 | |||
893 | Prior to IPython 6.1, `_ipython_display_` was the only way to display custom mime-types |
|
895 | Prior to IPython 6.1, `_ipython_display_` was the only way to display custom mime-types | |
894 | without registering a new Formatter. |
|
896 | without registering a new Formatter. | |
895 |
|
897 | |||
896 | IPython 6.1 introduces `_repr_mimebundle_` for displaying custom mime-types, |
|
898 | IPython 6.1 introduces `_repr_mimebundle_` for displaying custom mime-types, | |
897 | so `_ipython_display_` should only be used for objects that require unusual |
|
899 | so `_ipython_display_` should only be used for objects that require unusual | |
898 | display patterns, such as multiple display calls. |
|
900 | display patterns, such as multiple display calls. | |
899 | """ |
|
901 | """ | |
900 | print_method = ObjectName('_ipython_display_') |
|
902 | print_method = ObjectName('_ipython_display_') | |
901 | _return_type = (type(None), bool) |
|
903 | _return_type = (type(None), bool) | |
902 |
|
904 | |||
903 | @catch_format_error |
|
905 | @catch_format_error | |
904 | def __call__(self, obj): |
|
906 | def __call__(self, obj): | |
905 | """Compute the format for an object.""" |
|
907 | """Compute the format for an object.""" | |
906 | if self.enabled: |
|
908 | if self.enabled: | |
907 | # lookup registered printer |
|
909 | # lookup registered printer | |
908 | try: |
|
910 | try: | |
909 | printer = self.lookup(obj) |
|
911 | printer = self.lookup(obj) | |
910 | except KeyError: |
|
912 | except KeyError: | |
911 | pass |
|
913 | pass | |
912 | else: |
|
914 | else: | |
913 | printer(obj) |
|
915 | printer(obj) | |
914 | return True |
|
916 | return True | |
915 | # Finally look for special method names |
|
917 | # Finally look for special method names | |
916 | method = get_real_method(obj, self.print_method) |
|
918 | method = get_real_method(obj, self.print_method) | |
917 | if method is not None: |
|
919 | if method is not None: | |
918 | method() |
|
920 | method() | |
919 | return True |
|
921 | return True | |
920 |
|
922 | |||
921 |
|
923 | |||
922 | class MimeBundleFormatter(BaseFormatter): |
|
924 | class MimeBundleFormatter(BaseFormatter): | |
923 | """A Formatter for arbitrary mime-types. |
|
925 | """A Formatter for arbitrary mime-types. | |
924 |
|
926 | |||
925 | Unlike other `_repr_<mimetype>_` methods, |
|
927 | Unlike other `_repr_<mimetype>_` methods, | |
926 | `_repr_mimebundle_` should return mime-bundle data, |
|
928 | `_repr_mimebundle_` should return mime-bundle data, | |
927 | either the mime-keyed `data` dictionary or the tuple `(data, metadata)`. |
|
929 | either the mime-keyed `data` dictionary or the tuple `(data, metadata)`. | |
928 | Any mime-type is valid. |
|
930 | Any mime-type is valid. | |
929 |
|
931 | |||
930 | To define the callables that compute the mime-bundle representation of your |
|
932 | To define the callables that compute the mime-bundle representation of your | |
931 | objects, define a :meth:`_repr_mimebundle_` method or use the :meth:`for_type` |
|
933 | objects, define a :meth:`_repr_mimebundle_` method or use the :meth:`for_type` | |
932 | or :meth:`for_type_by_name` methods to register functions that handle |
|
934 | or :meth:`for_type_by_name` methods to register functions that handle | |
933 | this. |
|
935 | this. | |
934 |
|
936 | |||
935 | .. versionadded:: 6.1 |
|
937 | .. versionadded:: 6.1 | |
936 | """ |
|
938 | """ | |
937 | print_method = ObjectName('_repr_mimebundle_') |
|
939 | print_method = ObjectName('_repr_mimebundle_') | |
938 | _return_type = dict |
|
940 | _return_type = dict | |
939 |
|
941 | |||
940 | def _check_return(self, r, obj): |
|
942 | def _check_return(self, r, obj): | |
941 | r = super(MimeBundleFormatter, self)._check_return(r, obj) |
|
943 | r = super(MimeBundleFormatter, self)._check_return(r, obj) | |
942 | # always return (data, metadata): |
|
944 | # always return (data, metadata): | |
943 | if r is None: |
|
945 | if r is None: | |
944 | return {}, {} |
|
946 | return {}, {} | |
945 | if not isinstance(r, tuple): |
|
947 | if not isinstance(r, tuple): | |
946 | return r, {} |
|
948 | return r, {} | |
947 | return r |
|
949 | return r | |
948 |
|
950 | |||
949 | @catch_format_error |
|
951 | @catch_format_error | |
950 | def __call__(self, obj, include=None, exclude=None): |
|
952 | def __call__(self, obj, include=None, exclude=None): | |
951 | """Compute the format for an object. |
|
953 | """Compute the format for an object. | |
952 |
|
954 | |||
953 | Identical to parent's method but we pass extra parameters to the method. |
|
955 | Identical to parent's method but we pass extra parameters to the method. | |
954 |
|
956 | |||
955 | Unlike other _repr_*_ `_repr_mimebundle_` should allow extra kwargs, in |
|
957 | Unlike other _repr_*_ `_repr_mimebundle_` should allow extra kwargs, in | |
956 | particular `include` and `exclude`. |
|
958 | particular `include` and `exclude`. | |
957 | """ |
|
959 | """ | |
958 | if self.enabled: |
|
960 | if self.enabled: | |
959 | # lookup registered printer |
|
961 | # lookup registered printer | |
960 | try: |
|
962 | try: | |
961 | printer = self.lookup(obj) |
|
963 | printer = self.lookup(obj) | |
962 | except KeyError: |
|
964 | except KeyError: | |
963 | pass |
|
965 | pass | |
964 | else: |
|
966 | else: | |
965 | return printer(obj) |
|
967 | return printer(obj) | |
966 | # Finally look for special method names |
|
968 | # Finally look for special method names | |
967 | method = get_real_method(obj, self.print_method) |
|
969 | method = get_real_method(obj, self.print_method) | |
968 |
|
970 | |||
969 | if method is not None: |
|
971 | if method is not None: | |
970 | return method(include=include, exclude=exclude) |
|
972 | return method(include=include, exclude=exclude) | |
971 | return None |
|
973 | return None | |
972 | else: |
|
974 | else: | |
973 | return None |
|
975 | return None | |
974 |
|
976 | |||
975 |
|
977 | |||
976 | FormatterABC.register(BaseFormatter) |
|
978 | FormatterABC.register(BaseFormatter) | |
977 | FormatterABC.register(PlainTextFormatter) |
|
979 | FormatterABC.register(PlainTextFormatter) | |
978 | FormatterABC.register(HTMLFormatter) |
|
980 | FormatterABC.register(HTMLFormatter) | |
979 | FormatterABC.register(MarkdownFormatter) |
|
981 | FormatterABC.register(MarkdownFormatter) | |
980 | FormatterABC.register(SVGFormatter) |
|
982 | FormatterABC.register(SVGFormatter) | |
981 | FormatterABC.register(PNGFormatter) |
|
983 | FormatterABC.register(PNGFormatter) | |
982 | FormatterABC.register(PDFFormatter) |
|
984 | FormatterABC.register(PDFFormatter) | |
983 | FormatterABC.register(JPEGFormatter) |
|
985 | FormatterABC.register(JPEGFormatter) | |
984 | FormatterABC.register(LatexFormatter) |
|
986 | FormatterABC.register(LatexFormatter) | |
985 | FormatterABC.register(JSONFormatter) |
|
987 | FormatterABC.register(JSONFormatter) | |
986 | FormatterABC.register(JavascriptFormatter) |
|
988 | FormatterABC.register(JavascriptFormatter) | |
987 | FormatterABC.register(IPythonDisplayFormatter) |
|
989 | FormatterABC.register(IPythonDisplayFormatter) | |
988 | FormatterABC.register(MimeBundleFormatter) |
|
990 | FormatterABC.register(MimeBundleFormatter) | |
989 |
|
991 | |||
990 |
|
992 | |||
991 | def format_display_data(obj, include=None, exclude=None): |
|
993 | def format_display_data(obj, include=None, exclude=None): | |
992 | """Return a format data dict for an object. |
|
994 | """Return a format data dict for an object. | |
993 |
|
995 | |||
994 | By default all format types will be computed. |
|
996 | By default all format types will be computed. | |
995 |
|
997 | |||
996 | Parameters |
|
998 | Parameters | |
997 | ---------- |
|
999 | ---------- | |
998 | obj : object |
|
1000 | obj : object | |
999 | The Python object whose format data will be computed. |
|
1001 | The Python object whose format data will be computed. | |
1000 |
|
1002 | |||
1001 | Returns |
|
1003 | Returns | |
1002 | ------- |
|
1004 | ------- | |
1003 | format_dict : dict |
|
1005 | format_dict : dict | |
1004 | A dictionary of key/value pairs, one or each format that was |
|
1006 | A dictionary of key/value pairs, one or each format that was | |
1005 | generated for the object. The keys are the format types, which |
|
1007 | generated for the object. The keys are the format types, which | |
1006 | will usually be MIME type strings and the values and JSON'able |
|
1008 | will usually be MIME type strings and the values and JSON'able | |
1007 | data structure containing the raw data for the representation in |
|
1009 | data structure containing the raw data for the representation in | |
1008 | that format. |
|
1010 | that format. | |
1009 | include : list or tuple, optional |
|
1011 | include : list or tuple, optional | |
1010 | A list of format type strings (MIME types) to include in the |
|
1012 | A list of format type strings (MIME types) to include in the | |
1011 | format data dict. If this is set *only* the format types included |
|
1013 | format data dict. If this is set *only* the format types included | |
1012 | in this list will be computed. |
|
1014 | in this list will be computed. | |
1013 | exclude : list or tuple, optional |
|
1015 | exclude : list or tuple, optional | |
1014 | A list of format type string (MIME types) to exclude in the format |
|
1016 | A list of format type string (MIME types) to exclude in the format | |
1015 | data dict. If this is set all format types will be computed, |
|
1017 | data dict. If this is set all format types will be computed, | |
1016 | except for those included in this argument. |
|
1018 | except for those included in this argument. | |
1017 | """ |
|
1019 | """ | |
1018 | from .interactiveshell import InteractiveShell |
|
1020 | from .interactiveshell import InteractiveShell | |
1019 |
|
1021 | |||
1020 | return InteractiveShell.instance().display_formatter.format( |
|
1022 | return InteractiveShell.instance().display_formatter.format( | |
1021 | obj, |
|
1023 | obj, | |
1022 | include, |
|
1024 | include, | |
1023 | exclude |
|
1025 | exclude | |
1024 | ) |
|
1026 | ) |
@@ -1,897 +1,907 b'' | |||||
1 | """ History related magics and functionality """ |
|
1 | """ History related magics and functionality """ | |
2 |
|
2 | |||
3 | # Copyright (c) IPython Development Team. |
|
3 | # Copyright (c) IPython Development Team. | |
4 | # Distributed under the terms of the Modified BSD License. |
|
4 | # Distributed under the terms of the Modified BSD License. | |
5 |
|
5 | |||
6 |
|
6 | |||
7 | import atexit |
|
7 | import atexit | |
8 | import datetime |
|
8 | import datetime | |
9 | from pathlib import Path |
|
9 | from pathlib import Path | |
10 | import re |
|
10 | import re | |
11 | import sqlite3 |
|
11 | import sqlite3 | |
12 | import threading |
|
12 | import threading | |
13 |
|
13 | |||
14 | from traitlets.config.configurable import LoggingConfigurable |
|
14 | from traitlets.config.configurable import LoggingConfigurable | |
15 | from decorator import decorator |
|
15 | from decorator import decorator | |
16 | from IPython.utils.decorators import undoc |
|
16 | from IPython.utils.decorators import undoc | |
17 | from IPython.paths import locate_profile |
|
17 | from IPython.paths import locate_profile | |
18 | from traitlets import ( |
|
18 | from traitlets import ( | |
19 | Any, |
|
19 | Any, | |
20 | Bool, |
|
20 | Bool, | |
21 | Dict, |
|
21 | Dict, | |
22 | Instance, |
|
22 | Instance, | |
23 | Integer, |
|
23 | Integer, | |
24 | List, |
|
24 | List, | |
25 | Unicode, |
|
25 | Unicode, | |
26 | Union, |
|
26 | Union, | |
27 | TraitError, |
|
27 | TraitError, | |
28 | default, |
|
28 | default, | |
29 | observe, |
|
29 | observe, | |
30 | ) |
|
30 | ) | |
31 |
|
31 | |||
32 | #----------------------------------------------------------------------------- |
|
32 | #----------------------------------------------------------------------------- | |
33 | # Classes and functions |
|
33 | # Classes and functions | |
34 | #----------------------------------------------------------------------------- |
|
34 | #----------------------------------------------------------------------------- | |
35 |
|
35 | |||
36 | @undoc |
|
36 | @undoc | |
37 | class DummyDB(object): |
|
37 | class DummyDB(object): | |
38 | """Dummy DB that will act as a black hole for history. |
|
38 | """Dummy DB that will act as a black hole for history. | |
39 |
|
39 | |||
40 | Only used in the absence of sqlite""" |
|
40 | Only used in the absence of sqlite""" | |
41 | def execute(*args, **kwargs): |
|
41 | def execute(*args, **kwargs): | |
42 | return [] |
|
42 | return [] | |
43 |
|
43 | |||
44 | def commit(self, *args, **kwargs): |
|
44 | def commit(self, *args, **kwargs): | |
45 | pass |
|
45 | pass | |
46 |
|
46 | |||
47 | def __enter__(self, *args, **kwargs): |
|
47 | def __enter__(self, *args, **kwargs): | |
48 | pass |
|
48 | pass | |
49 |
|
49 | |||
50 | def __exit__(self, *args, **kwargs): |
|
50 | def __exit__(self, *args, **kwargs): | |
51 | pass |
|
51 | pass | |
52 |
|
52 | |||
53 |
|
53 | |||
54 | @decorator |
|
54 | @decorator | |
55 | def only_when_enabled(f, self, *a, **kw): |
|
55 | def only_when_enabled(f, self, *a, **kw): | |
56 | """Decorator: return an empty list in the absence of sqlite.""" |
|
56 | """Decorator: return an empty list in the absence of sqlite.""" | |
57 | if not self.enabled: |
|
57 | if not self.enabled: | |
58 | return [] |
|
58 | return [] | |
59 | else: |
|
59 | else: | |
60 | return f(self, *a, **kw) |
|
60 | return f(self, *a, **kw) | |
61 |
|
61 | |||
62 |
|
62 | |||
63 | # use 16kB as threshold for whether a corrupt history db should be saved |
|
63 | # use 16kB as threshold for whether a corrupt history db should be saved | |
64 | # that should be at least 100 entries or so |
|
64 | # that should be at least 100 entries or so | |
65 | _SAVE_DB_SIZE = 16384 |
|
65 | _SAVE_DB_SIZE = 16384 | |
66 |
|
66 | |||
67 | @decorator |
|
67 | @decorator | |
68 | def catch_corrupt_db(f, self, *a, **kw): |
|
68 | def catch_corrupt_db(f, self, *a, **kw): | |
69 | """A decorator which wraps HistoryAccessor method calls to catch errors from |
|
69 | """A decorator which wraps HistoryAccessor method calls to catch errors from | |
70 | a corrupt SQLite database, move the old database out of the way, and create |
|
70 | a corrupt SQLite database, move the old database out of the way, and create | |
71 | a new one. |
|
71 | a new one. | |
72 |
|
72 | |||
73 | We avoid clobbering larger databases because this may be triggered due to filesystem issues, |
|
73 | We avoid clobbering larger databases because this may be triggered due to filesystem issues, | |
74 | not just a corrupt file. |
|
74 | not just a corrupt file. | |
75 | """ |
|
75 | """ | |
76 | try: |
|
76 | try: | |
77 | return f(self, *a, **kw) |
|
77 | return f(self, *a, **kw) | |
78 | except (sqlite3.DatabaseError, sqlite3.OperationalError) as e: |
|
78 | except (sqlite3.DatabaseError, sqlite3.OperationalError) as e: | |
79 | self._corrupt_db_counter += 1 |
|
79 | self._corrupt_db_counter += 1 | |
80 | self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e) |
|
80 | self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e) | |
81 | if self.hist_file != ':memory:': |
|
81 | if self.hist_file != ':memory:': | |
82 | if self._corrupt_db_counter > self._corrupt_db_limit: |
|
82 | if self._corrupt_db_counter > self._corrupt_db_limit: | |
83 | self.hist_file = ':memory:' |
|
83 | self.hist_file = ':memory:' | |
84 | self.log.error("Failed to load history too many times, history will not be saved.") |
|
84 | self.log.error("Failed to load history too many times, history will not be saved.") | |
85 | elif self.hist_file.is_file(): |
|
85 | elif self.hist_file.is_file(): | |
86 | # move the file out of the way |
|
86 | # move the file out of the way | |
87 | base = str(self.hist_file.parent / self.hist_file.stem) |
|
87 | base = str(self.hist_file.parent / self.hist_file.stem) | |
88 | ext = self.hist_file.suffix |
|
88 | ext = self.hist_file.suffix | |
89 | size = self.hist_file.stat().st_size |
|
89 | size = self.hist_file.stat().st_size | |
90 | if size >= _SAVE_DB_SIZE: |
|
90 | if size >= _SAVE_DB_SIZE: | |
91 | # if there's significant content, avoid clobbering |
|
91 | # if there's significant content, avoid clobbering | |
92 | now = datetime.datetime.now().isoformat().replace(':', '.') |
|
92 | now = datetime.datetime.now().isoformat().replace(':', '.') | |
93 | newpath = base + '-corrupt-' + now + ext |
|
93 | newpath = base + '-corrupt-' + now + ext | |
94 | # don't clobber previous corrupt backups |
|
94 | # don't clobber previous corrupt backups | |
95 | for i in range(100): |
|
95 | for i in range(100): | |
96 | if not Path(newpath).exists(): |
|
96 | if not Path(newpath).exists(): | |
97 | break |
|
97 | break | |
98 | else: |
|
98 | else: | |
99 | newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext |
|
99 | newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext | |
100 | else: |
|
100 | else: | |
101 | # not much content, possibly empty; don't worry about clobbering |
|
101 | # not much content, possibly empty; don't worry about clobbering | |
102 | # maybe we should just delete it? |
|
102 | # maybe we should just delete it? | |
103 | newpath = base + '-corrupt' + ext |
|
103 | newpath = base + '-corrupt' + ext | |
104 | self.hist_file.rename(newpath) |
|
104 | self.hist_file.rename(newpath) | |
105 | self.log.error("History file was moved to %s and a new file created.", newpath) |
|
105 | self.log.error("History file was moved to %s and a new file created.", newpath) | |
106 | self.init_db() |
|
106 | self.init_db() | |
107 | return [] |
|
107 | return [] | |
108 | else: |
|
108 | else: | |
109 | # Failed with :memory:, something serious is wrong |
|
109 | # Failed with :memory:, something serious is wrong | |
110 | raise |
|
110 | raise | |
111 |
|
111 | |||
112 |
|
112 | |||
113 | class HistoryAccessorBase(LoggingConfigurable): |
|
113 | class HistoryAccessorBase(LoggingConfigurable): | |
114 | """An abstract class for History Accessors """ |
|
114 | """An abstract class for History Accessors """ | |
115 |
|
115 | |||
116 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): |
|
116 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): | |
117 | raise NotImplementedError |
|
117 | raise NotImplementedError | |
118 |
|
118 | |||
119 | def search(self, pattern="*", raw=True, search_raw=True, |
|
119 | def search(self, pattern="*", raw=True, search_raw=True, | |
120 | output=False, n=None, unique=False): |
|
120 | output=False, n=None, unique=False): | |
121 | raise NotImplementedError |
|
121 | raise NotImplementedError | |
122 |
|
122 | |||
123 | def get_range(self, session, start=1, stop=None, raw=True,output=False): |
|
123 | def get_range(self, session, start=1, stop=None, raw=True,output=False): | |
124 | raise NotImplementedError |
|
124 | raise NotImplementedError | |
125 |
|
125 | |||
126 | def get_range_by_str(self, rangestr, raw=True, output=False): |
|
126 | def get_range_by_str(self, rangestr, raw=True, output=False): | |
127 | raise NotImplementedError |
|
127 | raise NotImplementedError | |
128 |
|
128 | |||
129 |
|
129 | |||
130 | class HistoryAccessor(HistoryAccessorBase): |
|
130 | class HistoryAccessor(HistoryAccessorBase): | |
131 | """Access the history database without adding to it. |
|
131 | """Access the history database without adding to it. | |
132 |
|
132 | |||
133 | This is intended for use by standalone history tools. IPython shells use |
|
133 | This is intended for use by standalone history tools. IPython shells use | |
134 | HistoryManager, below, which is a subclass of this.""" |
|
134 | HistoryManager, below, which is a subclass of this.""" | |
135 |
|
135 | |||
136 | # counter for init_db retries, so we don't keep trying over and over |
|
136 | # counter for init_db retries, so we don't keep trying over and over | |
137 | _corrupt_db_counter = 0 |
|
137 | _corrupt_db_counter = 0 | |
138 | # after two failures, fallback on :memory: |
|
138 | # after two failures, fallback on :memory: | |
139 | _corrupt_db_limit = 2 |
|
139 | _corrupt_db_limit = 2 | |
140 |
|
140 | |||
141 | # String holding the path to the history file |
|
141 | # String holding the path to the history file | |
142 | hist_file = Union( |
|
142 | hist_file = Union( | |
143 | [Instance(Path), Unicode()], |
|
143 | [Instance(Path), Unicode()], | |
144 | help="""Path to file to use for SQLite history database. |
|
144 | help="""Path to file to use for SQLite history database. | |
145 |
|
145 | |||
146 | By default, IPython will put the history database in the IPython |
|
146 | By default, IPython will put the history database in the IPython | |
147 | profile directory. If you would rather share one history among |
|
147 | profile directory. If you would rather share one history among | |
148 | profiles, you can set this value in each, so that they are consistent. |
|
148 | profiles, you can set this value in each, so that they are consistent. | |
149 |
|
149 | |||
150 | Due to an issue with fcntl, SQLite is known to misbehave on some NFS |
|
150 | Due to an issue with fcntl, SQLite is known to misbehave on some NFS | |
151 | mounts. If you see IPython hanging, try setting this to something on a |
|
151 | mounts. If you see IPython hanging, try setting this to something on a | |
152 | local disk, e.g:: |
|
152 | local disk, e.g:: | |
153 |
|
153 | |||
154 | ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite |
|
154 | ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite | |
155 |
|
155 | |||
156 | you can also use the specific value `:memory:` (including the colon |
|
156 | you can also use the specific value `:memory:` (including the colon | |
157 | at both end but not the back ticks), to avoid creating an history file. |
|
157 | at both end but not the back ticks), to avoid creating an history file. | |
158 |
|
158 | |||
159 | """, |
|
159 | """, | |
160 | ).tag(config=True) |
|
160 | ).tag(config=True) | |
161 |
|
161 | |||
162 | enabled = Bool(True, |
|
162 | enabled = Bool(True, | |
163 | help="""enable the SQLite history |
|
163 | help="""enable the SQLite history | |
164 |
|
164 | |||
165 | set enabled=False to disable the SQLite history, |
|
165 | set enabled=False to disable the SQLite history, | |
166 | in which case there will be no stored history, no SQLite connection, |
|
166 | in which case there will be no stored history, no SQLite connection, | |
167 | and no background saving thread. This may be necessary in some |
|
167 | and no background saving thread. This may be necessary in some | |
168 | threaded environments where IPython is embedded. |
|
168 | threaded environments where IPython is embedded. | |
169 | """ |
|
169 | """ | |
170 | ).tag(config=True) |
|
170 | ).tag(config=True) | |
171 |
|
171 | |||
172 | connection_options = Dict( |
|
172 | connection_options = Dict( | |
173 | help="""Options for configuring the SQLite connection |
|
173 | help="""Options for configuring the SQLite connection | |
174 |
|
174 | |||
175 | These options are passed as keyword args to sqlite3.connect |
|
175 | These options are passed as keyword args to sqlite3.connect | |
176 | when establishing database connections. |
|
176 | when establishing database connections. | |
177 | """ |
|
177 | """ | |
178 | ).tag(config=True) |
|
178 | ).tag(config=True) | |
179 |
|
179 | |||
180 | # The SQLite database |
|
180 | # The SQLite database | |
181 | db = Any() |
|
181 | db = Any() | |
182 | @observe('db') |
|
182 | @observe('db') | |
183 | def _db_changed(self, change): |
|
183 | def _db_changed(self, change): | |
184 | """validate the db, since it can be an Instance of two different types""" |
|
184 | """validate the db, since it can be an Instance of two different types""" | |
185 | new = change['new'] |
|
185 | new = change['new'] | |
186 | connection_types = (DummyDB, sqlite3.Connection) |
|
186 | connection_types = (DummyDB, sqlite3.Connection) | |
187 | if not isinstance(new, connection_types): |
|
187 | if not isinstance(new, connection_types): | |
188 | msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \ |
|
188 | msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \ | |
189 | (self.__class__.__name__, new) |
|
189 | (self.__class__.__name__, new) | |
190 | raise TraitError(msg) |
|
190 | raise TraitError(msg) | |
191 |
|
191 | |||
192 | def __init__(self, profile="default", hist_file="", **traits): |
|
192 | def __init__(self, profile="default", hist_file="", **traits): | |
193 | """Create a new history accessor. |
|
193 | """Create a new history accessor. | |
194 |
|
194 | |||
195 | Parameters |
|
195 | Parameters | |
196 | ---------- |
|
196 | ---------- | |
197 | profile : str |
|
197 | profile : str | |
198 | The name of the profile from which to open history. |
|
198 | The name of the profile from which to open history. | |
199 | hist_file : str |
|
199 | hist_file : str | |
200 | Path to an SQLite history database stored by IPython. If specified, |
|
200 | Path to an SQLite history database stored by IPython. If specified, | |
201 | hist_file overrides profile. |
|
201 | hist_file overrides profile. | |
202 | config : :class:`~traitlets.config.loader.Config` |
|
202 | config : :class:`~traitlets.config.loader.Config` | |
203 | Config object. hist_file can also be set through this. |
|
203 | Config object. hist_file can also be set through this. | |
204 | """ |
|
204 | """ | |
205 | # We need a pointer back to the shell for various tasks. |
|
205 | # We need a pointer back to the shell for various tasks. | |
206 | super(HistoryAccessor, self).__init__(**traits) |
|
206 | super(HistoryAccessor, self).__init__(**traits) | |
207 | # defer setting hist_file from kwarg until after init, |
|
207 | # defer setting hist_file from kwarg until after init, | |
208 | # otherwise the default kwarg value would clobber any value |
|
208 | # otherwise the default kwarg value would clobber any value | |
209 | # set by config |
|
209 | # set by config | |
210 | if hist_file: |
|
210 | if hist_file: | |
211 | self.hist_file = hist_file |
|
211 | self.hist_file = hist_file | |
212 |
|
212 | |||
213 | try: |
|
213 | try: | |
214 | self.hist_file |
|
214 | self.hist_file | |
215 | except TraitError: |
|
215 | except TraitError: | |
216 | # No one has set the hist_file, yet. |
|
216 | # No one has set the hist_file, yet. | |
217 | self.hist_file = self._get_hist_file_name(profile) |
|
217 | self.hist_file = self._get_hist_file_name(profile) | |
218 |
|
218 | |||
219 | self.init_db() |
|
219 | self.init_db() | |
220 |
|
220 | |||
221 | def _get_hist_file_name(self, profile='default'): |
|
221 | def _get_hist_file_name(self, profile='default'): | |
222 | """Find the history file for the given profile name. |
|
222 | """Find the history file for the given profile name. | |
223 |
|
223 | |||
224 | This is overridden by the HistoryManager subclass, to use the shell's |
|
224 | This is overridden by the HistoryManager subclass, to use the shell's | |
225 | active profile. |
|
225 | active profile. | |
226 |
|
226 | |||
227 | Parameters |
|
227 | Parameters | |
228 | ---------- |
|
228 | ---------- | |
229 | profile : str |
|
229 | profile : str | |
230 | The name of a profile which has a history file. |
|
230 | The name of a profile which has a history file. | |
231 | """ |
|
231 | """ | |
232 | return Path(locate_profile(profile)) / "history.sqlite" |
|
232 | return Path(locate_profile(profile)) / "history.sqlite" | |
233 |
|
233 | |||
234 | @catch_corrupt_db |
|
234 | @catch_corrupt_db | |
235 | def init_db(self): |
|
235 | def init_db(self): | |
236 | """Connect to the database, and create tables if necessary.""" |
|
236 | """Connect to the database, and create tables if necessary.""" | |
237 | if not self.enabled: |
|
237 | if not self.enabled: | |
238 | self.db = DummyDB() |
|
238 | self.db = DummyDB() | |
239 | return |
|
239 | return | |
240 |
|
240 | |||
241 | # use detect_types so that timestamps return datetime objects |
|
241 | # use detect_types so that timestamps return datetime objects | |
242 | kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) |
|
242 | kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) | |
243 | kwargs.update(self.connection_options) |
|
243 | kwargs.update(self.connection_options) | |
244 | self.db = sqlite3.connect(str(self.hist_file), **kwargs) |
|
244 | self.db = sqlite3.connect(str(self.hist_file), **kwargs) | |
245 | self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer |
|
245 | self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer | |
246 | primary key autoincrement, start timestamp, |
|
246 | primary key autoincrement, start timestamp, | |
247 | end timestamp, num_cmds integer, remark text)""") |
|
247 | end timestamp, num_cmds integer, remark text)""") | |
248 | self.db.execute("""CREATE TABLE IF NOT EXISTS history |
|
248 | self.db.execute("""CREATE TABLE IF NOT EXISTS history | |
249 | (session integer, line integer, source text, source_raw text, |
|
249 | (session integer, line integer, source text, source_raw text, | |
250 | PRIMARY KEY (session, line))""") |
|
250 | PRIMARY KEY (session, line))""") | |
251 | # Output history is optional, but ensure the table's there so it can be |
|
251 | # Output history is optional, but ensure the table's there so it can be | |
252 | # enabled later. |
|
252 | # enabled later. | |
253 | self.db.execute("""CREATE TABLE IF NOT EXISTS output_history |
|
253 | self.db.execute("""CREATE TABLE IF NOT EXISTS output_history | |
254 | (session integer, line integer, output text, |
|
254 | (session integer, line integer, output text, | |
255 | PRIMARY KEY (session, line))""") |
|
255 | PRIMARY KEY (session, line))""") | |
256 | self.db.commit() |
|
256 | self.db.commit() | |
257 | # success! reset corrupt db count |
|
257 | # success! reset corrupt db count | |
258 | self._corrupt_db_counter = 0 |
|
258 | self._corrupt_db_counter = 0 | |
259 |
|
259 | |||
260 | def writeout_cache(self): |
|
260 | def writeout_cache(self): | |
261 | """Overridden by HistoryManager to dump the cache before certain |
|
261 | """Overridden by HistoryManager to dump the cache before certain | |
262 | database lookups.""" |
|
262 | database lookups.""" | |
263 | pass |
|
263 | pass | |
264 |
|
264 | |||
265 | ## ------------------------------- |
|
265 | ## ------------------------------- | |
266 | ## Methods for retrieving history: |
|
266 | ## Methods for retrieving history: | |
267 | ## ------------------------------- |
|
267 | ## ------------------------------- | |
268 | def _run_sql(self, sql, params, raw=True, output=False): |
|
268 | def _run_sql(self, sql, params, raw=True, output=False): | |
269 | """Prepares and runs an SQL query for the history database. |
|
269 | """Prepares and runs an SQL query for the history database. | |
270 |
|
270 | |||
271 | Parameters |
|
271 | Parameters | |
272 | ---------- |
|
272 | ---------- | |
273 | sql : str |
|
273 | sql : str | |
274 | Any filtering expressions to go after SELECT ... FROM ... |
|
274 | Any filtering expressions to go after SELECT ... FROM ... | |
275 | params : tuple |
|
275 | params : tuple | |
276 | Parameters passed to the SQL query (to replace "?") |
|
276 | Parameters passed to the SQL query (to replace "?") | |
277 | raw, output : bool |
|
277 | raw, output : bool | |
278 | See :meth:`get_range` |
|
278 | See :meth:`get_range` | |
279 |
|
279 | |||
280 | Returns |
|
280 | Returns | |
281 | ------- |
|
281 | ------- | |
282 | Tuples as :meth:`get_range` |
|
282 | Tuples as :meth:`get_range` | |
283 | """ |
|
283 | """ | |
284 | toget = 'source_raw' if raw else 'source' |
|
284 | toget = 'source_raw' if raw else 'source' | |
285 | sqlfrom = "history" |
|
285 | sqlfrom = "history" | |
286 | if output: |
|
286 | if output: | |
287 | sqlfrom = "history LEFT JOIN output_history USING (session, line)" |
|
287 | sqlfrom = "history LEFT JOIN output_history USING (session, line)" | |
288 | toget = "history.%s, output_history.output" % toget |
|
288 | toget = "history.%s, output_history.output" % toget | |
289 | cur = self.db.execute("SELECT session, line, %s FROM %s " %\ |
|
289 | cur = self.db.execute("SELECT session, line, %s FROM %s " %\ | |
290 | (toget, sqlfrom) + sql, params) |
|
290 | (toget, sqlfrom) + sql, params) | |
291 | if output: # Regroup into 3-tuples, and parse JSON |
|
291 | if output: # Regroup into 3-tuples, and parse JSON | |
292 | return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) |
|
292 | return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) | |
293 | return cur |
|
293 | return cur | |
294 |
|
294 | |||
295 | @only_when_enabled |
|
295 | @only_when_enabled | |
296 | @catch_corrupt_db |
|
296 | @catch_corrupt_db | |
297 | def get_session_info(self, session): |
|
297 | def get_session_info(self, session): | |
298 | """Get info about a session. |
|
298 | """Get info about a session. | |
299 |
|
299 | |||
300 | Parameters |
|
300 | Parameters | |
301 | ---------- |
|
301 | ---------- | |
302 |
|
302 | |||
303 | session : int |
|
303 | session : int | |
304 | Session number to retrieve. |
|
304 | Session number to retrieve. | |
305 |
|
305 | |||
306 | Returns |
|
306 | Returns | |
307 | ------- |
|
307 | ------- | |
308 |
|
308 | |||
309 | session_id : int |
|
309 | session_id : int | |
310 | Session ID number |
|
310 | Session ID number | |
311 | start : datetime |
|
311 | start : datetime | |
312 | Timestamp for the start of the session. |
|
312 | Timestamp for the start of the session. | |
313 | end : datetime |
|
313 | end : datetime | |
314 | Timestamp for the end of the session, or None if IPython crashed. |
|
314 | Timestamp for the end of the session, or None if IPython crashed. | |
315 | num_cmds : int |
|
315 | num_cmds : int | |
316 | Number of commands run, or None if IPython crashed. |
|
316 | Number of commands run, or None if IPython crashed. | |
317 | remark : unicode |
|
317 | remark : unicode | |
318 | A manually set description. |
|
318 | A manually set description. | |
319 | """ |
|
319 | """ | |
320 | query = "SELECT * from sessions where session == ?" |
|
320 | query = "SELECT * from sessions where session == ?" | |
321 | return self.db.execute(query, (session,)).fetchone() |
|
321 | return self.db.execute(query, (session,)).fetchone() | |
322 |
|
322 | |||
323 | @catch_corrupt_db |
|
323 | @catch_corrupt_db | |
324 | def get_last_session_id(self): |
|
324 | def get_last_session_id(self): | |
325 | """Get the last session ID currently in the database. |
|
325 | """Get the last session ID currently in the database. | |
326 |
|
326 | |||
327 | Within IPython, this should be the same as the value stored in |
|
327 | Within IPython, this should be the same as the value stored in | |
328 | :attr:`HistoryManager.session_number`. |
|
328 | :attr:`HistoryManager.session_number`. | |
329 | """ |
|
329 | """ | |
330 | for record in self.get_tail(n=1, include_latest=True): |
|
330 | for record in self.get_tail(n=1, include_latest=True): | |
331 | return record[0] |
|
331 | return record[0] | |
332 |
|
332 | |||
333 | @catch_corrupt_db |
|
333 | @catch_corrupt_db | |
334 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): |
|
334 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): | |
335 | """Get the last n lines from the history database. |
|
335 | """Get the last n lines from the history database. | |
336 |
|
336 | |||
337 | Parameters |
|
337 | Parameters | |
338 | ---------- |
|
338 | ---------- | |
339 | n : int |
|
339 | n : int | |
340 | The number of lines to get |
|
340 | The number of lines to get | |
341 | raw, output : bool |
|
341 | raw, output : bool | |
342 | See :meth:`get_range` |
|
342 | See :meth:`get_range` | |
343 | include_latest : bool |
|
343 | include_latest : bool | |
344 | If False (default), n+1 lines are fetched, and the latest one |
|
344 | If False (default), n+1 lines are fetched, and the latest one | |
345 | is discarded. This is intended to be used where the function |
|
345 | is discarded. This is intended to be used where the function | |
346 | is called by a user command, which it should not return. |
|
346 | is called by a user command, which it should not return. | |
347 |
|
347 | |||
348 | Returns |
|
348 | Returns | |
349 | ------- |
|
349 | ------- | |
350 | Tuples as :meth:`get_range` |
|
350 | Tuples as :meth:`get_range` | |
351 | """ |
|
351 | """ | |
352 | self.writeout_cache() |
|
352 | self.writeout_cache() | |
353 | if not include_latest: |
|
353 | if not include_latest: | |
354 | n += 1 |
|
354 | n += 1 | |
355 | cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?", |
|
355 | cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?", | |
356 | (n,), raw=raw, output=output) |
|
356 | (n,), raw=raw, output=output) | |
357 | if not include_latest: |
|
357 | if not include_latest: | |
358 | return reversed(list(cur)[1:]) |
|
358 | return reversed(list(cur)[1:]) | |
359 | return reversed(list(cur)) |
|
359 | return reversed(list(cur)) | |
360 |
|
360 | |||
361 | @catch_corrupt_db |
|
361 | @catch_corrupt_db | |
362 | def search(self, pattern="*", raw=True, search_raw=True, |
|
362 | def search(self, pattern="*", raw=True, search_raw=True, | |
363 | output=False, n=None, unique=False): |
|
363 | output=False, n=None, unique=False): | |
364 | """Search the database using unix glob-style matching (wildcards |
|
364 | """Search the database using unix glob-style matching (wildcards | |
365 | * and ?). |
|
365 | * and ?). | |
366 |
|
366 | |||
367 | Parameters |
|
367 | Parameters | |
368 | ---------- |
|
368 | ---------- | |
369 | pattern : str |
|
369 | pattern : str | |
370 | The wildcarded pattern to match when searching |
|
370 | The wildcarded pattern to match when searching | |
371 | search_raw : bool |
|
371 | search_raw : bool | |
372 | If True, search the raw input, otherwise, the parsed input |
|
372 | If True, search the raw input, otherwise, the parsed input | |
373 | raw, output : bool |
|
373 | raw, output : bool | |
374 | See :meth:`get_range` |
|
374 | See :meth:`get_range` | |
375 | n : None or int |
|
375 | n : None or int | |
376 | If an integer is given, it defines the limit of |
|
376 | If an integer is given, it defines the limit of | |
377 | returned entries. |
|
377 | returned entries. | |
378 | unique : bool |
|
378 | unique : bool | |
379 | When it is true, return only unique entries. |
|
379 | When it is true, return only unique entries. | |
380 |
|
380 | |||
381 | Returns |
|
381 | Returns | |
382 | ------- |
|
382 | ------- | |
383 | Tuples as :meth:`get_range` |
|
383 | Tuples as :meth:`get_range` | |
384 | """ |
|
384 | """ | |
385 | tosearch = "source_raw" if search_raw else "source" |
|
385 | tosearch = "source_raw" if search_raw else "source" | |
386 | if output: |
|
386 | if output: | |
387 | tosearch = "history." + tosearch |
|
387 | tosearch = "history." + tosearch | |
388 | self.writeout_cache() |
|
388 | self.writeout_cache() | |
389 | sqlform = "WHERE %s GLOB ?" % tosearch |
|
389 | sqlform = "WHERE %s GLOB ?" % tosearch | |
390 | params = (pattern,) |
|
390 | params = (pattern,) | |
391 | if unique: |
|
391 | if unique: | |
392 | sqlform += ' GROUP BY {0}'.format(tosearch) |
|
392 | sqlform += ' GROUP BY {0}'.format(tosearch) | |
393 | if n is not None: |
|
393 | if n is not None: | |
394 | sqlform += " ORDER BY session DESC, line DESC LIMIT ?" |
|
394 | sqlform += " ORDER BY session DESC, line DESC LIMIT ?" | |
395 | params += (n,) |
|
395 | params += (n,) | |
396 | elif unique: |
|
396 | elif unique: | |
397 | sqlform += " ORDER BY session, line" |
|
397 | sqlform += " ORDER BY session, line" | |
398 | cur = self._run_sql(sqlform, params, raw=raw, output=output) |
|
398 | cur = self._run_sql(sqlform, params, raw=raw, output=output) | |
399 | if n is not None: |
|
399 | if n is not None: | |
400 | return reversed(list(cur)) |
|
400 | return reversed(list(cur)) | |
401 | return cur |
|
401 | return cur | |
402 |
|
402 | |||
403 | @catch_corrupt_db |
|
403 | @catch_corrupt_db | |
404 | def get_range(self, session, start=1, stop=None, raw=True,output=False): |
|
404 | def get_range(self, session, start=1, stop=None, raw=True,output=False): | |
405 | """Retrieve input by session. |
|
405 | """Retrieve input by session. | |
406 |
|
406 | |||
407 | Parameters |
|
407 | Parameters | |
408 | ---------- |
|
408 | ---------- | |
409 | session : int |
|
409 | session : int | |
410 | Session number to retrieve. |
|
410 | Session number to retrieve. | |
411 | start : int |
|
411 | start : int | |
412 | First line to retrieve. |
|
412 | First line to retrieve. | |
413 | stop : int |
|
413 | stop : int | |
414 | End of line range (excluded from output itself). If None, retrieve |
|
414 | End of line range (excluded from output itself). If None, retrieve | |
415 | to the end of the session. |
|
415 | to the end of the session. | |
416 | raw : bool |
|
416 | raw : bool | |
417 | If True, return untranslated input |
|
417 | If True, return untranslated input | |
418 | output : bool |
|
418 | output : bool | |
419 | If True, attempt to include output. This will be 'real' Python |
|
419 | If True, attempt to include output. This will be 'real' Python | |
420 | objects for the current session, or text reprs from previous |
|
420 | objects for the current session, or text reprs from previous | |
421 | sessions if db_log_output was enabled at the time. Where no output |
|
421 | sessions if db_log_output was enabled at the time. Where no output | |
422 | is found, None is used. |
|
422 | is found, None is used. | |
423 |
|
423 | |||
424 | Returns |
|
424 | Returns | |
425 | ------- |
|
425 | ------- | |
426 | entries |
|
426 | entries | |
427 | An iterator over the desired lines. Each line is a 3-tuple, either |
|
427 | An iterator over the desired lines. Each line is a 3-tuple, either | |
428 | (session, line, input) if output is False, or |
|
428 | (session, line, input) if output is False, or | |
429 | (session, line, (input, output)) if output is True. |
|
429 | (session, line, (input, output)) if output is True. | |
430 | """ |
|
430 | """ | |
431 | if stop: |
|
431 | if stop: | |
432 | lineclause = "line >= ? AND line < ?" |
|
432 | lineclause = "line >= ? AND line < ?" | |
433 | params = (session, start, stop) |
|
433 | params = (session, start, stop) | |
434 | else: |
|
434 | else: | |
435 | lineclause = "line>=?" |
|
435 | lineclause = "line>=?" | |
436 | params = (session, start) |
|
436 | params = (session, start) | |
437 |
|
437 | |||
438 | return self._run_sql("WHERE session==? AND %s" % lineclause, |
|
438 | return self._run_sql("WHERE session==? AND %s" % lineclause, | |
439 | params, raw=raw, output=output) |
|
439 | params, raw=raw, output=output) | |
440 |
|
440 | |||
441 | def get_range_by_str(self, rangestr, raw=True, output=False): |
|
441 | def get_range_by_str(self, rangestr, raw=True, output=False): | |
442 | """Get lines of history from a string of ranges, as used by magic |
|
442 | """Get lines of history from a string of ranges, as used by magic | |
443 | commands %hist, %save, %macro, etc. |
|
443 | commands %hist, %save, %macro, etc. | |
444 |
|
444 | |||
445 | Parameters |
|
445 | Parameters | |
446 | ---------- |
|
446 | ---------- | |
447 | rangestr : str |
|
447 | rangestr : str | |
448 |
A string specifying ranges, e.g. "5 ~2/1-4". |
|
448 | A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used, | |
449 | :func:`magic_history` for full details. |
|
449 | this will return everything from current session's history. | |
|
450 | ||||
|
451 | See the documentation of :func:`%history` for the full details. | |||
|
452 | ||||
450 | raw, output : bool |
|
453 | raw, output : bool | |
451 | As :meth:`get_range` |
|
454 | As :meth:`get_range` | |
452 |
|
455 | |||
453 | Returns |
|
456 | Returns | |
454 | ------- |
|
457 | ------- | |
455 | Tuples as :meth:`get_range` |
|
458 | Tuples as :meth:`get_range` | |
456 | """ |
|
459 | """ | |
457 | for sess, s, e in extract_hist_ranges(rangestr): |
|
460 | for sess, s, e in extract_hist_ranges(rangestr): | |
458 | for line in self.get_range(sess, s, e, raw=raw, output=output): |
|
461 | for line in self.get_range(sess, s, e, raw=raw, output=output): | |
459 | yield line |
|
462 | yield line | |
460 |
|
463 | |||
461 |
|
464 | |||
462 | class HistoryManager(HistoryAccessor): |
|
465 | class HistoryManager(HistoryAccessor): | |
463 | """A class to organize all history-related functionality in one place. |
|
466 | """A class to organize all history-related functionality in one place. | |
464 | """ |
|
467 | """ | |
465 | # Public interface |
|
468 | # Public interface | |
466 |
|
469 | |||
467 | # An instance of the IPython shell we are attached to |
|
470 | # An instance of the IPython shell we are attached to | |
468 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', |
|
471 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', | |
469 | allow_none=True) |
|
472 | allow_none=True) | |
470 | # Lists to hold processed and raw history. These start with a blank entry |
|
473 | # Lists to hold processed and raw history. These start with a blank entry | |
471 | # so that we can index them starting from 1 |
|
474 | # so that we can index them starting from 1 | |
472 | input_hist_parsed = List([""]) |
|
475 | input_hist_parsed = List([""]) | |
473 | input_hist_raw = List([""]) |
|
476 | input_hist_raw = List([""]) | |
474 | # A list of directories visited during session |
|
477 | # A list of directories visited during session | |
475 | dir_hist = List() |
|
478 | dir_hist = List() | |
476 | @default('dir_hist') |
|
479 | @default('dir_hist') | |
477 | def _dir_hist_default(self): |
|
480 | def _dir_hist_default(self): | |
478 | try: |
|
481 | try: | |
479 | return [Path.cwd()] |
|
482 | return [Path.cwd()] | |
480 | except OSError: |
|
483 | except OSError: | |
481 | return [] |
|
484 | return [] | |
482 |
|
485 | |||
483 | # A dict of output history, keyed with ints from the shell's |
|
486 | # A dict of output history, keyed with ints from the shell's | |
484 | # execution count. |
|
487 | # execution count. | |
485 | output_hist = Dict() |
|
488 | output_hist = Dict() | |
486 | # The text/plain repr of outputs. |
|
489 | # The text/plain repr of outputs. | |
487 | output_hist_reprs = Dict() |
|
490 | output_hist_reprs = Dict() | |
488 |
|
491 | |||
489 | # The number of the current session in the history database |
|
492 | # The number of the current session in the history database | |
490 | session_number = Integer() |
|
493 | session_number = Integer() | |
491 |
|
494 | |||
492 | db_log_output = Bool(False, |
|
495 | db_log_output = Bool(False, | |
493 | help="Should the history database include output? (default: no)" |
|
496 | help="Should the history database include output? (default: no)" | |
494 | ).tag(config=True) |
|
497 | ).tag(config=True) | |
495 | db_cache_size = Integer(0, |
|
498 | db_cache_size = Integer(0, | |
496 | help="Write to database every x commands (higher values save disk access & power).\n" |
|
499 | help="Write to database every x commands (higher values save disk access & power).\n" | |
497 | "Values of 1 or less effectively disable caching." |
|
500 | "Values of 1 or less effectively disable caching." | |
498 | ).tag(config=True) |
|
501 | ).tag(config=True) | |
499 | # The input and output caches |
|
502 | # The input and output caches | |
500 | db_input_cache = List() |
|
503 | db_input_cache = List() | |
501 | db_output_cache = List() |
|
504 | db_output_cache = List() | |
502 |
|
505 | |||
503 | # History saving in separate thread |
|
506 | # History saving in separate thread | |
504 | save_thread = Instance('IPython.core.history.HistorySavingThread', |
|
507 | save_thread = Instance('IPython.core.history.HistorySavingThread', | |
505 | allow_none=True) |
|
508 | allow_none=True) | |
506 | save_flag = Instance(threading.Event, allow_none=True) |
|
509 | save_flag = Instance(threading.Event, allow_none=True) | |
507 |
|
510 | |||
508 | # Private interface |
|
511 | # Private interface | |
509 | # Variables used to store the three last inputs from the user. On each new |
|
512 | # Variables used to store the three last inputs from the user. On each new | |
510 | # history update, we populate the user's namespace with these, shifted as |
|
513 | # history update, we populate the user's namespace with these, shifted as | |
511 | # necessary. |
|
514 | # necessary. | |
512 | _i00 = Unicode(u'') |
|
515 | _i00 = Unicode(u'') | |
513 | _i = Unicode(u'') |
|
516 | _i = Unicode(u'') | |
514 | _ii = Unicode(u'') |
|
517 | _ii = Unicode(u'') | |
515 | _iii = Unicode(u'') |
|
518 | _iii = Unicode(u'') | |
516 |
|
519 | |||
517 | # A regex matching all forms of the exit command, so that we don't store |
|
520 | # A regex matching all forms of the exit command, so that we don't store | |
518 | # them in the history (it's annoying to rewind the first entry and land on |
|
521 | # them in the history (it's annoying to rewind the first entry and land on | |
519 | # an exit call). |
|
522 | # an exit call). | |
520 | _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$") |
|
523 | _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$") | |
521 |
|
524 | |||
522 | def __init__(self, shell=None, config=None, **traits): |
|
525 | def __init__(self, shell=None, config=None, **traits): | |
523 | """Create a new history manager associated with a shell instance. |
|
526 | """Create a new history manager associated with a shell instance. | |
524 | """ |
|
527 | """ | |
525 | # We need a pointer back to the shell for various tasks. |
|
528 | # We need a pointer back to the shell for various tasks. | |
526 | super(HistoryManager, self).__init__(shell=shell, config=config, |
|
529 | super(HistoryManager, self).__init__(shell=shell, config=config, | |
527 | **traits) |
|
530 | **traits) | |
528 | self.save_flag = threading.Event() |
|
531 | self.save_flag = threading.Event() | |
529 | self.db_input_cache_lock = threading.Lock() |
|
532 | self.db_input_cache_lock = threading.Lock() | |
530 | self.db_output_cache_lock = threading.Lock() |
|
533 | self.db_output_cache_lock = threading.Lock() | |
531 |
|
534 | |||
532 | try: |
|
535 | try: | |
533 | self.new_session() |
|
536 | self.new_session() | |
534 | except sqlite3.OperationalError: |
|
537 | except sqlite3.OperationalError: | |
535 | self.log.error("Failed to create history session in %s. History will not be saved.", |
|
538 | self.log.error("Failed to create history session in %s. History will not be saved.", | |
536 | self.hist_file, exc_info=True) |
|
539 | self.hist_file, exc_info=True) | |
537 | self.hist_file = ':memory:' |
|
540 | self.hist_file = ':memory:' | |
538 |
|
541 | |||
539 | if self.enabled and self.hist_file != ':memory:': |
|
542 | if self.enabled and self.hist_file != ':memory:': | |
540 | self.save_thread = HistorySavingThread(self) |
|
543 | self.save_thread = HistorySavingThread(self) | |
541 | self.save_thread.start() |
|
544 | self.save_thread.start() | |
542 |
|
545 | |||
543 | def _get_hist_file_name(self, profile=None): |
|
546 | def _get_hist_file_name(self, profile=None): | |
544 | """Get default history file name based on the Shell's profile. |
|
547 | """Get default history file name based on the Shell's profile. | |
545 |
|
548 | |||
546 | The profile parameter is ignored, but must exist for compatibility with |
|
549 | The profile parameter is ignored, but must exist for compatibility with | |
547 | the parent class.""" |
|
550 | the parent class.""" | |
548 | profile_dir = self.shell.profile_dir.location |
|
551 | profile_dir = self.shell.profile_dir.location | |
549 | return Path(profile_dir) / "history.sqlite" |
|
552 | return Path(profile_dir) / "history.sqlite" | |
550 |
|
553 | |||
551 | @only_when_enabled |
|
554 | @only_when_enabled | |
552 | def new_session(self, conn=None): |
|
555 | def new_session(self, conn=None): | |
553 | """Get a new session number.""" |
|
556 | """Get a new session number.""" | |
554 | if conn is None: |
|
557 | if conn is None: | |
555 | conn = self.db |
|
558 | conn = self.db | |
556 |
|
559 | |||
557 | with conn: |
|
560 | with conn: | |
558 | cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL, |
|
561 | cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL, | |
559 | NULL, "") """, (datetime.datetime.now(),)) |
|
562 | NULL, "") """, (datetime.datetime.now(),)) | |
560 | self.session_number = cur.lastrowid |
|
563 | self.session_number = cur.lastrowid | |
561 |
|
564 | |||
562 | def end_session(self): |
|
565 | def end_session(self): | |
563 | """Close the database session, filling in the end time and line count.""" |
|
566 | """Close the database session, filling in the end time and line count.""" | |
564 | self.writeout_cache() |
|
567 | self.writeout_cache() | |
565 | with self.db: |
|
568 | with self.db: | |
566 | self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE |
|
569 | self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE | |
567 | session==?""", (datetime.datetime.now(), |
|
570 | session==?""", (datetime.datetime.now(), | |
568 | len(self.input_hist_parsed)-1, self.session_number)) |
|
571 | len(self.input_hist_parsed)-1, self.session_number)) | |
569 | self.session_number = 0 |
|
572 | self.session_number = 0 | |
570 |
|
573 | |||
571 | def name_session(self, name): |
|
574 | def name_session(self, name): | |
572 | """Give the current session a name in the history database.""" |
|
575 | """Give the current session a name in the history database.""" | |
573 | with self.db: |
|
576 | with self.db: | |
574 | self.db.execute("UPDATE sessions SET remark=? WHERE session==?", |
|
577 | self.db.execute("UPDATE sessions SET remark=? WHERE session==?", | |
575 | (name, self.session_number)) |
|
578 | (name, self.session_number)) | |
576 |
|
579 | |||
577 | def reset(self, new_session=True): |
|
580 | def reset(self, new_session=True): | |
578 | """Clear the session history, releasing all object references, and |
|
581 | """Clear the session history, releasing all object references, and | |
579 | optionally open a new session.""" |
|
582 | optionally open a new session.""" | |
580 | self.output_hist.clear() |
|
583 | self.output_hist.clear() | |
581 | # The directory history can't be completely empty |
|
584 | # The directory history can't be completely empty | |
582 | self.dir_hist[:] = [Path.cwd()] |
|
585 | self.dir_hist[:] = [Path.cwd()] | |
583 |
|
586 | |||
584 | if new_session: |
|
587 | if new_session: | |
585 | if self.session_number: |
|
588 | if self.session_number: | |
586 | self.end_session() |
|
589 | self.end_session() | |
587 | self.input_hist_parsed[:] = [""] |
|
590 | self.input_hist_parsed[:] = [""] | |
588 | self.input_hist_raw[:] = [""] |
|
591 | self.input_hist_raw[:] = [""] | |
589 | self.new_session() |
|
592 | self.new_session() | |
590 |
|
593 | |||
591 | # ------------------------------ |
|
594 | # ------------------------------ | |
592 | # Methods for retrieving history |
|
595 | # Methods for retrieving history | |
593 | # ------------------------------ |
|
596 | # ------------------------------ | |
594 | def get_session_info(self, session=0): |
|
597 | def get_session_info(self, session=0): | |
595 | """Get info about a session. |
|
598 | """Get info about a session. | |
596 |
|
599 | |||
597 | Parameters |
|
600 | Parameters | |
598 | ---------- |
|
601 | ---------- | |
599 |
|
602 | |||
600 | session : int |
|
603 | session : int | |
601 | Session number to retrieve. The current session is 0, and negative |
|
604 | Session number to retrieve. The current session is 0, and negative | |
602 | numbers count back from current session, so -1 is the previous session. |
|
605 | numbers count back from current session, so -1 is the previous session. | |
603 |
|
606 | |||
604 | Returns |
|
607 | Returns | |
605 | ------- |
|
608 | ------- | |
606 |
|
609 | |||
607 | session_id : int |
|
610 | session_id : int | |
608 | Session ID number |
|
611 | Session ID number | |
609 | start : datetime |
|
612 | start : datetime | |
610 | Timestamp for the start of the session. |
|
613 | Timestamp for the start of the session. | |
611 | end : datetime |
|
614 | end : datetime | |
612 | Timestamp for the end of the session, or None if IPython crashed. |
|
615 | Timestamp for the end of the session, or None if IPython crashed. | |
613 | num_cmds : int |
|
616 | num_cmds : int | |
614 | Number of commands run, or None if IPython crashed. |
|
617 | Number of commands run, or None if IPython crashed. | |
615 | remark : unicode |
|
618 | remark : unicode | |
616 | A manually set description. |
|
619 | A manually set description. | |
617 | """ |
|
620 | """ | |
618 | if session <= 0: |
|
621 | if session <= 0: | |
619 | session += self.session_number |
|
622 | session += self.session_number | |
620 |
|
623 | |||
621 | return super(HistoryManager, self).get_session_info(session=session) |
|
624 | return super(HistoryManager, self).get_session_info(session=session) | |
622 |
|
625 | |||
623 | def _get_range_session(self, start=1, stop=None, raw=True, output=False): |
|
626 | def _get_range_session(self, start=1, stop=None, raw=True, output=False): | |
624 | """Get input and output history from the current session. Called by |
|
627 | """Get input and output history from the current session. Called by | |
625 | get_range, and takes similar parameters.""" |
|
628 | get_range, and takes similar parameters.""" | |
626 | input_hist = self.input_hist_raw if raw else self.input_hist_parsed |
|
629 | input_hist = self.input_hist_raw if raw else self.input_hist_parsed | |
627 |
|
630 | |||
628 | n = len(input_hist) |
|
631 | n = len(input_hist) | |
629 | if start < 0: |
|
632 | if start < 0: | |
630 | start += n |
|
633 | start += n | |
631 | if not stop or (stop > n): |
|
634 | if not stop or (stop > n): | |
632 | stop = n |
|
635 | stop = n | |
633 | elif stop < 0: |
|
636 | elif stop < 0: | |
634 | stop += n |
|
637 | stop += n | |
635 |
|
638 | |||
636 | for i in range(start, stop): |
|
639 | for i in range(start, stop): | |
637 | if output: |
|
640 | if output: | |
638 | line = (input_hist[i], self.output_hist_reprs.get(i)) |
|
641 | line = (input_hist[i], self.output_hist_reprs.get(i)) | |
639 | else: |
|
642 | else: | |
640 | line = input_hist[i] |
|
643 | line = input_hist[i] | |
641 | yield (0, i, line) |
|
644 | yield (0, i, line) | |
642 |
|
645 | |||
643 | def get_range(self, session=0, start=1, stop=None, raw=True,output=False): |
|
646 | def get_range(self, session=0, start=1, stop=None, raw=True,output=False): | |
644 | """Retrieve input by session. |
|
647 | """Retrieve input by session. | |
645 |
|
648 | |||
646 | Parameters |
|
649 | Parameters | |
647 | ---------- |
|
650 | ---------- | |
648 | session : int |
|
651 | session : int | |
649 | Session number to retrieve. The current session is 0, and negative |
|
652 | Session number to retrieve. The current session is 0, and negative | |
650 | numbers count back from current session, so -1 is previous session. |
|
653 | numbers count back from current session, so -1 is previous session. | |
651 | start : int |
|
654 | start : int | |
652 | First line to retrieve. |
|
655 | First line to retrieve. | |
653 | stop : int |
|
656 | stop : int | |
654 | End of line range (excluded from output itself). If None, retrieve |
|
657 | End of line range (excluded from output itself). If None, retrieve | |
655 | to the end of the session. |
|
658 | to the end of the session. | |
656 | raw : bool |
|
659 | raw : bool | |
657 | If True, return untranslated input |
|
660 | If True, return untranslated input | |
658 | output : bool |
|
661 | output : bool | |
659 | If True, attempt to include output. This will be 'real' Python |
|
662 | If True, attempt to include output. This will be 'real' Python | |
660 | objects for the current session, or text reprs from previous |
|
663 | objects for the current session, or text reprs from previous | |
661 | sessions if db_log_output was enabled at the time. Where no output |
|
664 | sessions if db_log_output was enabled at the time. Where no output | |
662 | is found, None is used. |
|
665 | is found, None is used. | |
663 |
|
666 | |||
664 | Returns |
|
667 | Returns | |
665 | ------- |
|
668 | ------- | |
666 | entries |
|
669 | entries | |
667 | An iterator over the desired lines. Each line is a 3-tuple, either |
|
670 | An iterator over the desired lines. Each line is a 3-tuple, either | |
668 | (session, line, input) if output is False, or |
|
671 | (session, line, input) if output is False, or | |
669 | (session, line, (input, output)) if output is True. |
|
672 | (session, line, (input, output)) if output is True. | |
670 | """ |
|
673 | """ | |
671 | if session <= 0: |
|
674 | if session <= 0: | |
672 | session += self.session_number |
|
675 | session += self.session_number | |
673 | if session==self.session_number: # Current session |
|
676 | if session==self.session_number: # Current session | |
674 | return self._get_range_session(start, stop, raw, output) |
|
677 | return self._get_range_session(start, stop, raw, output) | |
675 | return super(HistoryManager, self).get_range(session, start, stop, raw, |
|
678 | return super(HistoryManager, self).get_range(session, start, stop, raw, | |
676 | output) |
|
679 | output) | |
677 |
|
680 | |||
678 | ## ---------------------------- |
|
681 | ## ---------------------------- | |
679 | ## Methods for storing history: |
|
682 | ## Methods for storing history: | |
680 | ## ---------------------------- |
|
683 | ## ---------------------------- | |
681 | def store_inputs(self, line_num, source, source_raw=None): |
|
684 | def store_inputs(self, line_num, source, source_raw=None): | |
682 | """Store source and raw input in history and create input cache |
|
685 | """Store source and raw input in history and create input cache | |
683 | variables ``_i*``. |
|
686 | variables ``_i*``. | |
684 |
|
687 | |||
685 | Parameters |
|
688 | Parameters | |
686 | ---------- |
|
689 | ---------- | |
687 | line_num : int |
|
690 | line_num : int | |
688 | The prompt number of this input. |
|
691 | The prompt number of this input. | |
689 |
|
692 | |||
690 | source : str |
|
693 | source : str | |
691 | Python input. |
|
694 | Python input. | |
692 |
|
695 | |||
693 | source_raw : str, optional |
|
696 | source_raw : str, optional | |
694 | If given, this is the raw input without any IPython transformations |
|
697 | If given, this is the raw input without any IPython transformations | |
695 | applied to it. If not given, ``source`` is used. |
|
698 | applied to it. If not given, ``source`` is used. | |
696 | """ |
|
699 | """ | |
697 | if source_raw is None: |
|
700 | if source_raw is None: | |
698 | source_raw = source |
|
701 | source_raw = source | |
699 | source = source.rstrip('\n') |
|
702 | source = source.rstrip('\n') | |
700 | source_raw = source_raw.rstrip('\n') |
|
703 | source_raw = source_raw.rstrip('\n') | |
701 |
|
704 | |||
702 | # do not store exit/quit commands |
|
705 | # do not store exit/quit commands | |
703 | if self._exit_re.match(source_raw.strip()): |
|
706 | if self._exit_re.match(source_raw.strip()): | |
704 | return |
|
707 | return | |
705 |
|
708 | |||
706 | self.input_hist_parsed.append(source) |
|
709 | self.input_hist_parsed.append(source) | |
707 | self.input_hist_raw.append(source_raw) |
|
710 | self.input_hist_raw.append(source_raw) | |
708 |
|
711 | |||
709 | with self.db_input_cache_lock: |
|
712 | with self.db_input_cache_lock: | |
710 | self.db_input_cache.append((line_num, source, source_raw)) |
|
713 | self.db_input_cache.append((line_num, source, source_raw)) | |
711 | # Trigger to flush cache and write to DB. |
|
714 | # Trigger to flush cache and write to DB. | |
712 | if len(self.db_input_cache) >= self.db_cache_size: |
|
715 | if len(self.db_input_cache) >= self.db_cache_size: | |
713 | self.save_flag.set() |
|
716 | self.save_flag.set() | |
714 |
|
717 | |||
715 | # update the auto _i variables |
|
718 | # update the auto _i variables | |
716 | self._iii = self._ii |
|
719 | self._iii = self._ii | |
717 | self._ii = self._i |
|
720 | self._ii = self._i | |
718 | self._i = self._i00 |
|
721 | self._i = self._i00 | |
719 | self._i00 = source_raw |
|
722 | self._i00 = source_raw | |
720 |
|
723 | |||
721 | # hackish access to user namespace to create _i1,_i2... dynamically |
|
724 | # hackish access to user namespace to create _i1,_i2... dynamically | |
722 | new_i = '_i%s' % line_num |
|
725 | new_i = '_i%s' % line_num | |
723 | to_main = {'_i': self._i, |
|
726 | to_main = {'_i': self._i, | |
724 | '_ii': self._ii, |
|
727 | '_ii': self._ii, | |
725 | '_iii': self._iii, |
|
728 | '_iii': self._iii, | |
726 | new_i : self._i00 } |
|
729 | new_i : self._i00 } | |
727 |
|
730 | |||
728 | if self.shell is not None: |
|
731 | if self.shell is not None: | |
729 | self.shell.push(to_main, interactive=False) |
|
732 | self.shell.push(to_main, interactive=False) | |
730 |
|
733 | |||
731 | def store_output(self, line_num): |
|
734 | def store_output(self, line_num): | |
732 | """If database output logging is enabled, this saves all the |
|
735 | """If database output logging is enabled, this saves all the | |
733 | outputs from the indicated prompt number to the database. It's |
|
736 | outputs from the indicated prompt number to the database. It's | |
734 | called by run_cell after code has been executed. |
|
737 | called by run_cell after code has been executed. | |
735 |
|
738 | |||
736 | Parameters |
|
739 | Parameters | |
737 | ---------- |
|
740 | ---------- | |
738 | line_num : int |
|
741 | line_num : int | |
739 | The line number from which to save outputs |
|
742 | The line number from which to save outputs | |
740 | """ |
|
743 | """ | |
741 | if (not self.db_log_output) or (line_num not in self.output_hist_reprs): |
|
744 | if (not self.db_log_output) or (line_num not in self.output_hist_reprs): | |
742 | return |
|
745 | return | |
743 | output = self.output_hist_reprs[line_num] |
|
746 | output = self.output_hist_reprs[line_num] | |
744 |
|
747 | |||
745 | with self.db_output_cache_lock: |
|
748 | with self.db_output_cache_lock: | |
746 | self.db_output_cache.append((line_num, output)) |
|
749 | self.db_output_cache.append((line_num, output)) | |
747 | if self.db_cache_size <= 1: |
|
750 | if self.db_cache_size <= 1: | |
748 | self.save_flag.set() |
|
751 | self.save_flag.set() | |
749 |
|
752 | |||
750 | def _writeout_input_cache(self, conn): |
|
753 | def _writeout_input_cache(self, conn): | |
751 | with conn: |
|
754 | with conn: | |
752 | for line in self.db_input_cache: |
|
755 | for line in self.db_input_cache: | |
753 | conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)", |
|
756 | conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)", | |
754 | (self.session_number,)+line) |
|
757 | (self.session_number,)+line) | |
755 |
|
758 | |||
756 | def _writeout_output_cache(self, conn): |
|
759 | def _writeout_output_cache(self, conn): | |
757 | with conn: |
|
760 | with conn: | |
758 | for line in self.db_output_cache: |
|
761 | for line in self.db_output_cache: | |
759 | conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", |
|
762 | conn.execute("INSERT INTO output_history VALUES (?, ?, ?)", | |
760 | (self.session_number,)+line) |
|
763 | (self.session_number,)+line) | |
761 |
|
764 | |||
762 | @only_when_enabled |
|
765 | @only_when_enabled | |
763 | def writeout_cache(self, conn=None): |
|
766 | def writeout_cache(self, conn=None): | |
764 | """Write any entries in the cache to the database.""" |
|
767 | """Write any entries in the cache to the database.""" | |
765 | if conn is None: |
|
768 | if conn is None: | |
766 | conn = self.db |
|
769 | conn = self.db | |
767 |
|
770 | |||
768 | with self.db_input_cache_lock: |
|
771 | with self.db_input_cache_lock: | |
769 | try: |
|
772 | try: | |
770 | self._writeout_input_cache(conn) |
|
773 | self._writeout_input_cache(conn) | |
771 | except sqlite3.IntegrityError: |
|
774 | except sqlite3.IntegrityError: | |
772 | self.new_session(conn) |
|
775 | self.new_session(conn) | |
773 | print("ERROR! Session/line number was not unique in", |
|
776 | print("ERROR! Session/line number was not unique in", | |
774 | "database. History logging moved to new session", |
|
777 | "database. History logging moved to new session", | |
775 | self.session_number) |
|
778 | self.session_number) | |
776 | try: |
|
779 | try: | |
777 | # Try writing to the new session. If this fails, don't |
|
780 | # Try writing to the new session. If this fails, don't | |
778 | # recurse |
|
781 | # recurse | |
779 | self._writeout_input_cache(conn) |
|
782 | self._writeout_input_cache(conn) | |
780 | except sqlite3.IntegrityError: |
|
783 | except sqlite3.IntegrityError: | |
781 | pass |
|
784 | pass | |
782 | finally: |
|
785 | finally: | |
783 | self.db_input_cache = [] |
|
786 | self.db_input_cache = [] | |
784 |
|
787 | |||
785 | with self.db_output_cache_lock: |
|
788 | with self.db_output_cache_lock: | |
786 | try: |
|
789 | try: | |
787 | self._writeout_output_cache(conn) |
|
790 | self._writeout_output_cache(conn) | |
788 | except sqlite3.IntegrityError: |
|
791 | except sqlite3.IntegrityError: | |
789 | print("!! Session/line number for output was not unique", |
|
792 | print("!! Session/line number for output was not unique", | |
790 | "in database. Output will not be stored.") |
|
793 | "in database. Output will not be stored.") | |
791 | finally: |
|
794 | finally: | |
792 | self.db_output_cache = [] |
|
795 | self.db_output_cache = [] | |
793 |
|
796 | |||
794 |
|
797 | |||
795 | class HistorySavingThread(threading.Thread): |
|
798 | class HistorySavingThread(threading.Thread): | |
796 | """This thread takes care of writing history to the database, so that |
|
799 | """This thread takes care of writing history to the database, so that | |
797 | the UI isn't held up while that happens. |
|
800 | the UI isn't held up while that happens. | |
798 |
|
801 | |||
799 | It waits for the HistoryManager's save_flag to be set, then writes out |
|
802 | It waits for the HistoryManager's save_flag to be set, then writes out | |
800 | the history cache. The main thread is responsible for setting the flag when |
|
803 | the history cache. The main thread is responsible for setting the flag when | |
801 | the cache size reaches a defined threshold.""" |
|
804 | the cache size reaches a defined threshold.""" | |
802 | daemon = True |
|
805 | daemon = True | |
803 | stop_now = False |
|
806 | stop_now = False | |
804 | enabled = True |
|
807 | enabled = True | |
805 | def __init__(self, history_manager): |
|
808 | def __init__(self, history_manager): | |
806 | super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread") |
|
809 | super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread") | |
807 | self.history_manager = history_manager |
|
810 | self.history_manager = history_manager | |
808 | self.enabled = history_manager.enabled |
|
811 | self.enabled = history_manager.enabled | |
809 | atexit.register(self.stop) |
|
812 | atexit.register(self.stop) | |
810 |
|
813 | |||
811 | @only_when_enabled |
|
814 | @only_when_enabled | |
812 | def run(self): |
|
815 | def run(self): | |
813 | # We need a separate db connection per thread: |
|
816 | # We need a separate db connection per thread: | |
814 | try: |
|
817 | try: | |
815 | self.db = sqlite3.connect( |
|
818 | self.db = sqlite3.connect( | |
816 | str(self.history_manager.hist_file), |
|
819 | str(self.history_manager.hist_file), | |
817 | **self.history_manager.connection_options |
|
820 | **self.history_manager.connection_options | |
818 | ) |
|
821 | ) | |
819 | while True: |
|
822 | while True: | |
820 | self.history_manager.save_flag.wait() |
|
823 | self.history_manager.save_flag.wait() | |
821 | if self.stop_now: |
|
824 | if self.stop_now: | |
822 | self.db.close() |
|
825 | self.db.close() | |
823 | return |
|
826 | return | |
824 | self.history_manager.save_flag.clear() |
|
827 | self.history_manager.save_flag.clear() | |
825 | self.history_manager.writeout_cache(self.db) |
|
828 | self.history_manager.writeout_cache(self.db) | |
826 | except Exception as e: |
|
829 | except Exception as e: | |
827 | print(("The history saving thread hit an unexpected error (%s)." |
|
830 | print(("The history saving thread hit an unexpected error (%s)." | |
828 | "History will not be written to the database.") % repr(e)) |
|
831 | "History will not be written to the database.") % repr(e)) | |
829 |
|
832 | |||
830 | def stop(self): |
|
833 | def stop(self): | |
831 | """This can be called from the main thread to safely stop this thread. |
|
834 | """This can be called from the main thread to safely stop this thread. | |
832 |
|
835 | |||
833 | Note that it does not attempt to write out remaining history before |
|
836 | Note that it does not attempt to write out remaining history before | |
834 | exiting. That should be done by calling the HistoryManager's |
|
837 | exiting. That should be done by calling the HistoryManager's | |
835 | end_session method.""" |
|
838 | end_session method.""" | |
836 | self.stop_now = True |
|
839 | self.stop_now = True | |
837 | self.history_manager.save_flag.set() |
|
840 | self.history_manager.save_flag.set() | |
838 | self.join() |
|
841 | self.join() | |
839 |
|
842 | |||
840 |
|
843 | |||
841 | # To match, e.g. ~5/8-~2/3 |
|
844 | # To match, e.g. ~5/8-~2/3 | |
842 | range_re = re.compile(r""" |
|
845 | range_re = re.compile(r""" | |
843 | ((?P<startsess>~?\d+)/)? |
|
846 | ((?P<startsess>~?\d+)/)? | |
844 | (?P<start>\d+)? |
|
847 | (?P<start>\d+)? | |
845 | ((?P<sep>[\-:]) |
|
848 | ((?P<sep>[\-:]) | |
846 | ((?P<endsess>~?\d+)/)? |
|
849 | ((?P<endsess>~?\d+)/)? | |
847 | (?P<end>\d+))? |
|
850 | (?P<end>\d+))? | |
848 | $""", re.VERBOSE) |
|
851 | $""", re.VERBOSE) | |
849 |
|
852 | |||
850 |
|
853 | |||
851 | def extract_hist_ranges(ranges_str): |
|
854 | def extract_hist_ranges(ranges_str): | |
852 | """Turn a string of history ranges into 3-tuples of (session, start, stop). |
|
855 | """Turn a string of history ranges into 3-tuples of (session, start, stop). | |
853 |
|
856 | |||
|
857 | Empty string results in a `[(0, 1, None)]`, i.e. "everything from current | |||
|
858 | session". | |||
|
859 | ||||
854 | Examples |
|
860 | Examples | |
855 | -------- |
|
861 | -------- | |
856 | >>> list(extract_hist_ranges("~8/5-~7/4 2")) |
|
862 | >>> list(extract_hist_ranges("~8/5-~7/4 2")) | |
857 | [(-8, 5, None), (-7, 1, 5), (0, 2, 3)] |
|
863 | [(-8, 5, None), (-7, 1, 5), (0, 2, 3)] | |
858 | """ |
|
864 | """ | |
|
865 | if ranges_str == "": | |||
|
866 | yield (0, 1, None) # Everything from current session | |||
|
867 | return | |||
|
868 | ||||
859 | for range_str in ranges_str.split(): |
|
869 | for range_str in ranges_str.split(): | |
860 | rmatch = range_re.match(range_str) |
|
870 | rmatch = range_re.match(range_str) | |
861 | if not rmatch: |
|
871 | if not rmatch: | |
862 | continue |
|
872 | continue | |
863 | start = rmatch.group("start") |
|
873 | start = rmatch.group("start") | |
864 | if start: |
|
874 | if start: | |
865 | start = int(start) |
|
875 | start = int(start) | |
866 | end = rmatch.group("end") |
|
876 | end = rmatch.group("end") | |
867 | # If no end specified, get (a, a + 1) |
|
877 | # If no end specified, get (a, a + 1) | |
868 | end = int(end) if end else start + 1 |
|
878 | end = int(end) if end else start + 1 | |
869 | else: # start not specified |
|
879 | else: # start not specified | |
870 | if not rmatch.group('startsess'): # no startsess |
|
880 | if not rmatch.group('startsess'): # no startsess | |
871 | continue |
|
881 | continue | |
872 | start = 1 |
|
882 | start = 1 | |
873 | end = None # provide the entire session hist |
|
883 | end = None # provide the entire session hist | |
874 |
|
884 | |||
875 | if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3] |
|
885 | if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3] | |
876 | end += 1 |
|
886 | end += 1 | |
877 | startsess = rmatch.group("startsess") or "0" |
|
887 | startsess = rmatch.group("startsess") or "0" | |
878 | endsess = rmatch.group("endsess") or startsess |
|
888 | endsess = rmatch.group("endsess") or startsess | |
879 | startsess = int(startsess.replace("~","-")) |
|
889 | startsess = int(startsess.replace("~","-")) | |
880 | endsess = int(endsess.replace("~","-")) |
|
890 | endsess = int(endsess.replace("~","-")) | |
881 | assert endsess >= startsess, "start session must be earlier than end session" |
|
891 | assert endsess >= startsess, "start session must be earlier than end session" | |
882 |
|
892 | |||
883 | if endsess == startsess: |
|
893 | if endsess == startsess: | |
884 | yield (startsess, start, end) |
|
894 | yield (startsess, start, end) | |
885 | continue |
|
895 | continue | |
886 | # Multiple sessions in one range: |
|
896 | # Multiple sessions in one range: | |
887 | yield (startsess, start, None) |
|
897 | yield (startsess, start, None) | |
888 | for sess in range(startsess+1, endsess): |
|
898 | for sess in range(startsess+1, endsess): | |
889 | yield (sess, 1, None) |
|
899 | yield (sess, 1, None) | |
890 | yield (endsess, 1, end) |
|
900 | yield (endsess, 1, end) | |
891 |
|
901 | |||
892 |
|
902 | |||
893 | def _format_lineno(session, line): |
|
903 | def _format_lineno(session, line): | |
894 | """Helper function to format line numbers properly.""" |
|
904 | """Helper function to format line numbers properly.""" | |
895 | if session == 0: |
|
905 | if session == 0: | |
896 | return str(line) |
|
906 | return str(line) | |
897 | return "%s#%s" % (session, line) |
|
907 | return "%s#%s" % (session, line) |
@@ -1,729 +1,799 b'' | |||||
1 | """Input transformer machinery to support IPython special syntax. |
|
1 | """Input transformer machinery to support IPython special syntax. | |
2 |
|
2 | |||
3 | This includes the machinery to recognise and transform ``%magic`` commands, |
|
3 | This includes the machinery to recognise and transform ``%magic`` commands, | |
4 | ``!system`` commands, ``help?`` querying, prompt stripping, and so forth. |
|
4 | ``!system`` commands, ``help?`` querying, prompt stripping, and so forth. | |
5 |
|
5 | |||
6 | Added: IPython 7.0. Replaces inputsplitter and inputtransformer which were |
|
6 | Added: IPython 7.0. Replaces inputsplitter and inputtransformer which were | |
7 | deprecated in 7.0. |
|
7 | deprecated in 7.0. | |
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 | from codeop import compile_command |
|
13 | import ast | |
|
14 | import sys | |||
|
15 | from codeop import CommandCompiler, Compile | |||
14 | import re |
|
16 | import re | |
15 | import tokenize |
|
17 | import tokenize | |
16 | from typing import List, Tuple, Optional, Any |
|
18 | from typing import List, Tuple, Optional, Any | |
17 | import warnings |
|
19 | import warnings | |
18 |
|
20 | |||
19 | _indent_re = re.compile(r'^[ \t]+') |
|
21 | _indent_re = re.compile(r'^[ \t]+') | |
20 |
|
22 | |||
21 | def leading_empty_lines(lines): |
|
23 | def leading_empty_lines(lines): | |
22 | """Remove leading empty lines |
|
24 | """Remove leading empty lines | |
23 |
|
25 | |||
24 | If the leading lines are empty or contain only whitespace, they will be |
|
26 | If the leading lines are empty or contain only whitespace, they will be | |
25 | removed. |
|
27 | removed. | |
26 | """ |
|
28 | """ | |
27 | if not lines: |
|
29 | if not lines: | |
28 | return lines |
|
30 | return lines | |
29 | for i, line in enumerate(lines): |
|
31 | for i, line in enumerate(lines): | |
30 | if line and not line.isspace(): |
|
32 | if line and not line.isspace(): | |
31 | return lines[i:] |
|
33 | return lines[i:] | |
32 | return lines |
|
34 | return lines | |
33 |
|
35 | |||
34 | def leading_indent(lines): |
|
36 | def leading_indent(lines): | |
35 | """Remove leading indentation. |
|
37 | """Remove leading indentation. | |
36 |
|
38 | |||
37 | If the first line starts with a spaces or tabs, the same whitespace will be |
|
39 | If the first line starts with a spaces or tabs, the same whitespace will be | |
38 | removed from each following line in the cell. |
|
40 | removed from each following line in the cell. | |
39 | """ |
|
41 | """ | |
40 | if not lines: |
|
42 | if not lines: | |
41 | return lines |
|
43 | return lines | |
42 | m = _indent_re.match(lines[0]) |
|
44 | m = _indent_re.match(lines[0]) | |
43 | if not m: |
|
45 | if not m: | |
44 | return lines |
|
46 | return lines | |
45 | space = m.group(0) |
|
47 | space = m.group(0) | |
46 | n = len(space) |
|
48 | n = len(space) | |
47 | return [l[n:] if l.startswith(space) else l |
|
49 | return [l[n:] if l.startswith(space) else l | |
48 | for l in lines] |
|
50 | for l in lines] | |
49 |
|
51 | |||
50 | class PromptStripper: |
|
52 | class PromptStripper: | |
51 | """Remove matching input prompts from a block of input. |
|
53 | """Remove matching input prompts from a block of input. | |
52 |
|
54 | |||
53 | Parameters |
|
55 | Parameters | |
54 | ---------- |
|
56 | ---------- | |
55 | prompt_re : regular expression |
|
57 | prompt_re : regular expression | |
56 | A regular expression matching any input prompt (including continuation, |
|
58 | A regular expression matching any input prompt (including continuation, | |
57 | e.g. ``...``) |
|
59 | e.g. ``...``) | |
58 | initial_re : regular expression, optional |
|
60 | initial_re : regular expression, optional | |
59 | A regular expression matching only the initial prompt, but not continuation. |
|
61 | A regular expression matching only the initial prompt, but not continuation. | |
60 | If no initial expression is given, prompt_re will be used everywhere. |
|
62 | If no initial expression is given, prompt_re will be used everywhere. | |
61 | Used mainly for plain Python prompts (``>>>``), where the continuation prompt |
|
63 | Used mainly for plain Python prompts (``>>>``), where the continuation prompt | |
62 | ``...`` is a valid Python expression in Python 3, so shouldn't be stripped. |
|
64 | ``...`` is a valid Python expression in Python 3, so shouldn't be stripped. | |
63 |
|
65 | |||
64 | Notes |
|
66 | Notes | |
65 | ----- |
|
67 | ----- | |
66 |
|
68 | |||
67 | If initial_re and prompt_re differ, |
|
69 | If initial_re and prompt_re differ, | |
68 | only initial_re will be tested against the first line. |
|
70 | only initial_re will be tested against the first line. | |
69 | If any prompt is found on the first two lines, |
|
71 | If any prompt is found on the first two lines, | |
70 | prompts will be stripped from the rest of the block. |
|
72 | prompts will be stripped from the rest of the block. | |
71 | """ |
|
73 | """ | |
72 | def __init__(self, prompt_re, initial_re=None): |
|
74 | def __init__(self, prompt_re, initial_re=None): | |
73 | self.prompt_re = prompt_re |
|
75 | self.prompt_re = prompt_re | |
74 | self.initial_re = initial_re or prompt_re |
|
76 | self.initial_re = initial_re or prompt_re | |
75 |
|
77 | |||
76 | def _strip(self, lines): |
|
78 | def _strip(self, lines): | |
77 | return [self.prompt_re.sub('', l, count=1) for l in lines] |
|
79 | return [self.prompt_re.sub('', l, count=1) for l in lines] | |
78 |
|
80 | |||
79 | def __call__(self, lines): |
|
81 | def __call__(self, lines): | |
80 | if not lines: |
|
82 | if not lines: | |
81 | return lines |
|
83 | return lines | |
82 | if self.initial_re.match(lines[0]) or \ |
|
84 | if self.initial_re.match(lines[0]) or \ | |
83 | (len(lines) > 1 and self.prompt_re.match(lines[1])): |
|
85 | (len(lines) > 1 and self.prompt_re.match(lines[1])): | |
84 | return self._strip(lines) |
|
86 | return self._strip(lines) | |
85 | return lines |
|
87 | return lines | |
86 |
|
88 | |||
87 | classic_prompt = PromptStripper( |
|
89 | classic_prompt = PromptStripper( | |
88 | prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'), |
|
90 | prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'), | |
89 | initial_re=re.compile(r'^>>>( |$)') |
|
91 | initial_re=re.compile(r'^>>>( |$)') | |
90 | ) |
|
92 | ) | |
91 |
|
93 | |||
92 | ipython_prompt = PromptStripper(re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')) |
|
94 | ipython_prompt = PromptStripper( | |
|
95 | re.compile( | |||
|
96 | r""" | |||
|
97 | ^( # Match from the beginning of a line, either: | |||
|
98 | ||||
|
99 | # 1. First-line prompt: | |||
|
100 | ((\[nav\]|\[ins\])?\ )? # Vi editing mode prompt, if it's there | |||
|
101 | In\ # The 'In' of the prompt, with a space | |||
|
102 | \[\d+\]: # Command index, as displayed in the prompt | |||
|
103 | \ # With a mandatory trailing space | |||
|
104 | ||||
|
105 | | # ... or ... | |||
|
106 | ||||
|
107 | # 2. The three dots of the multiline prompt | |||
|
108 | \s* # All leading whitespace characters | |||
|
109 | \.{3,}: # The three (or more) dots | |||
|
110 | \ ? # With an optional trailing space | |||
|
111 | ||||
|
112 | ) | |||
|
113 | """, | |||
|
114 | re.VERBOSE, | |||
|
115 | ) | |||
|
116 | ) | |||
|
117 | ||||
93 |
|
118 | |||
94 | def cell_magic(lines): |
|
119 | def cell_magic(lines): | |
95 | if not lines or not lines[0].startswith('%%'): |
|
120 | if not lines or not lines[0].startswith('%%'): | |
96 | return lines |
|
121 | return lines | |
97 | if re.match(r'%%\w+\?', lines[0]): |
|
122 | if re.match(r'%%\w+\?', lines[0]): | |
98 | # This case will be handled by help_end |
|
123 | # This case will be handled by help_end | |
99 | return lines |
|
124 | return lines | |
100 | magic_name, _, first_line = lines[0][2:].rstrip().partition(' ') |
|
125 | magic_name, _, first_line = lines[0][2:].rstrip().partition(' ') | |
101 | body = ''.join(lines[1:]) |
|
126 | body = ''.join(lines[1:]) | |
102 | return ['get_ipython().run_cell_magic(%r, %r, %r)\n' |
|
127 | return ['get_ipython().run_cell_magic(%r, %r, %r)\n' | |
103 | % (magic_name, first_line, body)] |
|
128 | % (magic_name, first_line, body)] | |
104 |
|
129 | |||
105 |
|
130 | |||
106 | def _find_assign_op(token_line) -> Optional[int]: |
|
131 | def _find_assign_op(token_line) -> Optional[int]: | |
107 | """Get the index of the first assignment in the line ('=' not inside brackets) |
|
132 | """Get the index of the first assignment in the line ('=' not inside brackets) | |
108 |
|
133 | |||
109 | Note: We don't try to support multiple special assignment (a = b = %foo) |
|
134 | Note: We don't try to support multiple special assignment (a = b = %foo) | |
110 | """ |
|
135 | """ | |
111 | paren_level = 0 |
|
136 | paren_level = 0 | |
112 | for i, ti in enumerate(token_line): |
|
137 | for i, ti in enumerate(token_line): | |
113 | s = ti.string |
|
138 | s = ti.string | |
114 | if s == '=' and paren_level == 0: |
|
139 | if s == '=' and paren_level == 0: | |
115 | return i |
|
140 | return i | |
116 | if s in {'(','[','{'}: |
|
141 | if s in {'(','[','{'}: | |
117 | paren_level += 1 |
|
142 | paren_level += 1 | |
118 | elif s in {')', ']', '}'}: |
|
143 | elif s in {')', ']', '}'}: | |
119 | if paren_level > 0: |
|
144 | if paren_level > 0: | |
120 | paren_level -= 1 |
|
145 | paren_level -= 1 | |
121 | return None |
|
146 | return None | |
122 |
|
147 | |||
123 | def find_end_of_continued_line(lines, start_line: int): |
|
148 | def find_end_of_continued_line(lines, start_line: int): | |
124 | """Find the last line of a line explicitly extended using backslashes. |
|
149 | """Find the last line of a line explicitly extended using backslashes. | |
125 |
|
150 | |||
126 | Uses 0-indexed line numbers. |
|
151 | Uses 0-indexed line numbers. | |
127 | """ |
|
152 | """ | |
128 | end_line = start_line |
|
153 | end_line = start_line | |
129 | while lines[end_line].endswith('\\\n'): |
|
154 | while lines[end_line].endswith('\\\n'): | |
130 | end_line += 1 |
|
155 | end_line += 1 | |
131 | if end_line >= len(lines): |
|
156 | if end_line >= len(lines): | |
132 | break |
|
157 | break | |
133 | return end_line |
|
158 | return end_line | |
134 |
|
159 | |||
135 | def assemble_continued_line(lines, start: Tuple[int, int], end_line: int): |
|
160 | def assemble_continued_line(lines, start: Tuple[int, int], end_line: int): | |
136 | r"""Assemble a single line from multiple continued line pieces |
|
161 | r"""Assemble a single line from multiple continued line pieces | |
137 |
|
162 | |||
138 | Continued lines are lines ending in ``\``, and the line following the last |
|
163 | Continued lines are lines ending in ``\``, and the line following the last | |
139 | ``\`` in the block. |
|
164 | ``\`` in the block. | |
140 |
|
165 | |||
141 | For example, this code continues over multiple lines:: |
|
166 | For example, this code continues over multiple lines:: | |
142 |
|
167 | |||
143 | if (assign_ix is not None) \ |
|
168 | if (assign_ix is not None) \ | |
144 | and (len(line) >= assign_ix + 2) \ |
|
169 | and (len(line) >= assign_ix + 2) \ | |
145 | and (line[assign_ix+1].string == '%') \ |
|
170 | and (line[assign_ix+1].string == '%') \ | |
146 | and (line[assign_ix+2].type == tokenize.NAME): |
|
171 | and (line[assign_ix+2].type == tokenize.NAME): | |
147 |
|
172 | |||
148 | This statement contains four continued line pieces. |
|
173 | This statement contains four continued line pieces. | |
149 | Assembling these pieces into a single line would give:: |
|
174 | Assembling these pieces into a single line would give:: | |
150 |
|
175 | |||
151 | if (assign_ix is not None) and (len(line) >= assign_ix + 2) and (line[... |
|
176 | if (assign_ix is not None) and (len(line) >= assign_ix + 2) and (line[... | |
152 |
|
177 | |||
153 | This uses 0-indexed line numbers. *start* is (lineno, colno). |
|
178 | This uses 0-indexed line numbers. *start* is (lineno, colno). | |
154 |
|
179 | |||
155 | Used to allow ``%magic`` and ``!system`` commands to be continued over |
|
180 | Used to allow ``%magic`` and ``!system`` commands to be continued over | |
156 | multiple lines. |
|
181 | multiple lines. | |
157 | """ |
|
182 | """ | |
158 | parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1] |
|
183 | parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1] | |
159 | return ' '.join([p.rstrip()[:-1] for p in parts[:-1]] # Strip backslash+newline |
|
184 | return ' '.join([p.rstrip()[:-1] for p in parts[:-1]] # Strip backslash+newline | |
160 | + [parts[-1].rstrip()]) # Strip newline from last line |
|
185 | + [parts[-1].rstrip()]) # Strip newline from last line | |
161 |
|
186 | |||
162 | class TokenTransformBase: |
|
187 | class TokenTransformBase: | |
163 | """Base class for transformations which examine tokens. |
|
188 | """Base class for transformations which examine tokens. | |
164 |
|
189 | |||
165 | Special syntax should not be transformed when it occurs inside strings or |
|
190 | Special syntax should not be transformed when it occurs inside strings or | |
166 | comments. This is hard to reliably avoid with regexes. The solution is to |
|
191 | comments. This is hard to reliably avoid with regexes. The solution is to | |
167 | tokenise the code as Python, and recognise the special syntax in the tokens. |
|
192 | tokenise the code as Python, and recognise the special syntax in the tokens. | |
168 |
|
193 | |||
169 | IPython's special syntax is not valid Python syntax, so tokenising may go |
|
194 | IPython's special syntax is not valid Python syntax, so tokenising may go | |
170 | wrong after the special syntax starts. These classes therefore find and |
|
195 | wrong after the special syntax starts. These classes therefore find and | |
171 | transform *one* instance of special syntax at a time into regular Python |
|
196 | transform *one* instance of special syntax at a time into regular Python | |
172 | syntax. After each transformation, tokens are regenerated to find the next |
|
197 | syntax. After each transformation, tokens are regenerated to find the next | |
173 | piece of special syntax. |
|
198 | piece of special syntax. | |
174 |
|
199 | |||
175 | Subclasses need to implement one class method (find) |
|
200 | Subclasses need to implement one class method (find) | |
176 | and one regular method (transform). |
|
201 | and one regular method (transform). | |
177 |
|
202 | |||
178 | The priority attribute can select which transformation to apply if multiple |
|
203 | The priority attribute can select which transformation to apply if multiple | |
179 | transformers match in the same place. Lower numbers have higher priority. |
|
204 | transformers match in the same place. Lower numbers have higher priority. | |
180 | This allows "%magic?" to be turned into a help call rather than a magic call. |
|
205 | This allows "%magic?" to be turned into a help call rather than a magic call. | |
181 | """ |
|
206 | """ | |
182 | # Lower numbers -> higher priority (for matches in the same location) |
|
207 | # Lower numbers -> higher priority (for matches in the same location) | |
183 | priority = 10 |
|
208 | priority = 10 | |
184 |
|
209 | |||
185 | def sortby(self): |
|
210 | def sortby(self): | |
186 | return self.start_line, self.start_col, self.priority |
|
211 | return self.start_line, self.start_col, self.priority | |
187 |
|
212 | |||
188 | def __init__(self, start): |
|
213 | def __init__(self, start): | |
189 | self.start_line = start[0] - 1 # Shift from 1-index to 0-index |
|
214 | self.start_line = start[0] - 1 # Shift from 1-index to 0-index | |
190 | self.start_col = start[1] |
|
215 | self.start_col = start[1] | |
191 |
|
216 | |||
192 | @classmethod |
|
217 | @classmethod | |
193 | def find(cls, tokens_by_line): |
|
218 | def find(cls, tokens_by_line): | |
194 | """Find one instance of special syntax in the provided tokens. |
|
219 | """Find one instance of special syntax in the provided tokens. | |
195 |
|
220 | |||
196 | Tokens are grouped into logical lines for convenience, |
|
221 | Tokens are grouped into logical lines for convenience, | |
197 | so it is easy to e.g. look at the first token of each line. |
|
222 | so it is easy to e.g. look at the first token of each line. | |
198 | *tokens_by_line* is a list of lists of tokenize.TokenInfo objects. |
|
223 | *tokens_by_line* is a list of lists of tokenize.TokenInfo objects. | |
199 |
|
224 | |||
200 | This should return an instance of its class, pointing to the start |
|
225 | This should return an instance of its class, pointing to the start | |
201 | position it has found, or None if it found no match. |
|
226 | position it has found, or None if it found no match. | |
202 | """ |
|
227 | """ | |
203 | raise NotImplementedError |
|
228 | raise NotImplementedError | |
204 |
|
229 | |||
205 | def transform(self, lines: List[str]): |
|
230 | def transform(self, lines: List[str]): | |
206 | """Transform one instance of special syntax found by ``find()`` |
|
231 | """Transform one instance of special syntax found by ``find()`` | |
207 |
|
232 | |||
208 | Takes a list of strings representing physical lines, |
|
233 | Takes a list of strings representing physical lines, | |
209 | returns a similar list of transformed lines. |
|
234 | returns a similar list of transformed lines. | |
210 | """ |
|
235 | """ | |
211 | raise NotImplementedError |
|
236 | raise NotImplementedError | |
212 |
|
237 | |||
213 | class MagicAssign(TokenTransformBase): |
|
238 | class MagicAssign(TokenTransformBase): | |
214 | """Transformer for assignments from magics (a = %foo)""" |
|
239 | """Transformer for assignments from magics (a = %foo)""" | |
215 | @classmethod |
|
240 | @classmethod | |
216 | def find(cls, tokens_by_line): |
|
241 | def find(cls, tokens_by_line): | |
217 | """Find the first magic assignment (a = %foo) in the cell. |
|
242 | """Find the first magic assignment (a = %foo) in the cell. | |
218 | """ |
|
243 | """ | |
219 | for line in tokens_by_line: |
|
244 | for line in tokens_by_line: | |
220 | assign_ix = _find_assign_op(line) |
|
245 | assign_ix = _find_assign_op(line) | |
221 | if (assign_ix is not None) \ |
|
246 | if (assign_ix is not None) \ | |
222 | and (len(line) >= assign_ix + 2) \ |
|
247 | and (len(line) >= assign_ix + 2) \ | |
223 | and (line[assign_ix+1].string == '%') \ |
|
248 | and (line[assign_ix+1].string == '%') \ | |
224 | and (line[assign_ix+2].type == tokenize.NAME): |
|
249 | and (line[assign_ix+2].type == tokenize.NAME): | |
225 | return cls(line[assign_ix+1].start) |
|
250 | return cls(line[assign_ix+1].start) | |
226 |
|
251 | |||
227 | def transform(self, lines: List[str]): |
|
252 | def transform(self, lines: List[str]): | |
228 | """Transform a magic assignment found by the ``find()`` classmethod. |
|
253 | """Transform a magic assignment found by the ``find()`` classmethod. | |
229 | """ |
|
254 | """ | |
230 | start_line, start_col = self.start_line, self.start_col |
|
255 | start_line, start_col = self.start_line, self.start_col | |
231 | lhs = lines[start_line][:start_col] |
|
256 | lhs = lines[start_line][:start_col] | |
232 | end_line = find_end_of_continued_line(lines, start_line) |
|
257 | end_line = find_end_of_continued_line(lines, start_line) | |
233 | rhs = assemble_continued_line(lines, (start_line, start_col), end_line) |
|
258 | rhs = assemble_continued_line(lines, (start_line, start_col), end_line) | |
234 | assert rhs.startswith('%'), rhs |
|
259 | assert rhs.startswith('%'), rhs | |
235 | magic_name, _, args = rhs[1:].partition(' ') |
|
260 | magic_name, _, args = rhs[1:].partition(' ') | |
236 |
|
261 | |||
237 | lines_before = lines[:start_line] |
|
262 | lines_before = lines[:start_line] | |
238 | call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args) |
|
263 | call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args) | |
239 | new_line = lhs + call + '\n' |
|
264 | new_line = lhs + call + '\n' | |
240 | lines_after = lines[end_line+1:] |
|
265 | lines_after = lines[end_line+1:] | |
241 |
|
266 | |||
242 | return lines_before + [new_line] + lines_after |
|
267 | return lines_before + [new_line] + lines_after | |
243 |
|
268 | |||
244 |
|
269 | |||
245 | class SystemAssign(TokenTransformBase): |
|
270 | class SystemAssign(TokenTransformBase): | |
246 | """Transformer for assignments from system commands (a = !foo)""" |
|
271 | """Transformer for assignments from system commands (a = !foo)""" | |
247 | @classmethod |
|
272 | @classmethod | |
248 | def find(cls, tokens_by_line): |
|
273 | def find(cls, tokens_by_line): | |
249 | """Find the first system assignment (a = !foo) in the cell. |
|
274 | """Find the first system assignment (a = !foo) in the cell. | |
250 | """ |
|
275 | """ | |
251 | for line in tokens_by_line: |
|
276 | for line in tokens_by_line: | |
252 | assign_ix = _find_assign_op(line) |
|
277 | assign_ix = _find_assign_op(line) | |
253 | if (assign_ix is not None) \ |
|
278 | if (assign_ix is not None) \ | |
254 | and not line[assign_ix].line.strip().startswith('=') \ |
|
279 | and not line[assign_ix].line.strip().startswith('=') \ | |
255 | and (len(line) >= assign_ix + 2) \ |
|
280 | and (len(line) >= assign_ix + 2) \ | |
256 | and (line[assign_ix + 1].type == tokenize.ERRORTOKEN): |
|
281 | and (line[assign_ix + 1].type == tokenize.ERRORTOKEN): | |
257 | ix = assign_ix + 1 |
|
282 | ix = assign_ix + 1 | |
258 |
|
283 | |||
259 | while ix < len(line) and line[ix].type == tokenize.ERRORTOKEN: |
|
284 | while ix < len(line) and line[ix].type == tokenize.ERRORTOKEN: | |
260 | if line[ix].string == '!': |
|
285 | if line[ix].string == '!': | |
261 | return cls(line[ix].start) |
|
286 | return cls(line[ix].start) | |
262 | elif not line[ix].string.isspace(): |
|
287 | elif not line[ix].string.isspace(): | |
263 | break |
|
288 | break | |
264 | ix += 1 |
|
289 | ix += 1 | |
265 |
|
290 | |||
266 | def transform(self, lines: List[str]): |
|
291 | def transform(self, lines: List[str]): | |
267 | """Transform a system assignment found by the ``find()`` classmethod. |
|
292 | """Transform a system assignment found by the ``find()`` classmethod. | |
268 | """ |
|
293 | """ | |
269 | start_line, start_col = self.start_line, self.start_col |
|
294 | start_line, start_col = self.start_line, self.start_col | |
270 |
|
295 | |||
271 | lhs = lines[start_line][:start_col] |
|
296 | lhs = lines[start_line][:start_col] | |
272 | end_line = find_end_of_continued_line(lines, start_line) |
|
297 | end_line = find_end_of_continued_line(lines, start_line) | |
273 | rhs = assemble_continued_line(lines, (start_line, start_col), end_line) |
|
298 | rhs = assemble_continued_line(lines, (start_line, start_col), end_line) | |
274 | assert rhs.startswith('!'), rhs |
|
299 | assert rhs.startswith('!'), rhs | |
275 | cmd = rhs[1:] |
|
300 | cmd = rhs[1:] | |
276 |
|
301 | |||
277 | lines_before = lines[:start_line] |
|
302 | lines_before = lines[:start_line] | |
278 | call = "get_ipython().getoutput({!r})".format(cmd) |
|
303 | call = "get_ipython().getoutput({!r})".format(cmd) | |
279 | new_line = lhs + call + '\n' |
|
304 | new_line = lhs + call + '\n' | |
280 | lines_after = lines[end_line + 1:] |
|
305 | lines_after = lines[end_line + 1:] | |
281 |
|
306 | |||
282 | return lines_before + [new_line] + lines_after |
|
307 | return lines_before + [new_line] + lines_after | |
283 |
|
308 | |||
284 | # The escape sequences that define the syntax transformations IPython will |
|
309 | # The escape sequences that define the syntax transformations IPython will | |
285 | # apply to user input. These can NOT be just changed here: many regular |
|
310 | # apply to user input. These can NOT be just changed here: many regular | |
286 | # expressions and other parts of the code may use their hardcoded values, and |
|
311 | # expressions and other parts of the code may use their hardcoded values, and | |
287 | # for all intents and purposes they constitute the 'IPython syntax', so they |
|
312 | # for all intents and purposes they constitute the 'IPython syntax', so they | |
288 | # should be considered fixed. |
|
313 | # should be considered fixed. | |
289 |
|
314 | |||
290 | ESC_SHELL = '!' # Send line to underlying system shell |
|
315 | ESC_SHELL = '!' # Send line to underlying system shell | |
291 | ESC_SH_CAP = '!!' # Send line to system shell and capture output |
|
316 | ESC_SH_CAP = '!!' # Send line to system shell and capture output | |
292 | ESC_HELP = '?' # Find information about object |
|
317 | ESC_HELP = '?' # Find information about object | |
293 | ESC_HELP2 = '??' # Find extra-detailed information about object |
|
318 | ESC_HELP2 = '??' # Find extra-detailed information about object | |
294 | ESC_MAGIC = '%' # Call magic function |
|
319 | ESC_MAGIC = '%' # Call magic function | |
295 | ESC_MAGIC2 = '%%' # Call cell-magic function |
|
320 | ESC_MAGIC2 = '%%' # Call cell-magic function | |
296 | ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call |
|
321 | ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call | |
297 | ESC_QUOTE2 = ';' # Quote all args as a single string, call |
|
322 | ESC_QUOTE2 = ';' # Quote all args as a single string, call | |
298 | ESC_PAREN = '/' # Call first argument with rest of line as arguments |
|
323 | ESC_PAREN = '/' # Call first argument with rest of line as arguments | |
299 |
|
324 | |||
300 | ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'} |
|
325 | ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'} | |
301 | ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately |
|
326 | ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately | |
302 |
|
327 | |||
303 | def _make_help_call(target, esc, next_input=None): |
|
328 | def _make_help_call(target, esc, next_input=None): | |
304 | """Prepares a pinfo(2)/psearch call from a target name and the escape |
|
329 | """Prepares a pinfo(2)/psearch call from a target name and the escape | |
305 | (i.e. ? or ??)""" |
|
330 | (i.e. ? or ??)""" | |
306 | method = 'pinfo2' if esc == '??' \ |
|
331 | method = 'pinfo2' if esc == '??' \ | |
307 | else 'psearch' if '*' in target \ |
|
332 | else 'psearch' if '*' in target \ | |
308 | else 'pinfo' |
|
333 | else 'pinfo' | |
309 | arg = " ".join([method, target]) |
|
334 | arg = " ".join([method, target]) | |
310 | #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args) |
|
335 | #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args) | |
311 | t_magic_name, _, t_magic_arg_s = arg.partition(' ') |
|
336 | t_magic_name, _, t_magic_arg_s = arg.partition(' ') | |
312 | t_magic_name = t_magic_name.lstrip(ESC_MAGIC) |
|
337 | t_magic_name = t_magic_name.lstrip(ESC_MAGIC) | |
313 | if next_input is None: |
|
338 | if next_input is None: | |
314 | return 'get_ipython().run_line_magic(%r, %r)' % (t_magic_name, t_magic_arg_s) |
|
339 | return 'get_ipython().run_line_magic(%r, %r)' % (t_magic_name, t_magic_arg_s) | |
315 | else: |
|
340 | else: | |
316 | return 'get_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \ |
|
341 | return 'get_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \ | |
317 | (next_input, t_magic_name, t_magic_arg_s) |
|
342 | (next_input, t_magic_name, t_magic_arg_s) | |
318 |
|
343 | |||
319 | def _tr_help(content): |
|
344 | def _tr_help(content): | |
320 | """Translate lines escaped with: ? |
|
345 | """Translate lines escaped with: ? | |
321 |
|
346 | |||
322 | A naked help line should fire the intro help screen (shell.show_usage()) |
|
347 | A naked help line should fire the intro help screen (shell.show_usage()) | |
323 | """ |
|
348 | """ | |
324 | if not content: |
|
349 | if not content: | |
325 | return 'get_ipython().show_usage()' |
|
350 | return 'get_ipython().show_usage()' | |
326 |
|
351 | |||
327 | return _make_help_call(content, '?') |
|
352 | return _make_help_call(content, '?') | |
328 |
|
353 | |||
329 | def _tr_help2(content): |
|
354 | def _tr_help2(content): | |
330 | """Translate lines escaped with: ?? |
|
355 | """Translate lines escaped with: ?? | |
331 |
|
356 | |||
332 | A naked help line should fire the intro help screen (shell.show_usage()) |
|
357 | A naked help line should fire the intro help screen (shell.show_usage()) | |
333 | """ |
|
358 | """ | |
334 | if not content: |
|
359 | if not content: | |
335 | return 'get_ipython().show_usage()' |
|
360 | return 'get_ipython().show_usage()' | |
336 |
|
361 | |||
337 | return _make_help_call(content, '??') |
|
362 | return _make_help_call(content, '??') | |
338 |
|
363 | |||
339 | def _tr_magic(content): |
|
364 | def _tr_magic(content): | |
340 | "Translate lines escaped with a percent sign: %" |
|
365 | "Translate lines escaped with a percent sign: %" | |
341 | name, _, args = content.partition(' ') |
|
366 | name, _, args = content.partition(' ') | |
342 | return 'get_ipython().run_line_magic(%r, %r)' % (name, args) |
|
367 | return 'get_ipython().run_line_magic(%r, %r)' % (name, args) | |
343 |
|
368 | |||
344 | def _tr_quote(content): |
|
369 | def _tr_quote(content): | |
345 | "Translate lines escaped with a comma: ," |
|
370 | "Translate lines escaped with a comma: ," | |
346 | name, _, args = content.partition(' ') |
|
371 | name, _, args = content.partition(' ') | |
347 | return '%s("%s")' % (name, '", "'.join(args.split()) ) |
|
372 | return '%s("%s")' % (name, '", "'.join(args.split()) ) | |
348 |
|
373 | |||
349 | def _tr_quote2(content): |
|
374 | def _tr_quote2(content): | |
350 | "Translate lines escaped with a semicolon: ;" |
|
375 | "Translate lines escaped with a semicolon: ;" | |
351 | name, _, args = content.partition(' ') |
|
376 | name, _, args = content.partition(' ') | |
352 | return '%s("%s")' % (name, args) |
|
377 | return '%s("%s")' % (name, args) | |
353 |
|
378 | |||
354 | def _tr_paren(content): |
|
379 | def _tr_paren(content): | |
355 | "Translate lines escaped with a slash: /" |
|
380 | "Translate lines escaped with a slash: /" | |
356 | name, _, args = content.partition(' ') |
|
381 | name, _, args = content.partition(' ') | |
357 | return '%s(%s)' % (name, ", ".join(args.split())) |
|
382 | return '%s(%s)' % (name, ", ".join(args.split())) | |
358 |
|
383 | |||
359 | tr = { ESC_SHELL : 'get_ipython().system({!r})'.format, |
|
384 | tr = { ESC_SHELL : 'get_ipython().system({!r})'.format, | |
360 | ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format, |
|
385 | ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format, | |
361 | ESC_HELP : _tr_help, |
|
386 | ESC_HELP : _tr_help, | |
362 | ESC_HELP2 : _tr_help2, |
|
387 | ESC_HELP2 : _tr_help2, | |
363 | ESC_MAGIC : _tr_magic, |
|
388 | ESC_MAGIC : _tr_magic, | |
364 | ESC_QUOTE : _tr_quote, |
|
389 | ESC_QUOTE : _tr_quote, | |
365 | ESC_QUOTE2 : _tr_quote2, |
|
390 | ESC_QUOTE2 : _tr_quote2, | |
366 | ESC_PAREN : _tr_paren } |
|
391 | ESC_PAREN : _tr_paren } | |
367 |
|
392 | |||
368 | class EscapedCommand(TokenTransformBase): |
|
393 | class EscapedCommand(TokenTransformBase): | |
369 | """Transformer for escaped commands like %foo, !foo, or /foo""" |
|
394 | """Transformer for escaped commands like %foo, !foo, or /foo""" | |
370 | @classmethod |
|
395 | @classmethod | |
371 | def find(cls, tokens_by_line): |
|
396 | def find(cls, tokens_by_line): | |
372 | """Find the first escaped command (%foo, !foo, etc.) in the cell. |
|
397 | """Find the first escaped command (%foo, !foo, etc.) in the cell. | |
373 | """ |
|
398 | """ | |
374 | for line in tokens_by_line: |
|
399 | for line in tokens_by_line: | |
375 | if not line: |
|
400 | if not line: | |
376 | continue |
|
401 | continue | |
377 | ix = 0 |
|
402 | ix = 0 | |
378 | ll = len(line) |
|
403 | ll = len(line) | |
379 | while ll > ix and line[ix].type in {tokenize.INDENT, tokenize.DEDENT}: |
|
404 | while ll > ix and line[ix].type in {tokenize.INDENT, tokenize.DEDENT}: | |
380 | ix += 1 |
|
405 | ix += 1 | |
381 | if ix >= ll: |
|
406 | if ix >= ll: | |
382 | continue |
|
407 | continue | |
383 | if line[ix].string in ESCAPE_SINGLES: |
|
408 | if line[ix].string in ESCAPE_SINGLES: | |
384 | return cls(line[ix].start) |
|
409 | return cls(line[ix].start) | |
385 |
|
410 | |||
386 | def transform(self, lines): |
|
411 | def transform(self, lines): | |
387 | """Transform an escaped line found by the ``find()`` classmethod. |
|
412 | """Transform an escaped line found by the ``find()`` classmethod. | |
388 | """ |
|
413 | """ | |
389 | start_line, start_col = self.start_line, self.start_col |
|
414 | start_line, start_col = self.start_line, self.start_col | |
390 |
|
415 | |||
391 | indent = lines[start_line][:start_col] |
|
416 | indent = lines[start_line][:start_col] | |
392 | end_line = find_end_of_continued_line(lines, start_line) |
|
417 | end_line = find_end_of_continued_line(lines, start_line) | |
393 | line = assemble_continued_line(lines, (start_line, start_col), end_line) |
|
418 | line = assemble_continued_line(lines, (start_line, start_col), end_line) | |
394 |
|
419 | |||
395 | if len(line) > 1 and line[:2] in ESCAPE_DOUBLES: |
|
420 | if len(line) > 1 and line[:2] in ESCAPE_DOUBLES: | |
396 | escape, content = line[:2], line[2:] |
|
421 | escape, content = line[:2], line[2:] | |
397 | else: |
|
422 | else: | |
398 | escape, content = line[:1], line[1:] |
|
423 | escape, content = line[:1], line[1:] | |
399 |
|
424 | |||
400 | if escape in tr: |
|
425 | if escape in tr: | |
401 | call = tr[escape](content) |
|
426 | call = tr[escape](content) | |
402 | else: |
|
427 | else: | |
403 | call = '' |
|
428 | call = '' | |
404 |
|
429 | |||
405 | lines_before = lines[:start_line] |
|
430 | lines_before = lines[:start_line] | |
406 | new_line = indent + call + '\n' |
|
431 | new_line = indent + call + '\n' | |
407 | lines_after = lines[end_line + 1:] |
|
432 | lines_after = lines[end_line + 1:] | |
408 |
|
433 | |||
409 | return lines_before + [new_line] + lines_after |
|
434 | return lines_before + [new_line] + lines_after | |
410 |
|
435 | |||
411 | _help_end_re = re.compile(r"""(%{0,2} |
|
436 | _help_end_re = re.compile(r"""(%{0,2} | |
412 | (?!\d)[\w*]+ # Variable name |
|
437 | (?!\d)[\w*]+ # Variable name | |
413 | (\.(?!\d)[\w*]+)* # .etc.etc |
|
438 | (\.(?!\d)[\w*]+)* # .etc.etc | |
414 | ) |
|
439 | ) | |
415 | (\?\??)$ # ? or ?? |
|
440 | (\?\??)$ # ? or ?? | |
416 | """, |
|
441 | """, | |
417 | re.VERBOSE) |
|
442 | re.VERBOSE) | |
418 |
|
443 | |||
419 | class HelpEnd(TokenTransformBase): |
|
444 | class HelpEnd(TokenTransformBase): | |
420 | """Transformer for help syntax: obj? and obj??""" |
|
445 | """Transformer for help syntax: obj? and obj??""" | |
421 | # This needs to be higher priority (lower number) than EscapedCommand so |
|
446 | # This needs to be higher priority (lower number) than EscapedCommand so | |
422 | # that inspecting magics (%foo?) works. |
|
447 | # that inspecting magics (%foo?) works. | |
423 | priority = 5 |
|
448 | priority = 5 | |
424 |
|
449 | |||
425 | def __init__(self, start, q_locn): |
|
450 | def __init__(self, start, q_locn): | |
426 | super().__init__(start) |
|
451 | super().__init__(start) | |
427 | self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed |
|
452 | self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed | |
428 | self.q_col = q_locn[1] |
|
453 | self.q_col = q_locn[1] | |
429 |
|
454 | |||
430 | @classmethod |
|
455 | @classmethod | |
431 | def find(cls, tokens_by_line): |
|
456 | def find(cls, tokens_by_line): | |
432 | """Find the first help command (foo?) in the cell. |
|
457 | """Find the first help command (foo?) in the cell. | |
433 | """ |
|
458 | """ | |
434 | for line in tokens_by_line: |
|
459 | for line in tokens_by_line: | |
435 | # Last token is NEWLINE; look at last but one |
|
460 | # Last token is NEWLINE; look at last but one | |
436 | if len(line) > 2 and line[-2].string == '?': |
|
461 | if len(line) > 2 and line[-2].string == '?': | |
437 | # Find the first token that's not INDENT/DEDENT |
|
462 | # Find the first token that's not INDENT/DEDENT | |
438 | ix = 0 |
|
463 | ix = 0 | |
439 | while line[ix].type in {tokenize.INDENT, tokenize.DEDENT}: |
|
464 | while line[ix].type in {tokenize.INDENT, tokenize.DEDENT}: | |
440 | ix += 1 |
|
465 | ix += 1 | |
441 | return cls(line[ix].start, line[-2].start) |
|
466 | return cls(line[ix].start, line[-2].start) | |
442 |
|
467 | |||
443 | def transform(self, lines): |
|
468 | def transform(self, lines): | |
444 | """Transform a help command found by the ``find()`` classmethod. |
|
469 | """Transform a help command found by the ``find()`` classmethod. | |
445 | """ |
|
470 | """ | |
446 | piece = ''.join(lines[self.start_line:self.q_line+1]) |
|
471 | piece = ''.join(lines[self.start_line:self.q_line+1]) | |
447 | indent, content = piece[:self.start_col], piece[self.start_col:] |
|
472 | indent, content = piece[:self.start_col], piece[self.start_col:] | |
448 | lines_before = lines[:self.start_line] |
|
473 | lines_before = lines[:self.start_line] | |
449 | lines_after = lines[self.q_line + 1:] |
|
474 | lines_after = lines[self.q_line + 1:] | |
450 |
|
475 | |||
451 | m = _help_end_re.search(content) |
|
476 | m = _help_end_re.search(content) | |
452 | if not m: |
|
477 | if not m: | |
453 | raise SyntaxError(content) |
|
478 | raise SyntaxError(content) | |
454 | assert m is not None, content |
|
479 | assert m is not None, content | |
455 | target = m.group(1) |
|
480 | target = m.group(1) | |
456 | esc = m.group(3) |
|
481 | esc = m.group(3) | |
457 |
|
482 | |||
458 | # If we're mid-command, put it back on the next prompt for the user. |
|
483 | # If we're mid-command, put it back on the next prompt for the user. | |
459 | next_input = None |
|
484 | next_input = None | |
460 | if (not lines_before) and (not lines_after) \ |
|
485 | if (not lines_before) and (not lines_after) \ | |
461 | and content.strip() != m.group(0): |
|
486 | and content.strip() != m.group(0): | |
462 | next_input = content.rstrip('?\n') |
|
487 | next_input = content.rstrip('?\n') | |
463 |
|
488 | |||
464 | call = _make_help_call(target, esc, next_input=next_input) |
|
489 | call = _make_help_call(target, esc, next_input=next_input) | |
465 | new_line = indent + call + '\n' |
|
490 | new_line = indent + call + '\n' | |
466 |
|
491 | |||
467 | return lines_before + [new_line] + lines_after |
|
492 | return lines_before + [new_line] + lines_after | |
468 |
|
493 | |||
469 | def make_tokens_by_line(lines:List[str]): |
|
494 | def make_tokens_by_line(lines:List[str]): | |
470 | """Tokenize a series of lines and group tokens by line. |
|
495 | """Tokenize a series of lines and group tokens by line. | |
471 |
|
496 | |||
472 | The tokens for a multiline Python string or expression are grouped as one |
|
497 | The tokens for a multiline Python string or expression are grouped as one | |
473 | line. All lines except the last lines should keep their line ending ('\\n', |
|
498 | line. All lines except the last lines should keep their line ending ('\\n', | |
474 | '\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)` |
|
499 | '\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)` | |
475 | for example when passing block of text to this function. |
|
500 | for example when passing block of text to this function. | |
476 |
|
501 | |||
477 | """ |
|
502 | """ | |
478 | # NL tokens are used inside multiline expressions, but also after blank |
|
503 | # NL tokens are used inside multiline expressions, but also after blank | |
479 | # lines or comments. This is intentional - see https://bugs.python.org/issue17061 |
|
504 | # lines or comments. This is intentional - see https://bugs.python.org/issue17061 | |
480 | # We want to group the former case together but split the latter, so we |
|
505 | # We want to group the former case together but split the latter, so we | |
481 | # track parentheses level, similar to the internals of tokenize. |
|
506 | # track parentheses level, similar to the internals of tokenize. | |
482 |
|
507 | |||
483 | # reexported from token on 3.7+ |
|
508 | # reexported from token on 3.7+ | |
484 | NEWLINE, NL = tokenize.NEWLINE, tokenize.NL # type: ignore |
|
509 | NEWLINE, NL = tokenize.NEWLINE, tokenize.NL # type: ignore | |
485 | tokens_by_line:List[List[Any]] = [[]] |
|
510 | tokens_by_line:List[List[Any]] = [[]] | |
486 | if len(lines) > 1 and not lines[0].endswith(('\n', '\r', '\r\n', '\x0b', '\x0c')): |
|
511 | if len(lines) > 1 and not lines[0].endswith(('\n', '\r', '\r\n', '\x0b', '\x0c')): | |
487 | warnings.warn("`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified") |
|
512 | warnings.warn("`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified") | |
488 | parenlev = 0 |
|
513 | parenlev = 0 | |
489 | try: |
|
514 | try: | |
490 | for token in tokenize.generate_tokens(iter(lines).__next__): |
|
515 | for token in tokenize.generate_tokens(iter(lines).__next__): | |
491 | tokens_by_line[-1].append(token) |
|
516 | tokens_by_line[-1].append(token) | |
492 | if (token.type == NEWLINE) \ |
|
517 | if (token.type == NEWLINE) \ | |
493 | or ((token.type == NL) and (parenlev <= 0)): |
|
518 | or ((token.type == NL) and (parenlev <= 0)): | |
494 | tokens_by_line.append([]) |
|
519 | tokens_by_line.append([]) | |
495 | elif token.string in {'(', '[', '{'}: |
|
520 | elif token.string in {'(', '[', '{'}: | |
496 | parenlev += 1 |
|
521 | parenlev += 1 | |
497 | elif token.string in {')', ']', '}'}: |
|
522 | elif token.string in {')', ']', '}'}: | |
498 | if parenlev > 0: |
|
523 | if parenlev > 0: | |
499 | parenlev -= 1 |
|
524 | parenlev -= 1 | |
500 | except tokenize.TokenError: |
|
525 | except tokenize.TokenError: | |
501 | # Input ended in a multiline string or expression. That's OK for us. |
|
526 | # Input ended in a multiline string or expression. That's OK for us. | |
502 | pass |
|
527 | pass | |
503 |
|
528 | |||
504 |
|
529 | |||
505 | if not tokens_by_line[-1]: |
|
530 | if not tokens_by_line[-1]: | |
506 | tokens_by_line.pop() |
|
531 | tokens_by_line.pop() | |
507 |
|
532 | |||
508 |
|
533 | |||
509 | return tokens_by_line |
|
534 | return tokens_by_line | |
510 |
|
535 | |||
|
536 | ||||
|
537 | def has_sunken_brackets(tokens: List[tokenize.TokenInfo]): | |||
|
538 | """Check if the depth of brackets in the list of tokens drops below 0""" | |||
|
539 | parenlev = 0 | |||
|
540 | for token in tokens: | |||
|
541 | if token.string in {"(", "[", "{"}: | |||
|
542 | parenlev += 1 | |||
|
543 | elif token.string in {")", "]", "}"}: | |||
|
544 | parenlev -= 1 | |||
|
545 | if parenlev < 0: | |||
|
546 | return True | |||
|
547 | return False | |||
|
548 | ||||
|
549 | ||||
511 | def show_linewise_tokens(s: str): |
|
550 | def show_linewise_tokens(s: str): | |
512 | """For investigation and debugging""" |
|
551 | """For investigation and debugging""" | |
513 | if not s.endswith('\n'): |
|
552 | if not s.endswith('\n'): | |
514 | s += '\n' |
|
553 | s += '\n' | |
515 | lines = s.splitlines(keepends=True) |
|
554 | lines = s.splitlines(keepends=True) | |
516 | for line in make_tokens_by_line(lines): |
|
555 | for line in make_tokens_by_line(lines): | |
517 | print("Line -------") |
|
556 | print("Line -------") | |
518 | for tokinfo in line: |
|
557 | for tokinfo in line: | |
519 | print(" ", tokinfo) |
|
558 | print(" ", tokinfo) | |
520 |
|
559 | |||
521 | # Arbitrary limit to prevent getting stuck in infinite loops |
|
560 | # Arbitrary limit to prevent getting stuck in infinite loops | |
522 | TRANSFORM_LOOP_LIMIT = 500 |
|
561 | TRANSFORM_LOOP_LIMIT = 500 | |
523 |
|
562 | |||
524 | class TransformerManager: |
|
563 | class TransformerManager: | |
525 | """Applies various transformations to a cell or code block. |
|
564 | """Applies various transformations to a cell or code block. | |
526 |
|
565 | |||
527 | The key methods for external use are ``transform_cell()`` |
|
566 | The key methods for external use are ``transform_cell()`` | |
528 | and ``check_complete()``. |
|
567 | and ``check_complete()``. | |
529 | """ |
|
568 | """ | |
530 | def __init__(self): |
|
569 | def __init__(self): | |
531 | self.cleanup_transforms = [ |
|
570 | self.cleanup_transforms = [ | |
532 | leading_empty_lines, |
|
571 | leading_empty_lines, | |
533 | leading_indent, |
|
572 | leading_indent, | |
534 | classic_prompt, |
|
573 | classic_prompt, | |
535 | ipython_prompt, |
|
574 | ipython_prompt, | |
536 | ] |
|
575 | ] | |
537 | self.line_transforms = [ |
|
576 | self.line_transforms = [ | |
538 | cell_magic, |
|
577 | cell_magic, | |
539 | ] |
|
578 | ] | |
540 | self.token_transformers = [ |
|
579 | self.token_transformers = [ | |
541 | MagicAssign, |
|
580 | MagicAssign, | |
542 | SystemAssign, |
|
581 | SystemAssign, | |
543 | EscapedCommand, |
|
582 | EscapedCommand, | |
544 | HelpEnd, |
|
583 | HelpEnd, | |
545 | ] |
|
584 | ] | |
546 |
|
585 | |||
547 | def do_one_token_transform(self, lines): |
|
586 | def do_one_token_transform(self, lines): | |
548 | """Find and run the transform earliest in the code. |
|
587 | """Find and run the transform earliest in the code. | |
549 |
|
588 | |||
550 | Returns (changed, lines). |
|
589 | Returns (changed, lines). | |
551 |
|
590 | |||
552 | This method is called repeatedly until changed is False, indicating |
|
591 | This method is called repeatedly until changed is False, indicating | |
553 | that all available transformations are complete. |
|
592 | that all available transformations are complete. | |
554 |
|
593 | |||
555 | The tokens following IPython special syntax might not be valid, so |
|
594 | The tokens following IPython special syntax might not be valid, so | |
556 | the transformed code is retokenised every time to identify the next |
|
595 | the transformed code is retokenised every time to identify the next | |
557 | piece of special syntax. Hopefully long code cells are mostly valid |
|
596 | piece of special syntax. Hopefully long code cells are mostly valid | |
558 | Python, not using lots of IPython special syntax, so this shouldn't be |
|
597 | Python, not using lots of IPython special syntax, so this shouldn't be | |
559 | a performance issue. |
|
598 | a performance issue. | |
560 | """ |
|
599 | """ | |
561 | tokens_by_line = make_tokens_by_line(lines) |
|
600 | tokens_by_line = make_tokens_by_line(lines) | |
562 | candidates = [] |
|
601 | candidates = [] | |
563 | for transformer_cls in self.token_transformers: |
|
602 | for transformer_cls in self.token_transformers: | |
564 | transformer = transformer_cls.find(tokens_by_line) |
|
603 | transformer = transformer_cls.find(tokens_by_line) | |
565 | if transformer: |
|
604 | if transformer: | |
566 | candidates.append(transformer) |
|
605 | candidates.append(transformer) | |
567 |
|
606 | |||
568 | if not candidates: |
|
607 | if not candidates: | |
569 | # Nothing to transform |
|
608 | # Nothing to transform | |
570 | return False, lines |
|
609 | return False, lines | |
571 | ordered_transformers = sorted(candidates, key=TokenTransformBase.sortby) |
|
610 | ordered_transformers = sorted(candidates, key=TokenTransformBase.sortby) | |
572 | for transformer in ordered_transformers: |
|
611 | for transformer in ordered_transformers: | |
573 | try: |
|
612 | try: | |
574 | return True, transformer.transform(lines) |
|
613 | return True, transformer.transform(lines) | |
575 | except SyntaxError: |
|
614 | except SyntaxError: | |
576 | pass |
|
615 | pass | |
577 | return False, lines |
|
616 | return False, lines | |
578 |
|
617 | |||
579 | def do_token_transforms(self, lines): |
|
618 | def do_token_transforms(self, lines): | |
580 | for _ in range(TRANSFORM_LOOP_LIMIT): |
|
619 | for _ in range(TRANSFORM_LOOP_LIMIT): | |
581 | changed, lines = self.do_one_token_transform(lines) |
|
620 | changed, lines = self.do_one_token_transform(lines) | |
582 | if not changed: |
|
621 | if not changed: | |
583 | return lines |
|
622 | return lines | |
584 |
|
623 | |||
585 | raise RuntimeError("Input transformation still changing after " |
|
624 | raise RuntimeError("Input transformation still changing after " | |
586 | "%d iterations. Aborting." % TRANSFORM_LOOP_LIMIT) |
|
625 | "%d iterations. Aborting." % TRANSFORM_LOOP_LIMIT) | |
587 |
|
626 | |||
588 | def transform_cell(self, cell: str) -> str: |
|
627 | def transform_cell(self, cell: str) -> str: | |
589 | """Transforms a cell of input code""" |
|
628 | """Transforms a cell of input code""" | |
590 | if not cell.endswith('\n'): |
|
629 | if not cell.endswith('\n'): | |
591 | cell += '\n' # Ensure the cell has a trailing newline |
|
630 | cell += '\n' # Ensure the cell has a trailing newline | |
592 | lines = cell.splitlines(keepends=True) |
|
631 | lines = cell.splitlines(keepends=True) | |
593 | for transform in self.cleanup_transforms + self.line_transforms: |
|
632 | for transform in self.cleanup_transforms + self.line_transforms: | |
594 | lines = transform(lines) |
|
633 | lines = transform(lines) | |
595 |
|
634 | |||
596 | lines = self.do_token_transforms(lines) |
|
635 | lines = self.do_token_transforms(lines) | |
597 | return ''.join(lines) |
|
636 | return ''.join(lines) | |
598 |
|
637 | |||
599 | def check_complete(self, cell: str): |
|
638 | def check_complete(self, cell: str): | |
600 | """Return whether a block of code is ready to execute, or should be continued |
|
639 | """Return whether a block of code is ready to execute, or should be continued | |
601 |
|
640 | |||
602 | Parameters |
|
641 | Parameters | |
603 | ---------- |
|
642 | ---------- | |
604 | source : string |
|
643 | source : string | |
605 | Python input code, which can be multiline. |
|
644 | Python input code, which can be multiline. | |
606 |
|
645 | |||
607 | Returns |
|
646 | Returns | |
608 | ------- |
|
647 | ------- | |
609 | status : str |
|
648 | status : str | |
610 | One of 'complete', 'incomplete', or 'invalid' if source is not a |
|
649 | One of 'complete', 'incomplete', or 'invalid' if source is not a | |
611 | prefix of valid code. |
|
650 | prefix of valid code. | |
612 | indent_spaces : int or None |
|
651 | indent_spaces : int or None | |
613 | The number of spaces by which to indent the next line of code. If |
|
652 | The number of spaces by which to indent the next line of code. If | |
614 | status is not 'incomplete', this is None. |
|
653 | status is not 'incomplete', this is None. | |
615 | """ |
|
654 | """ | |
616 | # Remember if the lines ends in a new line. |
|
655 | # Remember if the lines ends in a new line. | |
617 | ends_with_newline = False |
|
656 | ends_with_newline = False | |
618 | for character in reversed(cell): |
|
657 | for character in reversed(cell): | |
619 | if character == '\n': |
|
658 | if character == '\n': | |
620 | ends_with_newline = True |
|
659 | ends_with_newline = True | |
621 | break |
|
660 | break | |
622 | elif character.strip(): |
|
661 | elif character.strip(): | |
623 | break |
|
662 | break | |
624 | else: |
|
663 | else: | |
625 | continue |
|
664 | continue | |
626 |
|
665 | |||
627 | if not ends_with_newline: |
|
666 | if not ends_with_newline: | |
628 | # Append an newline for consistent tokenization |
|
667 | # Append an newline for consistent tokenization | |
629 | # See https://bugs.python.org/issue33899 |
|
668 | # See https://bugs.python.org/issue33899 | |
630 | cell += '\n' |
|
669 | cell += '\n' | |
631 |
|
670 | |||
632 | lines = cell.splitlines(keepends=True) |
|
671 | lines = cell.splitlines(keepends=True) | |
633 |
|
672 | |||
634 | if not lines: |
|
673 | if not lines: | |
635 | return 'complete', None |
|
674 | return 'complete', None | |
636 |
|
675 | |||
637 | if lines[-1].endswith('\\'): |
|
676 | if lines[-1].endswith('\\'): | |
638 | # Explicit backslash continuation |
|
677 | # Explicit backslash continuation | |
639 | return 'incomplete', find_last_indent(lines) |
|
678 | return 'incomplete', find_last_indent(lines) | |
640 |
|
679 | |||
641 | try: |
|
680 | try: | |
642 | for transform in self.cleanup_transforms: |
|
681 | for transform in self.cleanup_transforms: | |
643 | if not getattr(transform, 'has_side_effects', False): |
|
682 | if not getattr(transform, 'has_side_effects', False): | |
644 | lines = transform(lines) |
|
683 | lines = transform(lines) | |
645 | except SyntaxError: |
|
684 | except SyntaxError: | |
646 | return 'invalid', None |
|
685 | return 'invalid', None | |
647 |
|
686 | |||
648 | if lines[0].startswith('%%'): |
|
687 | if lines[0].startswith('%%'): | |
649 | # Special case for cell magics - completion marked by blank line |
|
688 | # Special case for cell magics - completion marked by blank line | |
650 | if lines[-1].strip(): |
|
689 | if lines[-1].strip(): | |
651 | return 'incomplete', find_last_indent(lines) |
|
690 | return 'incomplete', find_last_indent(lines) | |
652 | else: |
|
691 | else: | |
653 | return 'complete', None |
|
692 | return 'complete', None | |
654 |
|
693 | |||
655 | try: |
|
694 | try: | |
656 | for transform in self.line_transforms: |
|
695 | for transform in self.line_transforms: | |
657 | if not getattr(transform, 'has_side_effects', False): |
|
696 | if not getattr(transform, 'has_side_effects', False): | |
658 | lines = transform(lines) |
|
697 | lines = transform(lines) | |
659 | lines = self.do_token_transforms(lines) |
|
698 | lines = self.do_token_transforms(lines) | |
660 | except SyntaxError: |
|
699 | except SyntaxError: | |
661 | return 'invalid', None |
|
700 | return 'invalid', None | |
662 |
|
701 | |||
663 | tokens_by_line = make_tokens_by_line(lines) |
|
702 | tokens_by_line = make_tokens_by_line(lines) | |
664 |
|
703 | |||
|
704 | # Bail if we got one line and there are more closing parentheses than | |||
|
705 | # the opening ones | |||
|
706 | if ( | |||
|
707 | len(lines) == 1 | |||
|
708 | and tokens_by_line | |||
|
709 | and has_sunken_brackets(tokens_by_line[0]) | |||
|
710 | ): | |||
|
711 | return "invalid", None | |||
|
712 | ||||
665 | if not tokens_by_line: |
|
713 | if not tokens_by_line: | |
666 | return 'incomplete', find_last_indent(lines) |
|
714 | return 'incomplete', find_last_indent(lines) | |
667 |
|
715 | |||
668 | if tokens_by_line[-1][-1].type != tokenize.ENDMARKER: |
|
716 | if tokens_by_line[-1][-1].type != tokenize.ENDMARKER: | |
669 | # We're in a multiline string or expression |
|
717 | # We're in a multiline string or expression | |
670 | return 'incomplete', find_last_indent(lines) |
|
718 | return 'incomplete', find_last_indent(lines) | |
671 |
|
719 | |||
672 | newline_types = {tokenize.NEWLINE, tokenize.COMMENT, tokenize.ENDMARKER} # type: ignore |
|
720 | newline_types = {tokenize.NEWLINE, tokenize.COMMENT, tokenize.ENDMARKER} # type: ignore | |
673 |
|
721 | |||
674 | # Pop the last line which only contains DEDENTs and ENDMARKER |
|
722 | # Pop the last line which only contains DEDENTs and ENDMARKER | |
675 | last_token_line = None |
|
723 | last_token_line = None | |
676 | if {t.type for t in tokens_by_line[-1]} in [ |
|
724 | if {t.type for t in tokens_by_line[-1]} in [ | |
677 | {tokenize.DEDENT, tokenize.ENDMARKER}, |
|
725 | {tokenize.DEDENT, tokenize.ENDMARKER}, | |
678 | {tokenize.ENDMARKER} |
|
726 | {tokenize.ENDMARKER} | |
679 | ] and len(tokens_by_line) > 1: |
|
727 | ] and len(tokens_by_line) > 1: | |
680 | last_token_line = tokens_by_line.pop() |
|
728 | last_token_line = tokens_by_line.pop() | |
681 |
|
729 | |||
682 | while tokens_by_line[-1] and tokens_by_line[-1][-1].type in newline_types: |
|
730 | while tokens_by_line[-1] and tokens_by_line[-1][-1].type in newline_types: | |
683 | tokens_by_line[-1].pop() |
|
731 | tokens_by_line[-1].pop() | |
684 |
|
732 | |||
685 | if not tokens_by_line[-1]: |
|
733 | if not tokens_by_line[-1]: | |
686 | return 'incomplete', find_last_indent(lines) |
|
734 | return 'incomplete', find_last_indent(lines) | |
687 |
|
735 | |||
688 | if tokens_by_line[-1][-1].string == ':': |
|
736 | if tokens_by_line[-1][-1].string == ':': | |
689 | # The last line starts a block (e.g. 'if foo:') |
|
737 | # The last line starts a block (e.g. 'if foo:') | |
690 | ix = 0 |
|
738 | ix = 0 | |
691 | while tokens_by_line[-1][ix].type in {tokenize.INDENT, tokenize.DEDENT}: |
|
739 | while tokens_by_line[-1][ix].type in {tokenize.INDENT, tokenize.DEDENT}: | |
692 | ix += 1 |
|
740 | ix += 1 | |
693 |
|
741 | |||
694 | indent = tokens_by_line[-1][ix].start[1] |
|
742 | indent = tokens_by_line[-1][ix].start[1] | |
695 | return 'incomplete', indent + 4 |
|
743 | return 'incomplete', indent + 4 | |
696 |
|
744 | |||
697 | if tokens_by_line[-1][0].line.endswith('\\'): |
|
745 | if tokens_by_line[-1][0].line.endswith('\\'): | |
698 | return 'incomplete', None |
|
746 | return 'incomplete', None | |
699 |
|
747 | |||
700 | # At this point, our checks think the code is complete (or invalid). |
|
748 | # At this point, our checks think the code is complete (or invalid). | |
701 | # We'll use codeop.compile_command to check this with the real parser |
|
749 | # We'll use codeop.compile_command to check this with the real parser | |
702 | try: |
|
750 | try: | |
703 | with warnings.catch_warnings(): |
|
751 | with warnings.catch_warnings(): | |
704 | warnings.simplefilter('error', SyntaxWarning) |
|
752 | warnings.simplefilter('error', SyntaxWarning) | |
705 | res = compile_command(''.join(lines), symbol='exec') |
|
753 | res = compile_command(''.join(lines), symbol='exec') | |
706 | except (SyntaxError, OverflowError, ValueError, TypeError, |
|
754 | except (SyntaxError, OverflowError, ValueError, TypeError, | |
707 | MemoryError, SyntaxWarning): |
|
755 | MemoryError, SyntaxWarning): | |
708 | return 'invalid', None |
|
756 | return 'invalid', None | |
709 | else: |
|
757 | else: | |
710 | if res is None: |
|
758 | if res is None: | |
711 | return 'incomplete', find_last_indent(lines) |
|
759 | return 'incomplete', find_last_indent(lines) | |
712 |
|
760 | |||
713 | if last_token_line and last_token_line[0].type == tokenize.DEDENT: |
|
761 | if last_token_line and last_token_line[0].type == tokenize.DEDENT: | |
714 | if ends_with_newline: |
|
762 | if ends_with_newline: | |
715 | return 'complete', None |
|
763 | return 'complete', None | |
716 | return 'incomplete', find_last_indent(lines) |
|
764 | return 'incomplete', find_last_indent(lines) | |
717 |
|
765 | |||
718 | # If there's a blank line at the end, assume we're ready to execute |
|
766 | # If there's a blank line at the end, assume we're ready to execute | |
719 | if not lines[-1].strip(): |
|
767 | if not lines[-1].strip(): | |
720 | return 'complete', None |
|
768 | return 'complete', None | |
721 |
|
769 | |||
722 | return 'complete', None |
|
770 | return 'complete', None | |
723 |
|
771 | |||
724 |
|
772 | |||
725 | def find_last_indent(lines): |
|
773 | def find_last_indent(lines): | |
726 | m = _indent_re.match(lines[-1]) |
|
774 | m = _indent_re.match(lines[-1]) | |
727 | if not m: |
|
775 | if not m: | |
728 | return 0 |
|
776 | return 0 | |
729 | return len(m.group(0).replace('\t', ' '*4)) |
|
777 | return len(m.group(0).replace('\t', ' '*4)) | |
|
778 | ||||
|
779 | ||||
|
780 | class MaybeAsyncCompile(Compile): | |||
|
781 | def __init__(self, extra_flags=0): | |||
|
782 | super().__init__() | |||
|
783 | self.flags |= extra_flags | |||
|
784 | ||||
|
785 | def __call__(self, *args, **kwds): | |||
|
786 | return compile(*args, **kwds) | |||
|
787 | ||||
|
788 | ||||
|
789 | class MaybeAsyncCommandCompiler(CommandCompiler): | |||
|
790 | def __init__(self, extra_flags=0): | |||
|
791 | self.compiler = MaybeAsyncCompile(extra_flags=extra_flags) | |||
|
792 | ||||
|
793 | ||||
|
794 | if (sys.version_info.major, sys.version_info.minor) >= (3, 8): | |||
|
795 | _extra_flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT | |||
|
796 | else: | |||
|
797 | _extra_flags = ast.PyCF_ONLY_AST | |||
|
798 | ||||
|
799 | compile_command = MaybeAsyncCommandCompiler(extra_flags=_extra_flags) |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed | ||
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now